From 281e18bc49b93ba3b449e0354bf1bf0e993aec53 Mon Sep 17 00:00:00 2001 From: Bohdan Dymchenko <142878290+bohdand-weka@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:42:21 +0200 Subject: [PATCH] feat: add cleanup script for local path provisioner (#28) --- internal/cli/cli.go | 2 - internal/cli/install/chart/cli.go | 42 ------ internal/cli/install/configure/configure.go | 48 ------- internal/cli/install/install.go | 41 ------ internal/cli/install/k3s/k3s.go | 81 ----------- internal/cli/local/cleanup/cleanup.go | 22 +++ internal/cli/local/cleanup/local-storage.go | 31 ++++ internal/cli/local/local.go | 12 +- internal/cli/local/setup/run.go | 135 ++++++++++++++++++ internal/cli/local/setup/setup.go | 76 ++++++++++ .../chart/install.go => local/upgrade/run.go} | 57 +++++--- internal/cli/local/upgrade/upgrade.go | 69 +++++++++ internal/install/bundle/file.go | 13 +- internal/install/chart/install.go | 3 +- internal/install/k3s/images.go | 8 +- internal/install/k3s/install.go | 8 -- internal/install/k3s/upgrade.go | 10 +- internal/install/web/api/k3s.go | 2 +- internal/local/cleanup/local-storage.go | 118 +++++++++++++++ 19 files changed, 505 insertions(+), 273 deletions(-) delete mode 100644 internal/cli/install/chart/cli.go delete mode 100644 internal/cli/install/configure/configure.go delete mode 100644 internal/cli/install/install.go delete mode 100644 internal/cli/install/k3s/k3s.go create mode 100644 internal/cli/local/cleanup/cleanup.go create mode 100644 internal/cli/local/cleanup/local-storage.go create mode 100644 internal/cli/local/setup/run.go create mode 100644 internal/cli/local/setup/setup.go rename internal/cli/{install/chart/install.go => local/upgrade/run.go} (66%) create mode 100644 internal/cli/local/upgrade/upgrade.go create mode 100644 internal/local/cleanup/local-storage.go diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 156ba49..78231f6 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -6,7 +6,6 @@ import ( "github.com/weka/gohomecli/internal/cli/api" "github.com/weka/gohomecli/internal/cli/app" "github.com/weka/gohomecli/internal/cli/config" - "github.com/weka/gohomecli/internal/cli/install" "github.com/weka/gohomecli/internal/cli/local" "github.com/weka/gohomecli/internal/utils" ) @@ -15,7 +14,6 @@ func init() { api.Cli.InitCobra(app.Cmd()) config.Cli.InitCobra(app.Cmd()) local.Cli.InitCobra(app.Cmd()) - install.Cli.InitCobra(app.Cmd()) } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/internal/cli/install/chart/cli.go b/internal/cli/install/chart/cli.go deleted file mode 100644 index c989bb6..0000000 --- a/internal/cli/install/chart/cli.go +++ /dev/null @@ -1,42 +0,0 @@ -package chart - -import ( - "github.com/spf13/cobra" - - "github.com/weka/gohomecli/internal/cli/app/hooks" -) - -var installCmdOpts struct { - kubeConfigPath string - localChart string - jsonConfig string - remoteDownload bool - remoteVersion string -} - -var bundlePathOverride string - -var chartCmd = &cobra.Command{ - Use: "chart", - Short: "Install Weka Home Helm chart", - Long: `Install Weka Home Helm chart on already deployed Kubernetes cluster`, - Args: cobra.NoArgs, - RunE: runInstallOrUpgrade, -} - -var Cli hooks.Cli - -func init() { - Cli.AddHook(func(appCmd *cobra.Command) { - appCmd.AddCommand(chartCmd) - - chartCmd.Flags().StringVarP(&installCmdOpts.kubeConfigPath, "kube-config", "k", "", "Path to kubeconfig file") - chartCmd.Flags().StringVarP(&installCmdOpts.localChart, "local-chart", "l", "", "Path to local chart directory/archive") - chartCmd.Flags().StringVarP(&installCmdOpts.jsonConfig, "json-config", "c", "", "Configuration in JSON format (file or JSON string)") - chartCmd.Flags().BoolVarP(&installCmdOpts.remoteDownload, "remote-download", "r", false, "Enable downloading chart from remote repository") - chartCmd.Flags().StringVar(&installCmdOpts.remoteVersion, "remote-version", "", "Version of the chart to download from remote repository") - chartCmd.MarkFlagsMutuallyExclusive("local-chart", "remote-download") - chartCmd.Flags().StringVar(&bundlePathOverride, "bundle", "", "bundle with images to load") - chartCmd.Flags().MarkHidden("bundle") - }) -} diff --git a/internal/cli/install/configure/configure.go b/internal/cli/install/configure/configure.go deleted file mode 100644 index aca437e..0000000 --- a/internal/cli/install/configure/configure.go +++ /dev/null @@ -1,48 +0,0 @@ -package configure - -import ( - "errors" - "net/http" - - "github.com/spf13/cobra" - - "github.com/weka/gohomecli/internal/cli/app/hooks" - "github.com/weka/gohomecli/internal/install/web" - "github.com/weka/gohomecli/internal/utils" -) - -var ( - Cli hooks.Cli - logger = utils.GetLogger("Configurer") -) - -var ( - configureCmd = &cobra.Command{ - Use: "configure", - Short: "Configure Weka Home interactively", - } - webArgs = struct{ bindAddr string }{} - webCmd = &cobra.Command{ - Use: "web", - Short: "Configure Weka Home using web interface", - RunE: func(cmd *cobra.Command, args []string) error { - logger.Info().Str("bindAddr", webArgs.bindAddr).Msg("Starting web server") - if err := web.ServeConfigurer(cmd.Context(), webArgs.bindAddr); errors.Is(err, http.ErrServerClosed) { - return nil - } else { - return err - } - }, - } -) - -func init() { - Cli.AddHook(func(appCmd *cobra.Command) { - appCmd.AddCommand(configureCmd) - - if web.IsEnabled() { - configureCmd.AddCommand(webCmd) - webCmd.Flags().StringVarP(&webArgs.bindAddr, "bind-addr", "b", ":8080", "Bind address for web server including port") - } - }) -} diff --git a/internal/cli/install/install.go b/internal/cli/install/install.go deleted file mode 100644 index 2a08304..0000000 --- a/internal/cli/install/install.go +++ /dev/null @@ -1,41 +0,0 @@ -package install - -import ( - "github.com/spf13/cobra" - - "github.com/weka/gohomecli/internal/cli/app/hooks" - "github.com/weka/gohomecli/internal/cli/install/chart" - "github.com/weka/gohomecli/internal/cli/install/configure" - "github.com/weka/gohomecli/internal/cli/install/k3s" - "github.com/weka/gohomecli/internal/env" - "github.com/weka/gohomecli/internal/utils" -) - -var Cli hooks.Cli - -var installGroup = &cobra.Group{ - ID: "install", - Title: "Manage installations", -} - -var installCmd = &cobra.Command{ - Use: "install", - Short: "Manage installations", - Long: "Manage wekahome installations", - GroupID: "install", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if !env.VerboseLogging { - utils.SetGlobalLoggingLevel(utils.InfoLevel) - } - }, -} - -func init() { - Cli.AddHook(func(appCmd *cobra.Command) { - appCmd.AddGroup(installGroup) - appCmd.AddCommand(installCmd) - k3s.Cli.InitCobra(installCmd) - chart.Cli.InitCobra(installCmd) - configure.Cli.InitCobra(installCmd) - }) -} diff --git a/internal/cli/install/k3s/k3s.go b/internal/cli/install/k3s/k3s.go deleted file mode 100644 index 5e0dc01..0000000 --- a/internal/cli/install/k3s/k3s.go +++ /dev/null @@ -1,81 +0,0 @@ -package k3s - -import ( - "github.com/weka/gohomecli/internal/install/bundle" - "github.com/weka/gohomecli/internal/install/k3s" - - "github.com/spf13/cobra" - - "github.com/weka/gohomecli/internal/cli/app/hooks" -) - -var Cli hooks.Cli - -func init() { - Cli.AddHook(func(appCmd *cobra.Command) { - appCmd.AddCommand(k3sCmd) - - k3sCmd.Flags().StringVarP(&k3sInstallConfig.Iface, "iface", "i", "", "interface for k3s network") - k3sCmd.Flags().StringVarP(&k3sInstallConfig.Hostname, "hostname", "n", k3s.Hostname(), "hostname for cluster") - k3sCmd.Flags().StringVar(&k3sInstallConfig.NodeIP, "ip", "", "primary IP internal address for wekahome API") - k3sCmd.Flags().StringSliceVar(&k3sInstallConfig.ExternalIPs, "ips", nil, "additional IP addresses for wekahome API (e.g public ip)") - k3sCmd.Flags().StringVar(&k3sInstallConfig.BundlePath, "bundle", bundle.BundlePath(), "bundle directory with k3s package") - k3sCmd.Flags().BoolVar(&k3sInstallConfig.Debug, "debug", false, "enable debug mode") - - k3sCmd.MarkFlagRequired("iface") - k3sCmd.Flags().MarkHidden("bundle") - k3sCmd.Flags().MarkHidden("ip") - k3sCmd.Flags().MarkHidden("debug") - - k3sUpgradeCmd.Flags().StringVar(&k3sUpgradeConfig.BundlePath, "bundle", bundle.BundlePath(), "bundle with k3s to install") - k3sUpgradeCmd.Flags().BoolVar(&k3sUpgradeConfig.Debug, "debug", false, "enable debug mode") - k3sUpgradeCmd.Flags().MarkHidden("bundle") - k3sUpgradeCmd.Flags().MarkHidden("debug") - - k3sImageImportCmd.Flags().StringVar(&k3sImportConfig.BundlePath, "bundle", bundle.BundlePath(), "bundle with images to load") - k3sImageImportCmd.Flags().BoolVar(&k3sImportConfig.FailFast, "fail-fast", false, "fail on first error") - k3sImageImportCmd.Flags().StringSliceVarP(&k3sImportConfig.ImagePaths, "image-path", "f", nil, "images to import (if specified, bundle images are ignored)") - k3sImageImportCmd.Flags().MarkHidden("bundle") - - k3sCmd.AddCommand(k3sUpgradeCmd) - k3sCmd.AddCommand(k3sImageImportCmd) - }) -} - -var ( - k3sInstallConfig k3s.InstallConfig - k3sUpgradeConfig k3s.UpgradeConfig - k3sImportConfig struct { - BundlePath string - ImagePaths []string - FailFast bool - } -) - -var k3sCmd = &cobra.Command{ - Use: "k3s", - Short: "Install k3s based kubernetes cluster", - RunE: func(cmd *cobra.Command, args []string) error { - return k3s.Install(cmd.Context(), k3sInstallConfig) - }, -} - -var k3sUpgradeCmd = &cobra.Command{ - Use: "upgrade", - Short: "Upgrade k3s cluster to bundled version", - RunE: func(cmd *cobra.Command, args []string) error { - return k3s.Upgrade(cmd.Context(), k3sUpgradeConfig) - }, -} - -var k3sImageImportCmd = &cobra.Command{ - Use: "images", - Short: "Import docker images from bundle", - RunE: func(cmd *cobra.Command, args []string) error { - if len(k3sImportConfig.ImagePaths) == 0 { - return k3s.ImportBundleImages(cmd.Context(), k3sImportConfig.BundlePath, k3sImportConfig.FailFast) - } - - return k3s.ImportImages(cmd.Context(), k3sImportConfig.ImagePaths, k3sImportConfig.FailFast) - }, -} diff --git a/internal/cli/local/cleanup/cleanup.go b/internal/cli/local/cleanup/cleanup.go new file mode 100644 index 0000000..c4164bd --- /dev/null +++ b/internal/cli/local/cleanup/cleanup.go @@ -0,0 +1,22 @@ +package cleanup + +import ( + "github.com/spf13/cobra" + + "github.com/weka/gohomecli/internal/cli/app/hooks" +) + +var ( + Cli hooks.Cli +) + +var cleanupCmd = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup local setup", +} + +func init() { + Cli.AddHook(func(appCmd *cobra.Command) { + appCmd.AddCommand(cleanupCmd) + }) +} diff --git a/internal/cli/local/cleanup/local-storage.go b/internal/cli/local/cleanup/local-storage.go new file mode 100644 index 0000000..eff8ea8 --- /dev/null +++ b/internal/cli/local/cleanup/local-storage.go @@ -0,0 +1,31 @@ +package cleanup + +import ( + "github.com/spf13/cobra" + "github.com/weka/gohomecli/internal/local/cleanup" +) + +var config struct { + LocalPath string +} + +var localStorageCmd = &cobra.Command{ + Use: "local-storage", + Short: "cleans up unused volumes from local path provisioner", + RunE: func(cmd *cobra.Command, args []string) error { + if config.LocalPath != "" { + cleanup.SetLocalStoragePath(config.LocalPath) + } + + return cleanup.LocalStorage(cmd.Context()) + }, +} + +func init() { + Cli = append(Cli, func(c *cobra.Command) { + cleanupCmd.AddCommand(localStorageCmd) + + cleanupCmd.Flags().StringVar(&config.LocalPath, "path", "", "directory to local-path-provisioner") + cleanupCmd.Flags().MarkHidden("bundle") + }) +} diff --git a/internal/cli/local/local.go b/internal/cli/local/local.go index 2663478..eca8caf 100644 --- a/internal/cli/local/local.go +++ b/internal/cli/local/local.go @@ -4,10 +4,10 @@ import ( "github.com/spf13/cobra" "github.com/weka/gohomecli/internal/cli/app/hooks" - "github.com/weka/gohomecli/internal/cli/install/chart" - "github.com/weka/gohomecli/internal/cli/install/configure" - "github.com/weka/gohomecli/internal/cli/install/k3s" + "github.com/weka/gohomecli/internal/cli/local/cleanup" "github.com/weka/gohomecli/internal/cli/local/dump" + "github.com/weka/gohomecli/internal/cli/local/setup" + "github.com/weka/gohomecli/internal/cli/local/upgrade" "github.com/weka/gohomecli/internal/env" "github.com/weka/gohomecli/internal/utils" ) @@ -37,8 +37,8 @@ func init() { appCmd.AddCommand(localCmd) dump.Cli.InitCobra(localCmd) - k3s.Cli.InitCobra(localCmd) - chart.Cli.InitCobra(localCmd) - configure.Cli.InitCobra(localCmd) + setup.Cli.InitCobra(localCmd) + upgrade.Cli.InitCobra(localCmd) + cleanup.Cli.InitCobra(localCmd) }) } diff --git a/internal/cli/local/setup/run.go b/internal/cli/local/setup/run.go new file mode 100644 index 0000000..cd0774f --- /dev/null +++ b/internal/cli/local/setup/run.go @@ -0,0 +1,135 @@ +package setup + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/weka/gohomecli/internal/install/bundle" + "github.com/weka/gohomecli/internal/install/chart" + "github.com/weka/gohomecli/internal/install/k3s" + "github.com/weka/gohomecli/internal/install/web" + "github.com/weka/gohomecli/internal/utils" +) + +func normPath(path string) (string, error) { + if strings.HasPrefix(path, "~/") { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("unable to expand home directory: %w", err) + } + + path = filepath.Join(homeDir, path[2:]) + } + + return filepath.Clean(path), nil +} + +func readConfiguration(jsonConfig string) (*chart.Configuration, error) { + if jsonConfig == "" { + return &chart.Configuration{}, nil + } + + var jsonConfigBytes []byte + if _, err := os.Stat(jsonConfig); err == nil { + logger.Debug().Str("path", jsonConfig).Msg("Reading JSON config from file") + jsonConfigBytes, err = os.ReadFile(jsonConfig) + if err != nil { + logger.Error().Err(err).Msg("Failed to read JSON config from file") + return nil, fmt.Errorf("failed to read JSON config from file: %w", err) + } + } else { + logger.Debug().Msg("Using JSON object from command line") + jsonConfigBytes = []byte(jsonConfig) + } + + logger.Debug().Msg("Parsing JSON config") + config := &chart.Configuration{} + err := json.Unmarshal(jsonConfigBytes, &config) + if err != nil { + logger.Error().Err(err).Msg("Failed to parse JSON config") + return nil, fmt.Errorf("failed to parse JSON config: %w", err) + } + + return config, nil +} + +func runSetup(cmd *cobra.Command, args []string) error { + if config.BundlePath != bundle.BundlePath() { + if err := bundle.SetBundlePath(config.BundlePath); err != nil { + return err + } + } + + if config.Web { + logger.Info().Str("bindAddr", config.WebBindAddr).Msg("Starting web server") + err := web.ServeConfigurer(cmd.Context(), config.WebBindAddr) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil + } + + err := k3s.Install(cmd.Context(), config.K3S) + if err != nil { + return err + } + + if len(k3sImportConfig.ImagePaths) == 0 { + err = k3s.ImportBundleImages(cmd.Context(), k3sImportConfig.FailFast) + } else { + err = k3s.ImportImages(cmd.Context(), k3sImportConfig.ImagePaths, k3sImportConfig.FailFast) + } + + if err != nil { + return err + } + + if config.Chart.remoteVersion != "" && !config.Chart.remoteDownload { + return fmt.Errorf("%w: --remote-version can only be used with --remote-download", utils.ErrValidationFailed) + } + + if config.Chart.kubeConfigPath != "" { + config.Chart.kubeConfigPath, err = normPath(config.Chart.kubeConfigPath) + if err != nil { + return err + } + } + + kubeConfig, err := chart.ReadKubeConfig(config.Chart.kubeConfigPath) + if err != nil { + return err + } + + chartConfig, err := readConfiguration(config.Chart.jsonConfig) + if err != nil { + return err + } + + var chartLocation *chart.LocationOverride + if config.Chart.remoteDownload { + chartLocation = &chart.LocationOverride{ + RemoteDownload: true, + Version: config.Chart.remoteVersion, + } + } + + if config.Chart.localChart != "" { + chartLocation = &chart.LocationOverride{ + Path: config.Chart.localChart, + } + } + + helmOptions := &chart.HelmOptions{ + KubeConfig: kubeConfig, + Override: chartLocation, + } + + return chart.InstallOrUpgrade(cmd.Context(), chartConfig, helmOptions) +} diff --git a/internal/cli/local/setup/setup.go b/internal/cli/local/setup/setup.go new file mode 100644 index 0000000..35a1faf --- /dev/null +++ b/internal/cli/local/setup/setup.go @@ -0,0 +1,76 @@ +package setup + +import ( + "github.com/spf13/cobra" + + "github.com/weka/gohomecli/internal/cli/app/hooks" + "github.com/weka/gohomecli/internal/install/bundle" + "github.com/weka/gohomecli/internal/install/k3s" + "github.com/weka/gohomecli/internal/install/web" + "github.com/weka/gohomecli/internal/utils" +) + +var ( + Cli hooks.Cli + logger = utils.GetLogger("setup") +) + +var config struct { + Web bool + WebBindAddr string + BundlePath string + K3S k3s.InstallConfig + Chart struct { + kubeConfigPath string + localChart string + jsonConfig string + remoteDownload bool + remoteVersion string + } +} + +var k3sImportConfig struct { + ImagePaths []string + FailFast bool +} + +var setupCmd = &cobra.Command{ + Use: "setup", + Short: "Install Local Weka Home", + Long: `Install Weka Home Helm chart with K3S bundle`, + RunE: runSetup, +} + +func init() { + Cli.AddHook(func(appCmd *cobra.Command) { + appCmd.AddCommand(setupCmd) + + if web.IsEnabled() { + setupCmd.Flags().BoolVar(&config.Web, "web", false, "start web installer") + setupCmd.Flags().StringVarP(&config.WebBindAddr, "bind-addr", "b", ":8080", "Bind address for web server including port") + } + + setupCmd.Flags().StringVar(&config.BundlePath, "bundle", bundle.BundlePath(), "bundle directory with k3s package") + + setupCmd.Flags().StringVarP(&config.K3S.Iface, "iface", "i", "", "interface for k3s network") + setupCmd.Flags().StringVarP(&config.K3S.Hostname, "hostname", "n", k3s.Hostname(), "hostname for cluster") + setupCmd.Flags().StringVar(&config.K3S.NodeIP, "ip", "", "primary IP internal address for wekahome API") + setupCmd.Flags().StringSliceVar(&config.K3S.ExternalIPs, "ips", nil, "additional IP addresses for wekahome API (e.g public ip)") + setupCmd.Flags().BoolVar(&config.K3S.Debug, "debug", false, "enable debug mode") + + setupCmd.MarkFlagRequired("iface") + setupCmd.Flags().MarkHidden("bundle") + setupCmd.Flags().MarkHidden("ip") + setupCmd.Flags().MarkHidden("debug") + + setupCmd.Flags().BoolVar(&k3sImportConfig.FailFast, "fail-fast", false, "fail on first error") + setupCmd.Flags().StringSliceVarP(&k3sImportConfig.ImagePaths, "image-path", "f", nil, "images to import (if specified, bundle images are ignored)") + + setupCmd.Flags().StringVarP(&config.Chart.kubeConfigPath, "kube-config", "k", "/etc/rancher/k3s/k3s.yaml", "Path to kubeconfig file") + setupCmd.Flags().StringVarP(&config.Chart.localChart, "local-chart", "l", "", "Path to local chart directory/archive") + setupCmd.Flags().StringVarP(&config.Chart.jsonConfig, "json-config", "c", "", "Configuration in JSON format (file or JSON string)") + setupCmd.Flags().BoolVarP(&config.Chart.remoteDownload, "remote-download", "r", false, "Enable downloading chart from remote repository") + setupCmd.Flags().StringVar(&config.Chart.remoteVersion, "remote-version", "", "Version of the chart to download from remote repository") + setupCmd.MarkFlagsMutuallyExclusive("local-chart", "remote-download") + }) +} diff --git a/internal/cli/install/chart/install.go b/internal/cli/local/upgrade/run.go similarity index 66% rename from internal/cli/install/chart/install.go rename to internal/cli/local/upgrade/run.go index 18c1560..dd84588 100644 --- a/internal/cli/install/chart/install.go +++ b/internal/cli/local/upgrade/run.go @@ -1,4 +1,4 @@ -package chart +package upgrade import ( "encoding/json" @@ -7,16 +7,14 @@ import ( "path/filepath" "strings" - "github.com/weka/gohomecli/internal/install/bundle" - "github.com/weka/gohomecli/internal/install/chart" - "github.com/spf13/cobra" + "github.com/weka/gohomecli/internal/install/bundle" + "github.com/weka/gohomecli/internal/install/chart" + "github.com/weka/gohomecli/internal/install/k3s" "github.com/weka/gohomecli/internal/utils" ) -var logger = utils.GetLogger("HelmChart") - func normPath(path string) (string, error) { if strings.HasPrefix(path, "~/") { homeDir, err := os.UserHomeDir() @@ -59,47 +57,60 @@ func readConfiguration(jsonConfig string) (*chart.Configuration, error) { return config, nil } -func runInstallOrUpgrade(cmd *cobra.Command, args []string) error { - if installCmdOpts.remoteVersion != "" && !installCmdOpts.remoteDownload { - return fmt.Errorf("%w: --remote-version can only be used with --remote-download", utils.ErrValidationFailed) - } - - if bundlePathOverride != "" { - err := bundle.SetBundlePath(bundlePathOverride) - if err != nil { +func runUpgrade(cmd *cobra.Command, args []string) error { + if config.BundlePath != bundle.BundlePath() { + if err := bundle.SetBundlePath(config.BundlePath); err != nil { return err } } - var err error - if installCmdOpts.kubeConfigPath != "" { - installCmdOpts.kubeConfigPath, err = normPath(installCmdOpts.kubeConfigPath) + err := k3s.Upgrade(cmd.Context(), config.K3S) + if err != nil { + return err + } + + if len(k3sImportConfig.ImagePaths) == 0 { + err = k3s.ImportBundleImages(cmd.Context(), k3sImportConfig.FailFast) + } else { + err = k3s.ImportImages(cmd.Context(), k3sImportConfig.ImagePaths, k3sImportConfig.FailFast) + } + + if err != nil { + return err + } + + if config.Chart.remoteVersion != "" && !config.Chart.remoteDownload { + return fmt.Errorf("%w: --remote-version can only be used with --remote-download", utils.ErrValidationFailed) + } + + if config.Chart.kubeConfigPath != "" { + config.Chart.kubeConfigPath, err = normPath(config.Chart.kubeConfigPath) if err != nil { return err } } - kubeConfig, err := chart.ReadKubeConfig(installCmdOpts.kubeConfigPath) + kubeConfig, err := chart.ReadKubeConfig(config.Chart.kubeConfigPath) if err != nil { return err } - chartConfig, err := readConfiguration(installCmdOpts.jsonConfig) + chartConfig, err := readConfiguration(config.Chart.jsonConfig) if err != nil { return err } var chartLocation *chart.LocationOverride - if installCmdOpts.remoteDownload { + if config.Chart.remoteDownload { chartLocation = &chart.LocationOverride{ RemoteDownload: true, - Version: installCmdOpts.remoteVersion, + Version: config.Chart.remoteVersion, } } - if installCmdOpts.localChart != "" { + if config.Chart.localChart != "" { chartLocation = &chart.LocationOverride{ - Path: installCmdOpts.localChart, + Path: config.Chart.localChart, } } diff --git a/internal/cli/local/upgrade/upgrade.go b/internal/cli/local/upgrade/upgrade.go new file mode 100644 index 0000000..21a52a0 --- /dev/null +++ b/internal/cli/local/upgrade/upgrade.go @@ -0,0 +1,69 @@ +package upgrade + +import ( + "github.com/spf13/cobra" + + "github.com/weka/gohomecli/internal/cli/app/hooks" + "github.com/weka/gohomecli/internal/install/bundle" + "github.com/weka/gohomecli/internal/install/k3s" + "github.com/weka/gohomecli/internal/install/web" + "github.com/weka/gohomecli/internal/utils" +) + +var ( + Cli hooks.Cli + logger = utils.GetLogger("upgrade") +) + +var config struct { + Web bool + WebBindAddr string + BundlePath string + K3S k3s.UpgradeConfig + Chart struct { + kubeConfigPath string + localChart string + jsonConfig string + remoteDownload bool + remoteVersion string + } +} + +var k3sImportConfig struct { + ImagePaths []string + FailFast bool +} + +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Upgrade Local Weka Home", + Long: `Upgrade Weka Home with K3S bundle`, + RunE: runUpgrade, +} + +func init() { + Cli.AddHook(func(appCmd *cobra.Command) { + appCmd.AddCommand(upgradeCmd) + + if web.IsEnabled() { + upgradeCmd.Flags().BoolVar(&config.Web, "web", false, "start web installer") + upgradeCmd.Flags().StringVarP(&config.WebBindAddr, "bind-addr", "b", ":8080", "Bind address for web server including port") + } + + upgradeCmd.Flags().StringVar(&config.BundlePath, "bundle", bundle.BundlePath(), "bundle directory with k3s package") + upgradeCmd.Flags().BoolVar(&config.K3S.Debug, "debug", false, "enable debug mode") + + upgradeCmd.Flags().MarkHidden("bundle") + upgradeCmd.Flags().MarkHidden("debug") + + upgradeCmd.Flags().BoolVar(&k3sImportConfig.FailFast, "fail-fast", false, "fail on first error") + upgradeCmd.Flags().StringSliceVarP(&k3sImportConfig.ImagePaths, "image-path", "f", nil, "images to import (if specified, bundle images are ignored)") + + upgradeCmd.Flags().StringVarP(&config.Chart.kubeConfigPath, "kube-config", "k", "/etc/rancher/k3s/k3s.yaml", "Path to kubeconfig file") + upgradeCmd.Flags().StringVarP(&config.Chart.localChart, "local-chart", "l", "", "Path to local chart directory/archive") + upgradeCmd.Flags().StringVarP(&config.Chart.jsonConfig, "json-config", "c", "", "Configuration in JSON format (file or JSON string)") + upgradeCmd.Flags().BoolVarP(&config.Chart.remoteDownload, "remote-download", "r", false, "Enable downloading chart from remote repository") + upgradeCmd.Flags().StringVar(&config.Chart.remoteVersion, "remote-version", "", "Version of the chart to download from remote repository") + upgradeCmd.MarkFlagsMutuallyExclusive("local-chart", "remote-download") + }) +} diff --git a/internal/install/bundle/file.go b/internal/install/bundle/file.go index a0f6fe2..d01df70 100644 --- a/internal/install/bundle/file.go +++ b/internal/install/bundle/file.go @@ -35,12 +35,17 @@ var bundlePath string // by default homecli is located in /opt/wekahome/{release}/bin // and bundle in /opt/wekahome/{release}/ func BundlePath() string { - if bundlePath != "" { - return bundlePath + if bundlePath == "" { + bundlePath = filepath.Join(executableDirectory(), "..") } - bundlePath = filepath.Clean(filepath.Join(executableDirectory(), "..")) - return bundlePath + realPath, err := filepath.EvalSymlinks(bundlePath) + if err != nil { + fmt.Printf("Bad bundle path %q", realPath) + os.Exit(1) + } + + return realPath } func BundleBinDir() string { diff --git a/internal/install/chart/install.go b/internal/install/chart/install.go index 8ba28dd..9c79810 100644 --- a/internal/install/chart/install.go +++ b/internal/install/chart/install.go @@ -158,7 +158,8 @@ func findBundledChart() (string, error) { return nil }) - if err != nil { + + if err != nil || path == "" { return "", fmt.Errorf("unable to find wekahome chart in bundle") } diff --git a/internal/install/k3s/images.go b/internal/install/k3s/images.go index 859cd02..27cfaa4 100644 --- a/internal/install/k3s/images.go +++ b/internal/install/k3s/images.go @@ -168,13 +168,7 @@ func ImportImages(ctx context.Context, imagePaths []string, failFast bool) error return nil } -func ImportBundleImages(ctx context.Context, bundlePathOverride string, failFast bool) error { - if bundlePathOverride != "" { - if err := bundle.SetBundlePath(bundlePathOverride); err != nil { - return err - } - } - +func ImportBundleImages(ctx context.Context, failFast bool) error { imagePaths := []string{} err := bundle.Walk("images", func(path string, info os.FileInfo, err error) error { if err != nil { diff --git a/internal/install/k3s/install.go b/internal/install/k3s/install.go index 548cef4..ea9c718 100644 --- a/internal/install/k3s/install.go +++ b/internal/install/k3s/install.go @@ -30,7 +30,6 @@ var k3sBundleRegexp = regexp.MustCompile(`k3s.*\.(tar(\.gz)?)|(tgz)`) type InstallConfig struct { Iface string // interface for k3s network to work on, required - BundlePath string // path to bundle with k3s and images Hostname string // host name of the cluster, optional, default from env NodeIP string // node ip to bind on as primary internal ip ExternalIPs []string // list of external ip addresses, optional @@ -63,13 +62,6 @@ func Install(ctx context.Context, c InstallConfig) error { return err } - if c.BundlePath != "" { - err := bundle.SetBundlePath(c.BundlePath) - if err != nil { - return err - } - } - name, manifest, err := findBundle() if err != nil { return err diff --git a/internal/install/k3s/upgrade.go b/internal/install/k3s/upgrade.go index 07bfe9f..0b7d6e1 100644 --- a/internal/install/k3s/upgrade.go +++ b/internal/install/k3s/upgrade.go @@ -13,8 +13,7 @@ import ( var ErrNotExist = errors.New("k3s not exists") type UpgradeConfig struct { - BundlePath string - Debug bool + Debug bool } func Upgrade(ctx context.Context, c UpgradeConfig) error { @@ -24,13 +23,6 @@ func Upgrade(ctx context.Context, c UpgradeConfig) error { return ErrNotExist } - if c.BundlePath != "" { - err := bundle.SetBundlePath(c.BundlePath) - if err != nil { - return err - } - } - logger.Debug().Msgf("Looking for bundle") file, manifest, err := findBundle() diff --git a/internal/install/web/api/k3s.go b/internal/install/web/api/k3s.go index fbf716c..2595de0 100644 --- a/internal/install/web/api/k3s.go +++ b/internal/install/web/api/k3s.go @@ -61,7 +61,7 @@ func k3sImportImages(w http.ResponseWriter, r *http.Request) { return } - err := k3s.ImportBundleImages(r.Context(), "", true) + err := k3s.ImportBundleImages(r.Context(), true) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/local/cleanup/local-storage.go b/internal/local/cleanup/local-storage.go new file mode 100644 index 0000000..56a3d4f --- /dev/null +++ b/internal/local/cleanup/local-storage.go @@ -0,0 +1,118 @@ +package cleanup + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/weka/gohomecli/internal/utils" +) + +var logger = utils.GetLogger("cleanup") + +var localStoragePath = "/opt/local-path-provisioner" + +func SetLocalStoragePath(path string) { + localStoragePath = path +} + +func LocalStorage(ctx context.Context) error { + used, err := getUsed(ctx) + if err != nil { + return err + } + + localPath, err := filepath.EvalSymlinks(localStoragePath) + if err != nil { + return err + } + + available, err := filepath.Glob(filepath.Join(localPath, "*")) + if err != nil { + return err + } + + var toRemove []string + for _, dir := range available { + if !used[dir] { + toRemove = append(toRemove, dir) + } + } + + if len(toRemove) == 0 { + logger.Info().Msg("Nothing to cleanup") + return nil + } + + for _, dir := range toRemove { + logger.Warn().Msg(dir) + } + + logger.Warn().Msg("Next directories will be removed in 10 seconds. Press CTRL+C to cancel") + + timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + select { + case <-ctx.Done(): + return fmt.Errorf("cancelled") + case <-timeout.Done(): + break + } + + for _, dir := range toRemove { + err := os.RemoveAll(dir) + if err != nil { + return err + } + logger.Info().Msgf("Persistent volume %q was deleted", filepath.Base(dir)) + } + + logger.Info().Msg("Local storage was cleaned up") + + return nil +} + +func getUsed(ctx context.Context) (map[string]bool, error) { + logger.Debug().Str("path", localStoragePath).Msg("Getting used volumes") + + kubeCmd := exec.CommandContext(ctx, "kubectl", "get", "pv", "-o", "jsonpath='{.items[*].spec.hostPath.path}'", "-A") + stdout, err := kubeCmd.StdoutPipe() + if err != nil { + return nil, err + } + stderr, err := kubeCmd.StderrPipe() + if err != nil { + return nil, err + } + + errWriter := utils.NewWriteScanner(func(b []byte) { + logger.Warn().Msg(string(b)) + }) + + go io.Copy(errWriter, stderr) + + if err := kubeCmd.Start(); err != nil { + return nil, err + } + + out, err := io.ReadAll(stdout) + if err != nil { + return nil, err + } + + if err = kubeCmd.Wait(); err != nil { + return nil, err + } + + var isUsed = map[string]bool{} + + for _, dir := range strings.Split(string(out), " ") { + isUsed[dir] = true + } + return isUsed, nil +}