diff --git a/cmd/clusterctl/client/config.go b/cmd/clusterctl/client/config.go index 4140c2cf1495..8f2d631d2a1f 100644 --- a/cmd/clusterctl/client/config.go +++ b/cmd/clusterctl/client/config.go @@ -134,7 +134,7 @@ type GetClusterTemplateOptions struct { ClusterName string // KubernetesVersion to use for the workload cluster. If unspecified, the value from os env variables - // or the .cluster-api/clusterctl.yaml config file will be used. + // or the $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml or .cluster-api/clusterctl.yaml config file will be used. KubernetesVersion string // ControlPlaneMachineCount defines the number of control plane machines to be added to the workload cluster. diff --git a/cmd/clusterctl/client/config/client.go b/cmd/clusterctl/client/config/client.go index a25faf9944b1..f689b402cdd1 100644 --- a/cmd/clusterctl/client/config/client.go +++ b/cmd/clusterctl/client/config/client.go @@ -86,9 +86,12 @@ func newConfigClient(path string, options ...Option) (*configClient, error) { } // if there is an injected reader, use it, otherwise use a default one + var err error if client.reader == nil { - client.reader = newViperReader() - if err := client.reader.Init(path); err != nil { + if client.reader, err = newViperReader(); err != nil { + return nil, errors.Wrap(err, "failed to create the configuration reader") + } + if err = client.reader.Init(path); err != nil { return nil, errors.Wrap(err, "failed to initialize the configuration reader") } } diff --git a/cmd/clusterctl/client/config/reader_viper.go b/cmd/clusterctl/client/config/reader_viper.go index 113423100efe..85485b038f67 100644 --- a/cmd/clusterctl/client/config/reader_viper.go +++ b/cmd/clusterctl/client/config/reader_viper.go @@ -27,17 +27,19 @@ import ( "strings" "time" + "github.com/adrg/xdg" "github.com/pkg/errors" "github.com/spf13/viper" - "k8s.io/client-go/util/homedir" logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" ) const ( - // ConfigFolder defines the name of the config folder under $home. + // ConfigFolder defines the old name of the config folder under $HOME. ConfigFolder = ".cluster-api" - // ConfigName defines the name of the config file under ConfigFolder. + // ConfigFolderXDG defines the name of the config folder under $XDG_CONFIG_HOME. + ConfigFolderXDG = "cluster-api" + // ConfigName defines the name of the config file under ConfigFolderXDG. ConfigName = "clusterctl" // DownloadConfigFile is the config file when fetching the config from a remote location. DownloadConfigFile = "clusterctl-download.yaml" @@ -58,14 +60,18 @@ func injectConfigPaths(configPaths []string) viperReaderOption { } // newViperReader returns a viperReader. -func newViperReader(opts ...viperReaderOption) Reader { +func newViperReader(opts ...viperReaderOption) (Reader, error) { + configDirectory, err := xdg.ConfigFile(ConfigFolderXDG) + if err != nil { + return nil, err + } vr := &viperReader{ - configPaths: []string{filepath.Join(homedir.HomeDir(), ConfigFolder)}, + configPaths: []string{configDirectory, filepath.Join(xdg.Home, ConfigFolder)}, } for _, o := range opts { o(vr) } - return vr + return vr, nil } // Init initialize the viperReader. @@ -89,15 +95,17 @@ func (v *viperReader) Init(path string) error { switch { case url.Scheme == "https" || url.Scheme == "http": - configPath := filepath.Join(homedir.HomeDir(), ConfigFolder) + var configDirectory string if len(v.configPaths) > 0 { - configPath = v.configPaths[0] - } - if err := os.MkdirAll(configPath, os.ModePerm); err != nil { - return err + configDirectory = v.configPaths[0] + } else { + configDirectory, err = xdg.ConfigFile(ConfigFolderXDG) + if err != nil { + return err + } } - downloadConfigFile := filepath.Join(configPath, DownloadConfigFile) + downloadConfigFile := filepath.Join(configDirectory, DownloadConfigFile) err = downloadFile(url.String(), downloadConfigFile) if err != nil { return err @@ -112,14 +120,14 @@ func (v *viperReader) Init(path string) error { viper.SetConfigFile(path) } } else { - // Checks if there is a default .cluster-api/clusterctl{.extension} file in home directory + // Checks if there is a default $XDG_CONFIG_HOME/cluster-api/clusterctl{.extension} or $HOME/.cluster-api/clusterctl{.extension} file if !v.checkDefaultConfig() { // since there is no default config to read from, just skip // reading in config log.V(5).Info("No default config file available") return nil } - // Configure viper for reading .cluster-api/clusterctl{.extension} in home directory + // Configure viper for reading $XDG_CONFIG_HOME/cluster-api/clusterctl{.extension} or $HOME/.cluster-api/clusterctl{.extension} file viper.SetConfigName(ConfigName) for _, p := range v.configPaths { viper.AddConfigPath(p) diff --git a/cmd/clusterctl/client/config/reader_viper_test.go b/cmd/clusterctl/client/config/reader_viper_test.go index 8e0b5b209821..53e37e89c5e5 100644 --- a/cmd/clusterctl/client/config/reader_viper_test.go +++ b/cmd/clusterctl/client/config/reader_viper_test.go @@ -108,7 +108,7 @@ func Test_viperReader_Init(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gg := NewWithT(t) - v := newViperReader(injectConfigPaths(tt.configDirs)) + v, _ := newViperReader(injectConfigPaths(tt.configDirs)) if tt.expectErr { gg.Expect(v.Init(tt.configPath)).ToNot(Succeed()) return @@ -168,7 +168,7 @@ func Test_viperReader_Get(t *testing.T) { t.Run(tt.name, func(t *testing.T) { gs := NewWithT(t) - v := newViperReader(injectConfigPaths([]string{dir})) + v, _ := newViperReader(injectConfigPaths([]string{dir})) gs.Expect(v.Init(configFile)).To(Succeed()) @@ -192,7 +192,8 @@ func Test_viperReader_GetWithoutDefaultConfig(t *testing.T) { _ = os.Setenv("FOO_FOO", "bar") - v := newViperReader(injectConfigPaths([]string{dir})) + v, err := newViperReader(injectConfigPaths([]string{dir})) + g.Expect(err).NotTo(HaveOccurred()) g.Expect(v.Init("")).To(Succeed()) got, err := v.Get("FOO_FOO") diff --git a/cmd/clusterctl/client/repository/overrides.go b/cmd/clusterctl/client/repository/overrides.go index 32c4a742ff5e..1a6c858347bf 100644 --- a/cmd/clusterctl/client/repository/overrides.go +++ b/cmd/clusterctl/client/repository/overrides.go @@ -23,9 +23,9 @@ import ( "runtime" "strings" + "github.com/adrg/xdg" "github.com/drone/envsubst/v2" "github.com/pkg/errors" - "k8s.io/client-go/util/homedir" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" ) @@ -68,7 +68,11 @@ func newOverride(o *newOverrideInput) Overrider { // Path returns the fully formed path to the file within the specified // overrides config. func (o *overrides) Path() (string, error) { - basepath := filepath.Join(homedir.HomeDir(), config.ConfigFolder, overrideFolder) + configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG) + if err != nil { + return "", err + } + basepath := filepath.Join(configDirectory, overrideFolder) f, err := o.configVariablesClient.Get(overrideFolderKey) if err == nil && strings.TrimSpace(f) != "" { basepath = f diff --git a/cmd/clusterctl/client/repository/overrides_test.go b/cmd/clusterctl/client/repository/overrides_test.go index b68e2c72bf6d..2565c5dfcc1a 100644 --- a/cmd/clusterctl/client/repository/overrides_test.go +++ b/cmd/clusterctl/client/repository/overrides_test.go @@ -21,8 +21,8 @@ import ( "path/filepath" "testing" + "github.com/adrg/xdg" . "github.com/onsi/gomega" - "k8s.io/client-go/util/homedir" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" @@ -30,6 +30,9 @@ import ( ) func TestOverrides(t *testing.T) { + configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG) + NewWithT(t).Expect(err).To(BeNil()) + tests := []struct { name string configVarClient config.VariablesClient @@ -39,17 +42,17 @@ func TestOverrides(t *testing.T) { { name: "returns default overrides path if no config provided", configVarClient: test.NewFakeVariableClient(), - expectedPath: filepath.Join(homedir.HomeDir(), config.ConfigFolder, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), + expectedPath: filepath.Join(configDirectory, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), }, { name: "returns default overrides path if config variable is empty", configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, ""), - expectedPath: filepath.Join(homedir.HomeDir(), config.ConfigFolder, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), + expectedPath: filepath.Join(configDirectory, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), }, { name: "returns default overrides path if config variable is whitespace", configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, " "), - expectedPath: filepath.Join(homedir.HomeDir(), config.ConfigFolder, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), + expectedPath: filepath.Join(configDirectory, overrideFolder, "infrastructure-myinfra", "v1.0.1", "infra-comp.yaml"), }, { name: "uses overrides folder from the config variables", @@ -86,7 +89,9 @@ func TestOverrides(t *testing.T) { filePath: "infra-comp.yaml", }) - g.Expect(override.Path()).To(Equal(tt.expectedPath)) + overridePath, err := override.Path() + g.Expect(err).To(BeNil()) + g.Expect(overridePath).To(Equal(tt.expectedPath)) }) } } diff --git a/cmd/clusterctl/cmd/config_repositories.go b/cmd/clusterctl/cmd/config_repositories.go index 48e189d18771..0531e50130c7 100644 --- a/cmd/clusterctl/cmd/config_repositories.go +++ b/cmd/clusterctl/cmd/config_repositories.go @@ -56,7 +56,7 @@ var configRepositoryCmd = &cobra.Command{ Display the list of providers and their repository configurations. clusterctl ships with a list of known providers; if necessary, edit - $HOME/.cluster-api/clusterctl.yaml file to add a new provider or to customize existing ones.`), + $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml file to add a new provider or to customize existing ones.`), Example: Examples(` # Displays the list of available providers. diff --git a/cmd/clusterctl/cmd/config_repositories_test.go b/cmd/clusterctl/cmd/config_repositories_test.go index dd816cf99b04..cde75d160556 100644 --- a/cmd/clusterctl/cmd/config_repositories_test.go +++ b/cmd/clusterctl/cmd/config_repositories_test.go @@ -86,7 +86,7 @@ var template = `--- providers: # add a custom provider - name: "my-infra-provider" - url: "/home/.cluster-api/overrides/infrastructure-docker/latest/infrastructure-components.yaml" + url: "/home/.config/cluster-api/overrides/infrastructure-docker/latest/infrastructure-components.yaml" type: "InfrastructureProvider" # add a custom provider - name: "another-provider" @@ -129,7 +129,7 @@ kubekey InfrastructureProvider https://github.com/kubesphere/kubek kubevirt InfrastructureProvider https://github.com/kubernetes-sigs/cluster-api-provider-kubevirt/releases/latest/ infrastructure-components.yaml maas InfrastructureProvider https://github.com/spectrocloud/cluster-api-provider-maas/releases/latest/ infrastructure-components.yaml metal3 InfrastructureProvider https://github.com/metal3-io/cluster-api-provider-metal3/releases/latest/ infrastructure-components.yaml -my-infra-provider InfrastructureProvider /home/.cluster-api/overrides/infrastructure-docker/latest/ infrastructure-components.yaml +my-infra-provider InfrastructureProvider /home/.config/cluster-api/overrides/infrastructure-docker/latest/ infrastructure-components.yaml nested InfrastructureProvider https://github.com/kubernetes-sigs/cluster-api-provider-nested/releases/latest/ infrastructure-components.yaml nutanix InfrastructureProvider https://github.com/nutanix-cloud-native/cluster-api-provider-nutanix/releases/latest/ infrastructure-components.yaml oci InfrastructureProvider https://github.com/oracle/cluster-api-provider-oci/releases/latest/ infrastructure-components.yaml @@ -251,7 +251,7 @@ var expectedOutputYaml = `- File: core_components.yaml - File: infrastructure-components.yaml Name: my-infra-provider ProviderType: InfrastructureProvider - URL: /home/.cluster-api/overrides/infrastructure-docker/latest/ + URL: /home/.config/cluster-api/overrides/infrastructure-docker/latest/ - File: infrastructure-components.yaml Name: nested ProviderType: InfrastructureProvider diff --git a/cmd/clusterctl/cmd/generate_cluster.go b/cmd/clusterctl/cmd/generate_cluster.go index 066a24a7e0cc..976780273dbf 100644 --- a/cmd/clusterctl/cmd/generate_cluster.go +++ b/cmd/clusterctl/cmd/generate_cluster.go @@ -55,7 +55,7 @@ var generateClusterClusterCmd = &cobra.Command{ Generate templates for creating workload clusters. clusterctl ships with a list of known providers; if necessary, edit - $HOME/.cluster-api/clusterctl.yaml to add new provider or to customize existing ones. + $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml to add new provider or to customize existing ones. Each provider configuration links to a repository; clusterctl uses this information to fetch templates when creating a new cluster.`), @@ -112,7 +112,7 @@ func init() { generateClusterClusterCmd.Flags().StringVarP(&gc.targetNamespace, "target-namespace", "n", "", "The namespace to use for the workload cluster. If unspecified, the current namespace will be used.") generateClusterClusterCmd.Flags().StringVar(&gc.kubernetesVersion, "kubernetes-version", "", - "The Kubernetes version to use for the workload cluster. If unspecified, the value from OS environment variables or the .cluster-api/clusterctl.yaml config file will be used.") + "The Kubernetes version to use for the workload cluster. If unspecified, the value from OS environment variables or the $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml config file will be used.") generateClusterClusterCmd.Flags().Int64Var(&gc.controlPlaneMachineCount, "control-plane-machine-count", 1, "The number of control plane machines for the workload cluster.") generateClusterClusterCmd.Flags().Int64Var(&gc.workerMachineCount, "worker-machine-count", 0, diff --git a/cmd/clusterctl/cmd/init.go b/cmd/clusterctl/cmd/init.go index f53342cd81ea..aac13344bead 100644 --- a/cmd/clusterctl/cmd/init.go +++ b/cmd/clusterctl/cmd/init.go @@ -56,7 +56,7 @@ var initCmd = &cobra.Command{ to have enough privileges to install the desired components. Use 'clusterctl config repositories' to get a list of available providers; if necessary, edit - $HOME/.cluster-api/clusterctl.yaml file to add new provider or to customize existing ones. + $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml file to add new provider or to customize existing ones. Some providers require environment variables to be set before running clusterctl init. Refer to the provider documentation, or use 'clusterctl config provider [name]' to get a list of required variables. diff --git a/cmd/clusterctl/cmd/root.go b/cmd/clusterctl/cmd/root.go index 517a42024e71..f24f5aac6912 100644 --- a/cmd/clusterctl/cmd/root.go +++ b/cmd/clusterctl/cmd/root.go @@ -25,9 +25,9 @@ import ( "strings" "github.com/MakeNowJust/heredoc" + "github.com/adrg/xdg" "github.com/pkg/errors" "github.com/spf13/cobra" - "k8s.io/client-go/util/homedir" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" @@ -56,16 +56,6 @@ var RootCmd = &cobra.Command{ Long: LongDesc(` Get started with Cluster API using clusterctl to create a management cluster, install providers, and create templates for your workload cluster.`), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - // Check if Config folder (~/.cluster-api) exist and if not create it - configFolderPath := filepath.Join(homedir.HomeDir(), config.ConfigFolder) - if _, err := os.Stat(configFolderPath); os.IsNotExist(err) { - if err := os.MkdirAll(filepath.Dir(configFolderPath), os.ModePerm); err != nil { - return errors.Wrapf(err, "failed to create the clusterctl config directory: %s", configFolderPath) - } - } - return nil - }, PersistentPostRunE: func(cmd *cobra.Command, args []string) error { // Check if clusterctl needs an upgrade "AFTER" running each command // and sub-command. @@ -78,7 +68,11 @@ var RootCmd = &cobra.Command{ // version check is disabled. Return early. return nil } - output, err := newVersionChecker(configClient.Variables()).Check() + checker, err := newVersionChecker(configClient.Variables()) + if err != nil { + return err + } + output, err := checker.Check() if err != nil { return errors.Wrap(err, "unable to verify clusterctl version") } @@ -87,8 +81,13 @@ var RootCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "\033[33m%s\033[0m", output) } + configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG) + if err != nil { + return err + } + // clean the downloaded config if was fetched from remote - downloadConfigFile := filepath.Join(homedir.HomeDir(), config.ConfigFolder, config.DownloadConfigFile) + downloadConfigFile := filepath.Join(configDirectory, config.DownloadConfigFile) if _, err := os.Stat(downloadConfigFile); err == nil { if verbosity != nil && *verbosity >= 5 { fmt.Fprintf(os.Stdout, "Removing downloaded clusterctl config file: %s\n", config.DownloadConfigFile) @@ -122,7 +121,7 @@ func init() { RootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", - "Path to clusterctl configuration (default is `$HOME/.cluster-api/clusterctl.yaml`) or to a remote location (i.e. https://example.com/clusterctl.yaml)") + "Path to clusterctl configuration (default is `$XDG_CONFIG_HOME/cluster-api/clusterctl.yaml`) or to a remote location (i.e. https://example.com/clusterctl.yaml)") RootCmd.AddGroup( &cobra.Group{ diff --git a/cmd/clusterctl/cmd/version_checker.go b/cmd/clusterctl/cmd/version_checker.go index 8aee0ef0607c..65308b319d3d 100644 --- a/cmd/clusterctl/cmd/version_checker.go +++ b/cmd/clusterctl/cmd/version_checker.go @@ -25,11 +25,11 @@ import ( "strings" "time" + "github.com/adrg/xdg" "github.com/blang/semver" "github.com/google/go-github/v48/github" "github.com/pkg/errors" "golang.org/x/oauth2" - "k8s.io/client-go/util/homedir" "sigs.k8s.io/yaml" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" @@ -51,7 +51,7 @@ type versionChecker struct { // newVersionChecker returns a versionChecker. Its behavior has been inspired // by https://github.com/cli/cli. -func newVersionChecker(vc config.VariablesClient) *versionChecker { +func newVersionChecker(vc config.VariablesClient) (*versionChecker, error) { var client *github.Client token, err := vc.Get("GITHUB_TOKEN") if err == nil { @@ -64,11 +64,16 @@ func newVersionChecker(vc config.VariablesClient) *versionChecker { client = github.NewClient(nil) } + configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG) + if err != nil { + return nil, err + } + return &versionChecker{ - versionFilePath: filepath.Join(homedir.HomeDir(), config.ConfigFolder, "version.yaml"), + versionFilePath: filepath.Join(configDirectory, "version.yaml"), cliVersion: version.Get, githubClient: client, - } + }, nil } // ReleaseInfo stores information about the release. @@ -87,7 +92,7 @@ type VersionState struct { // latest available release for CAPI // (https://github.com/kubernetes-sigs/cluster-api). It gets the latest // release from github at most once during a 24 hour period and caches the -// state by default in $HOME/.cluster-api/state.yaml. If the clusterctl +// state by default in $XDG_CONFIG_HOME/cluster-api/state.yaml. If the clusterctl // version is the same or greater it returns nothing. func (v *versionChecker) Check() (string, error) { log := logf.Log diff --git a/cmd/clusterctl/cmd/version_checker_test.go b/cmd/clusterctl/cmd/version_checker_test.go index 0ca77cfad89a..b48dcebf7616 100644 --- a/cmd/clusterctl/cmd/version_checker_test.go +++ b/cmd/clusterctl/cmd/version_checker_test.go @@ -24,8 +24,8 @@ import ( "testing" "time" + "github.com/adrg/xdg" . "github.com/onsi/gomega" - "k8s.io/client-go/util/homedir" "sigs.k8s.io/yaml" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" @@ -35,9 +35,15 @@ import ( func TestVersionChecker_newVersionChecker(t *testing.T) { g := NewWithT(t) - versionChecker := newVersionChecker(test.NewFakeVariableClient()) + versionChecker, err := newVersionChecker(test.NewFakeVariableClient()) - expectedStateFilePath := filepath.Join(homedir.HomeDir(), ".cluster-api", "version.yaml") + g.Expect(err).To(BeNil()) + + configHome, err := xdg.ConfigFile("cluster-api") + + g.Expect(err).To(BeNil()) + + expectedStateFilePath := filepath.Join(configHome, "version.yaml") g.Expect(versionChecker.versionFilePath).To(Equal(expectedStateFilePath)) g.Expect(versionChecker.cliVersion).ToNot(BeNil()) g.Expect(versionChecker.githubClient).ToNot(BeNil()) @@ -240,7 +246,9 @@ https://github.com/foo/bar/releases/v0.3.8-alpha.1 }, ) defer cleanup() - versionChecker := newVersionChecker(test.NewFakeVariableClient()) + versionChecker, err := newVersionChecker(test.NewFakeVariableClient()) + g.Expect(err).To(BeNil()) + versionChecker.cliVersion = tt.cliVersion versionChecker.githubClient = fakeGithubClient versionChecker.versionFilePath = tmpVersionFile @@ -272,7 +280,8 @@ func TestVersionChecker_WriteStateFile(t *testing.T) { tmpVersionFile, cleanDir := generateTempVersionFilePath(g) defer cleanDir() - versionChecker := newVersionChecker(test.NewFakeVariableClient()) + versionChecker, err := newVersionChecker(test.NewFakeVariableClient()) + g.Expect(err).To(BeNil()) versionChecker.versionFilePath = tmpVersionFile versionChecker.githubClient = fakeGithubClient @@ -303,13 +312,14 @@ func TestVersionChecker_ReadFromStateFile(t *testing.T) { }, ) defer cleanup1() - versionChecker := newVersionChecker(test.NewFakeVariableClient()) + versionChecker, err := newVersionChecker(test.NewFakeVariableClient()) + g.Expect(err).To(BeNil()) versionChecker.versionFilePath = tmpVersionFile versionChecker.githubClient = fakeGithubClient1 // this call to getLatestRelease will pull from our fakeGithubClient1 and // store the information including timestamp into the state file. - _, err := versionChecker.getLatestRelease() + _, err = versionChecker.getLatestRelease() g.Expect(err).ToNot(HaveOccurred()) // override the github client with response to a new version v0.3.99 @@ -359,11 +369,12 @@ func TestVersionChecker_ReadFromStateFileWithin24Hrs(t *testing.T) { }, ) defer cleanup1() - versionChecker := newVersionChecker(test.NewFakeVariableClient()) + versionChecker, err := newVersionChecker(test.NewFakeVariableClient()) + g.Expect(err).To(BeNil()) versionChecker.versionFilePath = tmpVersionFile versionChecker.githubClient = fakeGithubClient1 - _, err := versionChecker.getLatestRelease() + _, err = versionChecker.getLatestRelease() g.Expect(err).ToNot(HaveOccurred()) // Since the state file is more that 24 hours old we want to retrieve the diff --git a/cmd/clusterctl/hack/create-local-repository.py b/cmd/clusterctl/hack/create-local-repository.py index c8d5f750a0fa..bfb9880b1e8c 100755 --- a/cmd/clusterctl/hack/create-local-repository.py +++ b/cmd/clusterctl/hack/create-local-repository.py @@ -40,54 +40,54 @@ from __future__ import unicode_literals +import errno import json -import subprocess import os +import subprocess from distutils.dir_util import copy_tree from distutils.file_util import copy_file -import errno -import sys settings = {} providers = { - 'cluster-api': { - 'componentsFile': 'core-components.yaml', - 'nextVersion': 'v1.5.99', - 'type': 'CoreProvider', - }, - 'bootstrap-kubeadm': { - 'componentsFile': 'bootstrap-components.yaml', - 'nextVersion': 'v1.5.99', - 'type': 'BootstrapProvider', - 'configFolder': 'bootstrap/kubeadm/config/default', - }, - 'control-plane-kubeadm': { - 'componentsFile': 'control-plane-components.yaml', - 'nextVersion': 'v1.5.99', - 'type': 'ControlPlaneProvider', - 'configFolder': 'controlplane/kubeadm/config/default', - }, - 'infrastructure-docker': { - 'componentsFile': 'infrastructure-components.yaml', - 'nextVersion': 'v1.5.99', - 'type': 'InfrastructureProvider', - 'configFolder': 'test/infrastructure/docker/config/default', - }, - 'infrastructure-in-memory': { + 'cluster-api': { + 'componentsFile': 'core-components.yaml', + 'nextVersion': 'v1.5.99', + 'type': 'CoreProvider', + }, + 'bootstrap-kubeadm': { + 'componentsFile': 'bootstrap-components.yaml', + 'nextVersion': 'v1.5.99', + 'type': 'BootstrapProvider', + 'configFolder': 'bootstrap/kubeadm/config/default', + }, + 'control-plane-kubeadm': { + 'componentsFile': 'control-plane-components.yaml', + 'nextVersion': 'v1.5.99', + 'type': 'ControlPlaneProvider', + 'configFolder': 'controlplane/kubeadm/config/default', + }, + 'infrastructure-docker': { + 'componentsFile': 'infrastructure-components.yaml', + 'nextVersion': 'v1.5.99', + 'type': 'InfrastructureProvider', + 'configFolder': 'test/infrastructure/docker/config/default', + }, + 'infrastructure-in-memory': { 'componentsFile': 'infrastructure-components.yaml', 'nextVersion': 'v1.5.99', 'type': 'InfrastructureProvider', 'configFolder': 'test/infrastructure/inmemory/config/default', }, 'runtime-extension-test': { - 'componentsFile': 'runtime-extension-components.yaml', - 'nextVersion': 'v1.5.99', - 'type': 'RuntimeExtensionProvider', - 'configFolder': 'test/extension/config/default', - }, + 'componentsFile': 'runtime-extension-components.yaml', + 'nextVersion': 'v1.5.99', + 'type': 'RuntimeExtensionProvider', + 'configFolder': 'test/extension/config/default', + }, } + def load_settings(): global settings try: @@ -95,6 +95,7 @@ def load_settings(): except Exception as e: raise Exception('failed to load clusterctl-settings.json: {}'.format(e)) + def load_providers(): provider_repos = settings.get('provider_repos', []) for repo in provider_repos: @@ -108,26 +109,31 @@ def load_providers(): except Exception as e: raise Exception('failed to load clusterctl-settings.json from repo {}: {}'.format(repo, e)) + def execCmd(args): try: out = subprocess.Popen(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) stdout, stderr = out.communicate() if stderr is not None: raise Exception('stderr contains: \n{}'.format(stderr)) return stdout - except Exception as e: + except Exception as e: raise Exception('failed to run {}: {}'.format(args, e)) -def get_home(): - return os.path.expanduser('~') def get_repository_folder(): - home = get_home() - return os.path.join(home, '.cluster-api', 'dev-repository') + config_dir = os.getenv("XDG_CONFIG_HOME", "") + if config_dir == "": + home_dir = os.getenv("HOME", "") + if home_dir == "": + raise Exception('HOME variable is not set') + config_dir = os.path.join(home_dir, ".config") + return os.path.join(config_dir, 'cluster-api', 'dev-repository') + def write_local_repository(provider, version, components_file, components_yaml, metadata_file): try: @@ -155,6 +161,7 @@ def write_local_repository(provider, version, components_file, components_yaml, except Exception as e: raise Exception('failed to write {} to {}: {}'.format(components_file, provider_folder, e)) + def create_local_repositories(): providerList = settings.get('providers', []) assert providerList is not None, 'invalid configuration: please define the list of providers to override' @@ -162,31 +169,38 @@ def create_local_repositories(): for provider in providerList: p = providers.get(provider) - assert p is not None, 'invalid configuration: please specify the configuration for the {} provider'.format(provider) + assert p is not None, 'invalid configuration: please specify the configuration for the {} provider'.format( + provider) repo = p.get('repo', '.') config_folder = p.get('configFolder', 'config/default') - metadata_file = repo+'/metadata.yaml' + metadata_file = repo + '/metadata.yaml' next_version = p.get('nextVersion') - assert next_version is not None, 'invalid configuration for provider {}: please provide nextVersion value'.format(provider) + assert next_version is not None, 'invalid configuration for provider {}: please provide nextVersion value'.format( + provider) name, type = splitNameAndType(provider) - assert name is not None, 'invalid configuration for provider {}: please use a valid provider label'.format(provider) + assert name is not None, 'invalid configuration for provider {}: please use a valid provider label'.format( + provider) components_file = p.get('componentsFile') - assert components_file is not None, 'invalid configuration for provider {}: please provide componentsFile value'.format(provider) + assert components_file is not None, 'invalid configuration for provider {}: please provide componentsFile value'.format( + provider) execCmd(['make', 'kustomize']) components_yaml = execCmd(['./hack/tools/bin/kustomize', 'build', os.path.join(repo, config_folder)]) - components_path = write_local_repository(provider, next_version, components_file, components_yaml, metadata_file) + components_path = write_local_repository(provider, next_version, components_file, components_yaml, + metadata_file) yield name, type, next_version, components_path + def injectLatest(path): head, tail = os.path.split(path) return '{}/latest/{}'.format(head, tail) + def create_dev_config(repos): yaml = "providers:\n" for name, type, next_version, components_path in repos: @@ -205,6 +219,7 @@ def create_dev_config(repos): except Exception as e: raise Exception('failed to write {}: {}'.format(config_path, e)) + def splitNameAndType(provider): if provider == 'cluster-api': return 'cluster-api', 'CoreProvider' @@ -222,27 +237,35 @@ def splitNameAndType(provider): return provider[len('addon-'):], 'AddonProvider' return None, None + def CoreProviderFlag(): return '--core' + def BootstrapProviderFlag(): return '--bootstrap' + def ControlPlaneProviderFlag(): return '--control-plane' + def InfrastructureProviderFlag(): return '--infrastructure' + def IPAMProviderFlag(): return '--ipam' + def RuntimeExtensionProviderFlag(): return '--runtime-extension' + def AddonProviderFlag(): return '--addon' + def type_to_flag(type): switcher = { 'CoreProvider': CoreProviderFlag, @@ -256,19 +279,21 @@ def type_to_flag(type): func = switcher.get(type, lambda: 'Invalid type') return func() + def print_instructions(repos): providerList = settings.get('providers', []) - print ('clusterctl local overrides generated from local repositories for the {} providers.'.format(', '.join(providerList))) - print ('in order to use them, please run:') + print('clusterctl local overrides generated from local repositories for the {} providers.'.format( + ', '.join(providerList))) + print('in order to use them, please run:') print cmd = "clusterctl init \\\n" for name, type, next_version, components_path in repos: cmd += " {} {}:{} \\\n".format(type_to_flag(type), name, next_version) cmd += " --config ~/.cluster-api/dev-repository/config.yaml" - print (cmd) + print(cmd) print if 'infrastructure-docker' in providerList: - print ('please check the documentation for additional steps required for using the docker provider') + print('please check the documentation for additional steps required for using the docker provider') print if 'infrastructure-in-memory' in providerList: print ('please check the documentation for additional steps required for using the in-memory provider') diff --git a/docs/book/src/clusterctl/commands/additional-commands.md b/docs/book/src/clusterctl/commands/additional-commands.md index 278d8d9a1977..f7b251964b79 100644 --- a/docs/book/src/clusterctl/commands/additional-commands.md +++ b/docs/book/src/clusterctl/commands/additional-commands.md @@ -3,7 +3,7 @@ Display the list of providers and their repository configurations. clusterctl ships with a list of known providers; if necessary, edit -$HOME/.cluster-api/clusterctl.yaml file to add a new provider or to customize existing ones. +$XDG_CONFIG_HOME/cluster-api/clusterctl.yaml file to add a new provider or to customize existing ones. # clusterctl help diff --git a/docs/book/src/clusterctl/commands/init.md b/docs/book/src/clusterctl/commands/init.md index 366b3a11bac5..032a8771bae3 100644 --- a/docs/book/src/clusterctl/commands/init.md +++ b/docs/book/src/clusterctl/commands/init.md @@ -127,10 +127,10 @@ See [clusterctl configuration](../configuration.md) for more info about provider