2025-01-28 15:42:50 -06:00
|
|
|
package remotefetcher
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2025-02-08 15:17:34 -06:00
|
|
|
type RemoteFetcher interface {
|
2025-01-28 15:42:50 -06:00
|
|
|
// Fetch retrieves the configuration from the specified URL or source
|
|
|
|
// Returns the raw data as bytes or an error
|
|
|
|
Fetch(source string) ([]byte, error)
|
|
|
|
|
|
|
|
// Parse decodes the raw data into a Go structure (e.g., Commands, CommandLists)
|
|
|
|
// Takes the raw data as input and populates the target interface
|
|
|
|
Parse(data []byte, target interface{}) error
|
|
|
|
|
|
|
|
// Hash returns the hash of the configuration data
|
|
|
|
Hash(data []byte) string
|
|
|
|
}
|
|
|
|
|
2025-02-08 15:17:34 -06:00
|
|
|
// ErrIgnoreFileNotFound is returned when the file is not found and should be ignored
|
|
|
|
var ErrIgnoreFileNotFound = errors.New("remotefetcher: file not found")
|
2025-01-28 15:42:50 -06:00
|
|
|
|
2025-02-08 15:17:34 -06:00
|
|
|
func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (RemoteFetcher, error) {
|
|
|
|
var fetcher RemoteFetcher
|
2025-01-28 15:42:50 -06:00
|
|
|
|
|
|
|
config := FetcherConfig{}
|
|
|
|
for _, option := range options {
|
|
|
|
option(&config)
|
|
|
|
}
|
2025-02-08 15:17:34 -06:00
|
|
|
|
|
|
|
// If FileType is empty (i.e. WithFileType was not called), yaml is the default file type
|
|
|
|
if strings.TrimSpace(config.FileType) == "" {
|
|
|
|
config.FileType = "yaml"
|
|
|
|
}
|
2025-01-28 15:42:50 -06:00
|
|
|
if strings.HasPrefix(source, "http") || strings.HasPrefix(source, "https") {
|
|
|
|
fetcher = NewHTTPFetcher(options...)
|
|
|
|
} else if strings.HasPrefix(source, "s3") {
|
|
|
|
var err error
|
2025-02-08 15:17:34 -06:00
|
|
|
fetcher, err = NewS3Fetcher(source, options...)
|
2025-01-28 15:42:50 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fetcher = &LocalFetcher{}
|
|
|
|
|
|
|
|
return fetcher, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: should local files be cached?
|
|
|
|
|
|
|
|
data, err := fetcher.Fetch(source)
|
|
|
|
if err != nil {
|
|
|
|
if config.IgnoreFileNotFound && isFileNotFoundError(err) {
|
2025-02-08 15:17:34 -06:00
|
|
|
return nil, ErrIgnoreFileNotFound
|
2025-01-28 15:42:50 -06:00
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hash := fetcher.Hash(data)
|
|
|
|
if cachedData, cacheMeta, exists := cache.Get(hash); exists {
|
|
|
|
return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil
|
|
|
|
}
|
|
|
|
|
2025-02-08 15:17:34 -06:00
|
|
|
cacheData, err := cache.Set(source, hash, data, config.FileType)
|
2025-01-28 15:42:50 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &CachedFetcher{data: data, path: cacheData.Path, dataType: cacheData.Type}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isFileNotFoundError(err error) bool {
|
|
|
|
// Implement logic to check if the error is a "file not found" error
|
|
|
|
// This can be based on the error type or message
|
|
|
|
return strings.Contains(err.Error(), "file not found")
|
|
|
|
}
|