From af8f242df70851c3c0761737412f0ea596e9ae52 Mon Sep 17 00:00:00 2001 From: nathan-nicholson Date: Thu, 19 Dec 2024 19:49:18 -0600 Subject: [PATCH] chore: begin logging refactor * add logger interface * add error logging * add utility for making k1 directory that returns an error Signed-off-by: nathan-nicholson --- cmd/aws/command.go | 36 ++++++------------ cmd/aws/create.go | 50 +++++++++++++++++-------- cmd/aws/types.go | 12 ++++++ cmd/root.go | 6 +-- internal/common/logger.go | 8 ++++ internal/utilities/flags.go | 51 +++++++++++++------------ internal/utilities/utilities.go | 29 ++++++++++++-- internal/utilities/utilities_test.go | 56 ++++++++++++++++++++++++++++ main.go | 13 +++++-- 9 files changed, 185 insertions(+), 76 deletions(-) create mode 100644 cmd/aws/types.go create mode 100644 internal/common/logger.go create mode 100644 internal/utilities/utilities_test.go diff --git a/cmd/aws/command.go b/cmd/aws/command.go index c4d54fdb..07314fca 100644 --- a/cmd/aws/command.go +++ b/cmd/aws/command.go @@ -12,7 +12,6 @@ import ( "github.com/konstructio/kubefirst-api/pkg/constants" "github.com/konstructio/kubefirst/internal/common" - "github.com/konstructio/kubefirst/internal/logger" "github.com/spf13/cobra" ) @@ -45,25 +44,8 @@ var ( supportedGitProtocolOverride = []string{"https", "ssh"} ) -type Printer interface { - AddWriter(w io.Writer) - Print(s string) error -} - -type awsCommand struct { - logger logger.Logger - printer Printer -} - -func NewAwsCommand(logger logger.Logger, printer Printer) *awsCommand { - return &awsCommand{ - logger, - printer, - } -} - -func NewCommand() *cobra.Command { - awsCmd := &cobra.Command{ +func NewCommand(logger common.Logger, writer io.Writer) *cobra.Command { + cmd := &cobra.Command{ Use: "aws", Short: "kubefirst aws installation", Long: "kubefirst aws", @@ -73,19 +55,23 @@ func NewCommand() *cobra.Command { }, } + service := AwsService{ + logger, + writer, + } + // wire up new commands - awsCmd.AddCommand(Create(), Destroy(), Quota(), RootCredentials()) + cmd.AddCommand(Create(service), Destroy(), Quota(), RootCredentials()) - return awsCmd + return cmd } -func Create() *cobra.Command { +func Create(service AwsService) *cobra.Command { createCmd := &cobra.Command{ Use: "create", Short: "create the kubefirst platform running in aws", TraverseChildren: true, - RunE: createAws, - // PreRun: common.CheckDocker, + RunE: service.createAws, } awsDefaults := constants.GetCloudDefaults().Aws diff --git a/cmd/aws/create.go b/cmd/aws/create.go index 80c19481..41c38090 100644 --- a/cmd/aws/create.go +++ b/cmd/aws/create.go @@ -27,47 +27,64 @@ import ( "github.com/spf13/viper" ) -func createAws(cmd *cobra.Command, _ []string) error { +func (s *AwsService) createAws(cmd *cobra.Command, _ []string) error { + fmt.Fprintln(s.writer, "Starting to create AWS cluster") + cliFlags, err := utilities.GetFlags(cmd, "aws") if err != nil { - progress.Error(err.Error()) - return nil + s.logger.Error("failed to get flags", "error", err) + return fmt.Errorf("failed to get flags: %w", err) } - progress.DisplayLogHints(40) + //TODO - Add progress steps + //progress.DisplayLogHints(40) isValid, catalogApps, err := catalog.ValidateCatalogApps(cliFlags.InstallCatalogApps) if !isValid { + s.logger.Error("invalid catalog apps", "error", err) return fmt.Errorf("invalid catalog apps: %w", err) } err = ValidateProvidedFlags(cliFlags.GitProvider) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to validate provided flags", "error", err) return fmt.Errorf("failed to validate provided flags: %w", err) } - utilities.CreateK1ClusterDirectory(cliFlags.ClusterName) + // Create k1 cluster directory + homePath, err := os.UserHomeDir() + + if err != nil { + s.logger.Error("failed to get user home directory", "error", err) + return fmt.Errorf("failed to get user home directory: %w", err) + } + + err = utilities.CreateK1ClusterDirectoryE(homePath, cliFlags.ClusterName) + + if err != nil { + s.logger.Error("failed to create k1 cluster directory", "error", err) + return fmt.Errorf("failed to create k1 cluster directory: %w", err) + } // If cluster setup is complete, return clusterSetupComplete := viper.GetBool("kubefirst-checks.cluster-install-complete") if clusterSetupComplete { - err = fmt.Errorf("this cluster install process has already completed successfully") - progress.Error(err.Error()) + s.logger.Info("cluster install process has already completed successfully") + fmt.Fprintln(s.writer, "Cluster install process has already completed successfully") return nil } // Validate aws region config, err := awsinternal.NewAwsV2(cloudRegionFlag) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to validate AWS region", "error", err) return fmt.Errorf("failed to validate AWS region: %w", err) } awsClient := &awsinternal.Configuration{Config: config} creds, err := awsClient.Config.Credentials.Retrieve(aws.BackgroundContext()) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to retrieve AWS credentials", "error", err) return fmt.Errorf("failed to retrieve AWS credentials: %w", err) } @@ -75,18 +92,20 @@ func createAws(cmd *cobra.Command, _ []string) error { viper.Set("kubefirst.state-store-creds.secret-access-key-id", creds.SecretAccessKey) viper.Set("kubefirst.state-store-creds.token", creds.SessionToken) if err := viper.WriteConfig(); err != nil { + s.logger.Error("failed to write config", "error", err) + return fmt.Errorf("failed to write config: %w", err) } _, err = awsClient.CheckAvailabilityZones(cliFlags.CloudRegion) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to check availability zones", "error", err) return fmt.Errorf("failed to check availability zones: %w", err) } gitAuth, err := gitShim.ValidateGitCredentials(cliFlags.GitProvider, cliFlags.GithubOrg, cliFlags.GitlabGroup) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to validate Git credentials", "error", err) return fmt.Errorf("failed to validate Git credentials: %w", err) } @@ -105,13 +124,14 @@ func createAws(cmd *cobra.Command, _ []string) error { err = gitShim.InitializeGitProvider(&initGitParameters) if err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to initialize Git provider", "error", err) return fmt.Errorf("failed to initialize Git provider: %w", err) } } viper.Set(fmt.Sprintf("kubefirst-checks.%s-credentials", cliFlags.GitProvider), true) if err := viper.WriteConfig(); err != nil { + s.logger.Error("failed to write config", "error", err) return fmt.Errorf("failed to write config: %w", err) } @@ -124,12 +144,12 @@ func createAws(cmd *cobra.Command, _ []string) error { err = pkg.IsAppAvailable(fmt.Sprintf("%s/api/proxyHealth", cluster.GetConsoleIngressURL()), "kubefirst api") if err != nil { - progress.Error("unable to start kubefirst api") + s.logger.Error("failed to check kubefirst API availability", "error", err) return fmt.Errorf("failed to check kubefirst API availability: %w", err) } if err := provision.CreateMgmtCluster(gitAuth, cliFlags, catalogApps); err != nil { - progress.Error(err.Error()) + s.logger.Error("failed to create management cluster", "error", err) return fmt.Errorf("failed to create management cluster: %w", err) } diff --git a/cmd/aws/types.go b/cmd/aws/types.go new file mode 100644 index 00000000..677ab8b2 --- /dev/null +++ b/cmd/aws/types.go @@ -0,0 +1,12 @@ +package aws + +import ( + "io" + + "github.com/konstructio/kubefirst/internal/common" +) + +type AwsService struct { + logger common.Logger + writer io.Writer +} diff --git a/cmd/root.go b/cmd/root.go index 1fc1daff..d06173f8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,7 @@ import ( // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { +func Execute(logger common.Logger) { // rootCmd represents the base command when called without any subcommands rootCmd := &cobra.Command{ Use: "kubefirst", @@ -48,8 +48,7 @@ func Execute() { rootCmd.SetOut(os.Stdout) rootCmd.AddCommand( - betaCmd, - aws.NewCommand(), + aws.NewCommand(logger, os.Stderr), civo.NewCommand(), digitalocean.NewCommand(), k3d.NewCommand(), @@ -57,6 +56,7 @@ func Execute() { LaunchCommand(), LetsEncryptCommand(), TerraformCommand(), + betaCmd, infoCmd, versionCmd, resetCmd, diff --git a/internal/common/logger.go b/internal/common/logger.go new file mode 100644 index 00000000..c1bd2aac --- /dev/null +++ b/internal/common/logger.go @@ -0,0 +1,8 @@ +package common + +type Logger interface { + Debug(msg string, args ...any) + Info(msg string, args ...any) + Warn(msg string, args ...any) + Error(msg string, args ...any) +} diff --git a/internal/utilities/flags.go b/internal/utilities/flags.go index c6ac1dd5..37b23f00 100644 --- a/internal/utilities/flags.go +++ b/internal/utilities/flags.go @@ -10,7 +10,6 @@ import ( "fmt" "strings" - "github.com/konstructio/kubefirst/internal/progress" "github.com/konstructio/kubefirst/internal/types" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -21,112 +20,112 @@ func GetFlags(cmd *cobra.Command, cloudProvider string) (types.CliFlags, error) alertsEmailFlag, err := cmd.Flags().GetString("alerts-email") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get alerts-email flag: %w", err) } cloudRegionFlag, err := cmd.Flags().GetString("cloud-region") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get cloud-region flag: %w", err) } clusterNameFlag, err := cmd.Flags().GetString("cluster-name") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get cluster-name flag: %w", err) } dnsProviderFlag, err := cmd.Flags().GetString("dns-provider") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get dns-provider flag: %w", err) } subdomainFlag, err := cmd.Flags().GetString("subdomain") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get subdomain flag: %w", err) } domainNameFlag, err := cmd.Flags().GetString("domain-name") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get domain-name flag: %w", err) } githubOrgFlag, err := cmd.Flags().GetString("github-org") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get github-org flag: %w", err) } githubOrgFlag = strings.ToLower(githubOrgFlag) gitlabGroupFlag, err := cmd.Flags().GetString("gitlab-group") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get gitlab-group flag: %w", err) } gitlabGroupFlag = strings.ToLower(gitlabGroupFlag) gitProviderFlag, err := cmd.Flags().GetString("git-provider") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get git-provider flag: %w", err) } gitProtocolFlag, err := cmd.Flags().GetString("git-protocol") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get git-protocol flag: %w", err) } gitopsTemplateURLFlag, err := cmd.Flags().GetString("gitops-template-url") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get gitops-template-url flag: %w", err) } gitopsTemplateBranchFlag, err := cmd.Flags().GetString("gitops-template-branch") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get gitops-template-branch flag: %w", err) } useTelemetryFlag, err := cmd.Flags().GetBool("use-telemetry") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get use-telemetry flag: %w", err) } nodeTypeFlag, err := cmd.Flags().GetString("node-type") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get node-type flag: %w", err) } installCatalogAppsFlag, err := cmd.Flags().GetString("install-catalog-apps") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get install-catalog-apps flag: %w", err) } nodeCountFlag, err := cmd.Flags().GetString("node-count") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get node-count flag: %w", err) } installKubefirstProFlag, err := cmd.Flags().GetBool("install-kubefirst-pro") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get install-kubefirst-pro flag: %w", err) } if cloudProvider == "aws" { ecrFlag, err := cmd.Flags().GetBool("ecr") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get ecr flag: %w", err) } @@ -136,7 +135,7 @@ func GetFlags(cmd *cobra.Command, cloudProvider string) (types.CliFlags, error) if cloudProvider == "azure" { dnsAzureResourceGroup, err := cmd.Flags().GetString("dns-azure-resource-group") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get dns-azure-resource-group flag: %w", err) } cliFlags.DNSAzureRG = dnsAzureResourceGroup @@ -145,7 +144,7 @@ func GetFlags(cmd *cobra.Command, cloudProvider string) (types.CliFlags, error) if cloudProvider == "google" { googleProject, err := cmd.Flags().GetString("google-project") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get google-project flag: %w", err) } @@ -155,35 +154,35 @@ func GetFlags(cmd *cobra.Command, cloudProvider string) (types.CliFlags, error) if cloudProvider == "k3s" { k3sServersPrivateIps, err := cmd.Flags().GetStringSlice("servers-private-ips") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get servers-private-ips flag: %w", err) } cliFlags.K3sServersPrivateIPs = k3sServersPrivateIps k3sServersPublicIps, err := cmd.Flags().GetStringSlice("servers-public-ips") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get servers-public-ips flag: %w", err) } cliFlags.K3sServersPublicIPs = k3sServersPublicIps k3sSSHUserFlag, err := cmd.Flags().GetString("ssh-user") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get ssh-user flag: %w", err) } cliFlags.K3sSSHUser = k3sSSHUserFlag k3sSSHPrivateKeyFlag, err := cmd.Flags().GetString("ssh-privatekey") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get ssh-privatekey flag: %w", err) } cliFlags.K3sSSHPrivateKey = k3sSSHPrivateKeyFlag K3sServersArgsFlags, err := cmd.Flags().GetStringSlice("servers-args") if err != nil { - progress.Error(err.Error()) + return cliFlags, fmt.Errorf("failed to get servers-args flag: %w", err) } cliFlags.K3sServersArgs = K3sServersArgsFlags diff --git a/internal/utilities/utilities.go b/internal/utilities/utilities.go index 77b8c513..b2d13322 100644 --- a/internal/utilities/utilities.go +++ b/internal/utilities/utilities.go @@ -35,13 +35,34 @@ func CreateK1ClusterDirectory(clusterName string) { return } + err = CreateK1ClusterDirectoryE(homePath, clusterName) + + if err != nil { + log.Info().Msg(err.Error()) + } + + log.Info().Msg("K1 cluster directory created") + +} + +func CreateK1ClusterDirectoryE(homePath, clusterName string) error { + k1Dir := fmt.Sprintf("%s/.k1/%s", homePath, clusterName) - if _, err := os.Stat(k1Dir); os.IsNotExist(err) { - err := os.MkdirAll(k1Dir, os.ModePerm) - if err != nil { - log.Info().Msgf("%q directory already exists, continuing", k1Dir) + + _, err := os.Stat(k1Dir) + + if err != nil { + if os.IsNotExist(err) { + err := os.MkdirAll(k1Dir, os.ModePerm) + if err != nil { + return fmt.Errorf("error creating directory: %w", err) + } + } else { + return fmt.Errorf("error checking directory: %w", err) } } + + return nil } func CreateClusterRecordFromRaw( diff --git a/internal/utilities/utilities_test.go b/internal/utilities/utilities_test.go new file mode 100644 index 00000000..f51e2389 --- /dev/null +++ b/internal/utilities/utilities_test.go @@ -0,0 +1,56 @@ +package utilities + +import ( + "fmt" + "os" + "testing" +) + +func TestCreateK1ClusterDirectoryE(t *testing.T) { + tests := []struct { + name string + homePath string + clusterName string + wantOk bool + wantErr bool + }{ + { + name: "successfully creates new directory", + homePath: t.TempDir(), + clusterName: "test-cluster", + wantOk: true, + wantErr: false, + }, + { + name: "empty cluster name", + homePath: t.TempDir(), + clusterName: "", + wantOk: true, + wantErr: false, + }, + { + name: "invalid home path", + homePath: "/nonexistent/path", + clusterName: "test-cluster", + wantOk: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := CreateK1ClusterDirectoryE(tt.homePath, tt.clusterName) + if (err != nil) != tt.wantErr { + t.Errorf("CreateK1ClusterDirectoryE() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + expectedPath := fmt.Sprintf("%s/.k1/%s", tt.homePath, tt.clusterName) + if _, err := os.Stat(expectedPath); os.IsNotExist(err) { + t.Errorf("Directory was not created at %s", expectedPath) + } + } + }) + } +} diff --git a/main.go b/main.go index e030707e..04be31cc 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ package main import ( "fmt" stdLog "log" + "log/slog" "os" "time" @@ -117,11 +118,17 @@ func main() { }(logFileObj) // setup default logging - // this Go standard log is active to keep compatibility with current code base stdLog.SetOutput(logFileObj) stdLog.SetPrefix("LOG: ") stdLog.SetFlags(stdLog.Ldate) + // Configure slog to write to the logfile + slogHandler := slog.NewTextHandler(logFileObj, &slog.HandlerOptions{ + Level: slog.LevelInfo, + }) + + logger := slog.New(slogHandler) + log.Logger = zeroLog.New(logFileObj).With().Timestamp().Logger() viper.Set("k1-paths.logs-dir", logsFolder) @@ -137,11 +144,11 @@ func main() { progress.InitializeProgressTerminal() go func() { - cmd.Execute() + cmd.Execute(logger) }() progress.Progress.Run() } else { - cmd.Execute() + cmd.Execute(logger) } }