From abb1a3ece04bd76a3766ae25648590abfb66ac88 Mon Sep 17 00:00:00 2001 From: Mehul-Kumar-27 <202151092@iiitvadodara.ac.in> Date: Tue, 5 Nov 2024 02:08:20 +0530 Subject: [PATCH] making config.json work with the replicator --- config.json | 5 +- internal/config/config.go | 321 ++++++++++++++------------------ internal/config/new_config.go | 136 -------------- internal/satellite/satellite.go | 13 +- internal/state/state_process.go | 163 +++++++++++----- internal/utils/utils.go | 2 + main.go | 55 +----- 7 files changed, 267 insertions(+), 428 deletions(-) delete mode 100644 internal/config/new_config.go diff --git a/config.json b/config.json index 1e90a3e..807d2f9 100644 --- a/config.json +++ b/config.json @@ -9,7 +9,8 @@ "log_level": "info", "own_registry_adr": "127.0.0.1", "own_registry_port": "8585", - "states": ["https://registry.bupd.xyz/satellite/group10/state:latest"], + "states": ["https://registry.bupd.xyz/satellite-test-group-state/state:latest"], "url_or_file": "https://registry.bupd.xyz", - "zotconfigpath": "./registry/config.json" + "zotconfigpath": "./registry/config.json", + "use_unsecure": true } diff --git a/internal/config/config.go b/internal/config/config.go index 11d0d59..2e2a9bb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,185 +1,140 @@ package config -// import ( -// "fmt" -// "os" - -// "github.com/joho/godotenv" -// "github.com/spf13/viper" -// ) - -// var AppConfig *Config - -// type Config struct { -// log_level string -// own_registry bool -// own_registry_adr string -// own_registry_port string -// zot_config_path string -// input string -// zot_url string -// registry string -// repository string -// user_input string -// scheme string -// api_version string -// image string -// harbor_password string -// harbor_username string -// env string -// use_unsecure bool -// remote_registry_url string -// group_name string -// state_artifact_name string -// state_fetch_period string -// } - -// func GetLogLevel() string { -// return AppConfig.log_level -// } - -// func GetOwnRegistry() bool { -// return AppConfig.own_registry -// } - -// func GetOwnRegistryAdr() string { -// return AppConfig.own_registry_adr -// } - -// func GetOwnRegistryPort() string { -// return AppConfig.own_registry_port -// } - -// func GetZotConfigPath() string { -// return AppConfig.zot_config_path -// } - -// func GetInput() string { -// return AppConfig.input -// } - -// func SetZotURL(url string) { -// AppConfig.zot_url = url -// } - -// func GetZotURL() string { -// return AppConfig.zot_url -// } - -// func SetRegistry(registry string) { -// AppConfig.registry = registry -// } - -// func GetRegistry() string { -// return AppConfig.registry -// } - -// func SetRepository(repository string) { -// AppConfig.repository = repository -// } - -// func GetRepository() string { -// return AppConfig.repository -// } - -// func SetUserInput(user_input string) { -// AppConfig.user_input = user_input -// } - -// func GetUserInput() string { -// return AppConfig.user_input -// } - -// func SetScheme(scheme string) { -// AppConfig.scheme = scheme -// } - -// func GetScheme() string { -// return AppConfig.scheme -// } - -// func SetAPIVersion(api_version string) { -// AppConfig.api_version = api_version -// } - -// func GetAPIVersion() string { -// return AppConfig.api_version -// } - -// func SetImage(image string) { -// AppConfig.image = image -// } - -// func GetImage() string { -// return AppConfig.image -// } - -// func UseUnsecure() bool { -// return AppConfig.use_unsecure -// } - -// func GetHarborPassword() string { -// return AppConfig.harbor_password -// } - -// func GetHarborUsername() string { -// return AppConfig.harbor_username -// } - -// func SetRemoteRegistryURL(url string) { -// AppConfig.remote_registry_url = url -// } - -// func GetRemoteRegistryURL() string { -// return AppConfig.remote_registry_url -// } - -// func GetGroupName() string { -// return AppConfig.group_name -// } - -// func GetStateArtifactName() string { -// return AppConfig.state_artifact_name -// } - -// func GetStateFetchPeriod() string { -// return AppConfig.state_fetch_period -// } - -// func LoadConfig() (*Config, error) { -// viper.SetConfigName("config") -// viper.SetConfigType("toml") -// viper.AddConfigPath(".") -// if err := viper.ReadInConfig(); err != nil { -// return nil, fmt.Errorf("error reading config file at path '%s': %w", viper.ConfigFileUsed(), err) -// } - -// // Load environment and start satellite -// if err := godotenv.Load(); err != nil { -// return &Config{}, fmt.Errorf("error loading .env file: %w", err) -// } -// var use_unsecure bool -// if os.Getenv("USE_UNSECURE") == "true" { -// use_unsecure = true -// } else { -// use_unsecure = false -// } - -// return &Config{ -// log_level: viper.GetString("log_level"), -// own_registry: viper.GetBool("bring_own_registry"), -// own_registry_adr: viper.GetString("own_registry_adr"), -// own_registry_port: viper.GetString("own_registry_port"), -// zot_config_path: viper.GetString("zotConfigPath"), -// input: viper.GetString("url_or_file"), -// harbor_password: os.Getenv("HARBOR_PASSWORD"), -// harbor_username: os.Getenv("HARBOR_USERNAME"), -// env: os.Getenv("ENV"), -// zot_url: os.Getenv("ZOT_URL"), -// use_unsecure: use_unsecure, -// group_name: os.Getenv("GROUP_NAME"), -// state_artifact_name: os.Getenv("STATE_ARTIFACT_NAME"), -// state_fetch_period: os.Getenv("STATE_FETCH_PERIOD"), -// }, nil -// } - +import ( + "encoding/json" + "fmt" + "os" +) + +var appConfig *Config + +const DefaultConfigPath string = "config.json" + +type Auth struct { + Name string `json:"name"` + Registry string `json:"registry"` + Secret string `json:"secret"` +} + +type Config struct { + Auth Auth `json:"auth"` + BringOwnRegistry bool `json:"bring_own_registry"` + GroundControlURL string `json:"ground_control_url"` + LogLevel string `json:"log_level"` + OwnRegistryAddress string `json:"own_registry_adr"` + OwnRegistryPort string `json:"own_registry_port"` + States []string `json:"states"` + URLOrFile string `json:"url_or_file"` + ZotConfigPath string `json:"zotconfigpath"` + UseUnsecure bool `json:"use_unsecure"` + ZotUrl string `json:"zot_url"` + StateFetchPeriod string `json:"state_fetch_period"` +} + +func GetLogLevel() string { + return appConfig.LogLevel +} + +func GetOwnRegistry() bool { + return appConfig.BringOwnRegistry +} + +func GetOwnRegistryAdr() string { + return appConfig.OwnRegistryAddress +} + +func GetOwnRegistryPort() string { + return appConfig.OwnRegistryPort +} + +func GetZotConfigPath() string { + return appConfig.ZotConfigPath +} + +func GetInput() string { + return appConfig.URLOrFile +} + +func SetZotURL(url string) { + appConfig.ZotUrl = url +} + +func GetZotURL() string { + return appConfig.ZotUrl +} + +func UseUnsecure() bool { + return appConfig.UseUnsecure +} + +func GetHarborPassword() string { + return appConfig.Auth.Secret +} + +func GetHarborUsername() string { + return appConfig.Auth.Name +} + +func SetRemoteRegistryURL(url string) { + appConfig.Auth.Registry = url +} + +func GetRemoteRegistryURL() string { + return appConfig.Auth.Registry +} + +func GetStateFetchPeriod() string { + return appConfig.StateFetchPeriod +} + +func GetStates() []string { + return appConfig.States +} + +func ParseConfigFromJson(jsonData string) (*Config, error) { + var config Config + err := json.Unmarshal([]byte(jsonData), &config) + if err != nil { + return nil, err + } + return &config, nil +} + +func ReadConfigData(configPath string) ([]byte, error) { + + fileInfo, err := os.Stat(configPath) + if err != nil { + return nil, err + } + if fileInfo.IsDir() { + return nil, os.ErrNotExist + } + data, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + return data, nil +} + +func LoadConfig() (*Config, error) { + configData, err := ReadConfigData(DefaultConfigPath) + if err != nil { + fmt.Printf("Error reading config file: %v\n", err) + os.Exit(1) + } + config, err := ParseConfigFromJson(string(configData)) + if err != nil { + fmt.Printf("Error parsing config file: %v\n", err) + os.Exit(1) + } + return config, nil +} + +func InitConfig() error { + var err error + appConfig, err = LoadConfig() + if err != nil { + return err + } + return nil +} diff --git a/internal/config/new_config.go b/internal/config/new_config.go deleted file mode 100644 index 8dc3463..0000000 --- a/internal/config/new_config.go +++ /dev/null @@ -1,136 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" -) - -var appConfig *Config - -const DefaultConfigPath string = "config.json" - -type Auth struct { - Name string `json:"name"` - Registry string `json:"registry"` - Secret string `json:"secret"` -} - -type Config struct { - Auths Auth `json:"auth"` - BringOwnRegistry bool `json:"bring_own_registry"` - GroundControlURL string `json:"ground_control_url"` - LogLevel string `json:"log_level"` - OwnRegistryAddress string `json:"own_registry_adr"` - OwnRegistryPort string `json:"own_registry_port"` - States []string `json:"states"` - URLOrFile string `json:"url_or_file"` - ZotConfigPath string `json:"zotconfigpath"` - UseUnsecure bool `json:"use_unsecure"` - ZotUrl string `json:"zot_url"` - StateFetchPeriod string `json:"state_fetch_period"` -} - -func GetLogLevel() string { - return appConfig.LogLevel -} - -func GetOwnRegistry() bool { - return appConfig.BringOwnRegistry -} - -func GetOwnRegistryAdr() string { - return appConfig.OwnRegistryAddress -} - -func GetOwnRegistryPort() string { - return appConfig.OwnRegistryPort -} - -func GetZotConfigPath() string { - return appConfig.ZotConfigPath -} - -func GetInput() string { - return appConfig.URLOrFile -} - -func SetZotURL(url string) { - appConfig.ZotUrl = url -} - -func GetZotURL() string { - return appConfig.ZotUrl -} - -func UseUnsecure() bool { - return appConfig.UseUnsecure -} - -func GetHarborPassword() string { - return appConfig.Auths.Secret -} - -func GetHarborUsername() string { - return appConfig.Auths.Name -} - -func SetRemoteRegistryURL(url string) { - appConfig.Auths.Registry = url -} - -func GetRemoteRegistryURL() string { - return appConfig.Auths.Registry -} - -func GetStateFetchPeriod() string { - return appConfig.StateFetchPeriod -} - -func ParseConfigFromJson(jsonData string) (*Config, error) { - var config Config - err := json.Unmarshal([]byte(jsonData), &config) - if err != nil { - return nil, err - } - return &config, nil -} - -func ReadConfigData(configPath string) ([]byte, error) { - - fileInfo, err := os.Stat(configPath) - if err != nil { - return nil, err - } - if fileInfo.IsDir() { - return nil, os.ErrNotExist - } - data, err := os.ReadFile(configPath) - if err != nil { - return nil, err - } - return data, nil -} - -func LoadConfig() (*Config, error) { - configData, err := ReadConfigData(DefaultConfigPath) - if err != nil { - fmt.Printf("Error reading config file: %v\n", err) - os.Exit(1) - } - config, err := ParseConfigFromJson(string(configData)) - if err != nil { - fmt.Printf("Error parsing config file: %v\n", err) - os.Exit(1) - } - return config, nil -} - -func InitConfig() error { - var err error - appConfig, err = LoadConfig() - if err != nil { - return err - } - return nil -} diff --git a/internal/satellite/satellite.go b/internal/satellite/satellite.go index cc1f415..1d0d038 100644 --- a/internal/satellite/satellite.go +++ b/internal/satellite/satellite.go @@ -12,15 +12,13 @@ import ( ) type Satellite struct { - stateReader state.StateReader - stateArtifactFetcher state.StateFetcher - schedulerKey scheduler.SchedulerKey + stateReader state.StateReader + schedulerKey scheduler.SchedulerKey } -func NewSatellite(ctx context.Context, stateArtifactFetcher state.StateFetcher, schedulerKey scheduler.SchedulerKey) *Satellite { +func NewSatellite(ctx context.Context, schedulerKey scheduler.SchedulerKey) *Satellite { return &Satellite{ - stateArtifactFetcher: stateArtifactFetcher, - schedulerKey: schedulerKey, + schedulerKey: schedulerKey, } } @@ -45,7 +43,8 @@ func (s *Satellite) Run(ctx context.Context) error { // Create a simple notifier and add it to the process notifier := notifier.NewSimpleNotifier(ctx) // Creating a process to fetch and replicate the state - fetchAndReplicateStateProcess := state.NewFetchAndReplicateStateProcess(scheduler.NextID(), cronExpr, s.stateArtifactFetcher, notifier, userName, password, zotURL, sourceRegistry, useUnsecure) + states := config.GetStates() + fetchAndReplicateStateProcess := state.NewFetchAndReplicateStateProcess(scheduler.NextID(), cronExpr, notifier, userName, password, zotURL, sourceRegistry, useUnsecure, states) // Add the process to the scheduler scheduler.Schedule(fetchAndReplicateStateProcess) diff --git a/internal/state/state_process.go b/internal/state/state_process.go index 14bcc42..a130d09 100644 --- a/internal/state/state_process.go +++ b/internal/state/state_process.go @@ -6,6 +6,7 @@ import ( "fmt" "sync" + "container-registry.com/harbor-satellite/internal/config" "container-registry.com/harbor-satellite/internal/notifier" "container-registry.com/harbor-satellite/internal/utils" "container-registry.com/harbor-satellite/logger" @@ -25,26 +26,38 @@ type FetchAndReplicateAuthConfig struct { } type FetchAndReplicateStateProcess struct { - id uint64 - name string - stateArtifactFetcher StateFetcher - cronExpr string - isRunning bool - stateReader StateReader - notifier notifier.Notifier - mu *sync.Mutex - authConfig FetchAndReplicateAuthConfig -} - -func NewFetchAndReplicateStateProcess(id uint64, cronExpr string, stateFetcher StateFetcher, notifier notifier.Notifier, username, password, remoteRegistryURL, sourceRegistryURL string, useUnsecure bool) *FetchAndReplicateStateProcess { + id uint64 + name string + cronExpr string + isRunning bool + stateMap []StateMap + notifier notifier.Notifier + mu *sync.Mutex + authConfig FetchAndReplicateAuthConfig +} + +type StateMap struct { + url string + State StateReader +} + +func NewStateMap(url []string) []StateMap { + var stateMap []StateMap + for _, u := range url { + stateMap = append(stateMap, StateMap{url: u, State: nil}) + } + return stateMap +} + +func NewFetchAndReplicateStateProcess(id uint64, cronExpr string, notifier notifier.Notifier, username, password, remoteRegistryURL, sourceRegistryURL string, useUnsecure bool, states []string) *FetchAndReplicateStateProcess { return &FetchAndReplicateStateProcess{ - id: id, - name: FetchAndReplicateStateProcessName, - cronExpr: cronExpr, - isRunning: false, - stateArtifactFetcher: stateFetcher, - notifier: notifier, - mu: &sync.Mutex{}, + id: id, + name: FetchAndReplicateStateProcessName, + cronExpr: cronExpr, + isRunning: false, + notifier: notifier, + mu: &sync.Mutex{}, + stateMap: NewStateMap(states), authConfig: FetchAndReplicateAuthConfig{ Username: username, Password: password, @@ -62,32 +75,49 @@ func (f *FetchAndReplicateStateProcess) Execute(ctx context.Context) error { return fmt.Errorf("process %s already running", f.GetName()) } defer f.stop() - newStateFetched, err := f.FetchAndProcessState(log) - if err != nil { - return err - } - log.Info().Msg("State fetched successfully") - deleteEntity, replicateEntity, newState := f.GetChanges(newStateFetched, log) - f.LogChanges(deleteEntity, replicateEntity, log) - if err := f.notifier.Notify(); err != nil { - log.Error().Err(err).Msg("Error sending notification") - } - replicator := NewBasicReplicator(f.authConfig.Username, f.authConfig.Password, f.authConfig.remoteRegistryURL, f.authConfig.sourceRegistry, f.authConfig.useUnsecure) - // Delete the entities from the remote registry - if err := replicator.DeleteReplicationEntity(ctx, deleteEntity); err != nil { - log.Error().Err(err).Msg("Error deleting entities") - return err - } - // Replicate the entities to the remote registry - if err := replicator.Replicate(ctx, replicateEntity); err != nil { - log.Error().Err(err).Msg("Error replicating state") - return err + + for _, state := range f.stateMap { + log.Info().Msgf("Processing state for %s", state.url) + stateFetcher, err := processInput(state.url, f.authConfig.Username, f.authConfig.Password, log) + if err != nil { + log.Error().Err(err).Msg("Error processing input") + return err + } + newStateFetched, err := f.FetchAndProcessState(stateFetcher, log) + if err != nil { + log.Error().Err(err).Msg("Error fetching state") + return err + } + log.Info().Msgf("State fetched successfully for %s", state.url) + deleteEntity, replicateEntity, newState := f.GetChanges(newStateFetched, log, state.State) + f.LogChanges(deleteEntity, replicateEntity, log) + if err := f.notifier.Notify(); err != nil { + log.Error().Err(err).Msg("Error sending notification") + } + log.Info().Msg("Replicating state") + log.Info().Msg("Replicator config") + log.Info().Msgf("Username: %s", f.authConfig.Username) + log.Info().Msgf("Password: %s", f.authConfig.Password) + log.Info().Msgf("Remote registry URL: %s", f.authConfig.remoteRegistryURL) + log.Info().Msgf("Source registry: %s", f.authConfig.sourceRegistry) + + replicator := NewBasicReplicator(f.authConfig.Username, f.authConfig.Password, f.authConfig.remoteRegistryURL, f.authConfig.sourceRegistry, f.authConfig.useUnsecure) + // Delete the entities from the remote registry + if err := replicator.DeleteReplicationEntity(ctx, deleteEntity); err != nil { + log.Error().Err(err).Msg("Error deleting entities") + return err + } + // Replicate the entities to the remote registry + if err := replicator.Replicate(ctx, replicateEntity); err != nil { + log.Error().Err(err).Msg("Error replicating state") + return err + } + state.State = newState } - f.stateReader = newState return nil } -func (f *FetchAndReplicateStateProcess) GetChanges(newState StateReader, log *zerolog.Logger) ([]ArtifactReader, []ArtifactReader, StateReader) { +func (f *FetchAndReplicateStateProcess) GetChanges(newState StateReader, log *zerolog.Logger, oldState StateReader) ([]ArtifactReader, []ArtifactReader, StateReader) { log.Info().Msg("Getting changes") // Remove artifacts with null tags from the new state newState = f.RemoveNullTagArtifacts(newState) @@ -95,14 +125,14 @@ func (f *FetchAndReplicateStateProcess) GetChanges(newState StateReader, log *ze var entityToDelete []ArtifactReader var entityToReplicate []ArtifactReader - if f.stateReader == nil { + if oldState == nil { log.Warn().Msg("Old state is nil") return entityToDelete, newState.GetArtifacts(), newState } - + // Create maps for quick lookups oldArtifactsMap := make(map[string]ArtifactReader) - for _, oldArtifact := range f.stateReader.GetArtifacts() { + for _, oldArtifact := range oldState.GetArtifacts() { tag := oldArtifact.GetTags()[0] oldArtifactsMap[oldArtifact.GetName()+"|"+tag] = oldArtifact } @@ -199,9 +229,9 @@ func ProcessState(state *StateReader) (*StateReader, error) { return state, nil } -func (f *FetchAndReplicateStateProcess) FetchAndProcessState(log *zerolog.Logger) (StateReader, error) { +func (f *FetchAndReplicateStateProcess) FetchAndProcessState(fetcher StateFetcher, log *zerolog.Logger) (StateReader, error) { state := NewState() - err := f.stateArtifactFetcher.FetchStateArtifact(&state) + err := fetcher.FetchStateArtifact(&state) if err != nil { log.Error().Err(err).Msg("Error fetching state artifact") return nil, err @@ -214,3 +244,44 @@ func (f *FetchAndReplicateStateProcess) LogChanges(deleteEntity, replicateEntity log.Warn().Msgf("Total artifacts to delete: %d", len(deleteEntity)) log.Warn().Msgf("Total artifacts to replicate: %d", len(replicateEntity)) } + +func processInput(input, username, password string, log *zerolog.Logger) (StateFetcher, error) { + + if utils.IsValidURL(input) { + return processURLInput(utils.FormatRegistryURL(input), username, password, log) + } + + log.Info().Msg("Input is not a valid URL, checking if it is a file path") + if err := validateFilePath(input, log); err != nil { + return nil, err + } + + return processFileInput(input, username, password, log) +} + +func validateFilePath(path string, log *zerolog.Logger) error { + if utils.HasInvalidPathChars(path) { + log.Error().Msg("Path contains invalid characters") + return fmt.Errorf("invalid file path: %s", path) + } + if err := utils.GetAbsFilePath(path); err != nil { + log.Error().Err(err).Msg("No file found") + return fmt.Errorf("no file found: %s", path) + } + return nil +} + +func processURLInput(input, username, password string, log *zerolog.Logger) (StateFetcher, error) { + log.Info().Msg("Input is a valid URL") + config.SetRemoteRegistryURL(input) + + stateArtifactFetcher := NewURLStateFetcher(input, username, password) + + return stateArtifactFetcher, nil +} + +func processFileInput(input, username, password string, log *zerolog.Logger) (StateFetcher, error) { + log.Info().Msg("Input is a valid file path") + stateArtifactFetcher := NewFileStateFetcher(input, username, password) + return stateArtifactFetcher, nil +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 92dcefa..0b2ce7d 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -44,6 +44,8 @@ func HandleOwnRegistry() error { // LaunchDefaultZotRegistry launches the default Zot registry using the Zot config path func LaunchDefaultZotRegistry() error { + defaultZotURL := fmt.Sprintf("%s:%s", "127.0.0.1", "8585") + config.SetZotURL(defaultZotURL) launch, err := registry.LaunchRegistry(config.GetZotConfigPath()) if !launch { return fmt.Errorf("error launching registry: %w", err) diff --git a/main.go b/main.go index 11467f2..2bff96c 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "container-registry.com/harbor-satellite/internal/satellite" "container-registry.com/harbor-satellite/internal/scheduler" "container-registry.com/harbor-satellite/internal/server" - "container-registry.com/harbor-satellite/internal/state" "container-registry.com/harbor-satellite/internal/utils" "container-registry.com/harbor-satellite/logger" "golang.org/x/sync/errgroup" @@ -55,13 +54,8 @@ func run() error { log.Error().Err(err).Msg("Error starting scheduler") return err } - // Process Input (file or URL) - stateArtifactFetcher, err := processInput(ctx, log) - if err != nil || stateArtifactFetcher == nil { - return fmt.Errorf("error processing input: %w", err) - } - satelliteService := satellite.NewSatellite(ctx, stateArtifactFetcher, scheduler.GetSchedulerKey()) + satelliteService := satellite.NewSatellite(ctx, scheduler.GetSchedulerKey()) g.Go(func() error { return satelliteService.Run(ctx) @@ -117,50 +111,3 @@ func handleRegistrySetup(g *errgroup.Group, log *zerolog.Logger, cancel context. } return nil } - -func processInput(ctx context.Context, log *zerolog.Logger) (state.StateFetcher, error) { - input := config.GetInput() - - if utils.IsValidURL(input) { - return processURLInput(input, log) - } - - log.Info().Msg("Input is not a valid URL, checking if it is a file path") - if err := validateFilePath(input, log); err != nil { - return nil, err - } - - return processFileInput(input, log) -} - -func processURLInput(input string, log *zerolog.Logger) (state.StateFetcher, error) { - log.Info().Msg("Input is a valid URL") - config.SetRemoteRegistryURL(input) - - username := config.GetHarborUsername() - password := config.GetHarborPassword() - - stateArtifactFetcher := state.NewURLStateFetcher(input, username, password) - - return stateArtifactFetcher, nil -} - -func processFileInput(input string, log *zerolog.Logger) (state.StateFetcher, error) { - log.Info().Msg("Input is a valid file path") - username := config.GetHarborUsername() - password := config.GetHarborPassword() - stateArtifactFetcher := state.NewFileStateFetcher(input, username, password) - return stateArtifactFetcher, nil -} - -func validateFilePath(path string, log *zerolog.Logger) error { - if utils.HasInvalidPathChars(path) { - log.Error().Msg("Path contains invalid characters") - return fmt.Errorf("invalid file path: %s", path) - } - if err := utils.GetAbsFilePath(path); err != nil { - log.Error().Err(err).Msg("No file found") - return fmt.Errorf("no file found: %s", path) - } - return nil -}