From ea878a5d5768b7e2a47a8605250ec8b080044e8d Mon Sep 17 00:00:00 2001 From: Piotr Skamruk Date: Thu, 21 May 2026 17:07:39 +0200 Subject: [PATCH] feat: Support for config file reading Add support for configuration files as described in #49 Signed-off-by: Piotr Skamruk --- go.mod | 2 +- main.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 04128cd..12a543a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cloudoperators/greenhouse v0.8.0 github.com/onsi/gomega v1.38.3 github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 k8s.io/apimachinery v0.35.0 k8s.io/client-go v0.35.0 @@ -65,7 +66,6 @@ require ( github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/main.go b/main.go index b145f3d..dac9d94 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,10 @@ import ( "fmt" "os" "os/signal" + "path/filepath" "syscall" + "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/cloudoperators/cloudctl/cmd" @@ -18,15 +20,68 @@ import ( ) func main() { + var ( + config string + ) + + // It is assumed there that $HOME is always set to correct value + home := os.Getenv("HOME") + // Optionally read environment variables, config files, etc. viper.SetEnvPrefix("CLOUDCTL") viper.AutomaticEnv() + pflag.StringVar(&config, "config", "", "Path to configuration file") + pflag.Parse() + + viper.SetConfigType("yaml") + + if len(config) > 0 { + // First we are trying config provided as command line parameter. + viper.SetConfigFile(config) + } else { + // Then we are searching for ".cloudctl.yaml" in current or home directory + viper.AddConfigPath(".") + viper.AddConfigPath(home) + viper.SetConfigName(".cloudctl") + } + + viper.BindPFlags(pflag.CommandLine) + err := viper.ReadInConfig() + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // If reading config in above described locations failed, we are looking for configuration + // in these locations: + // ./cloudctl.yaml + // $HOME/cloudclt.yaml + // $XDG_CONFIG_HOME/cloudctl/cloudctl.yaml + // $XDG_CONFIG_HOME/cloudctl.yaml + // $HOME/.config/cloudctl/cloudctl.yaml + // $HOME/.config/cloudctl.yaml + viper.SetConfigName("cloudctl") + if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); len(xdgConfig) > 0 { + viper.AddConfigPath(filepath.Join(xdgConfig, "cloudctl")) + viper.AddConfigPath(xdgConfig) + } else { + viper.AddConfigPath(filepath.Join(home, ".config", "cloudctl")) + viper.AddConfigPath(filepath.Join(home, ".config")) + } + err = viper.ReadInConfig() + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + err = nil + } + } + + // Show error message and exit if config file was found but failed to read. + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + // Graceful cancellation on SIGINT/SIGTERM ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() - if err := cmd.Execute(ctx); err != nil { + if err = cmd.Execute(ctx); err != nil { // Print errors to stderr fmt.Fprintln(os.Stderr, err) os.Exit(1)