Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feature(clusterctl): Follow XDG Directory standard for config/data/..…
…. files

feat(clusterctl): use XDG_CONFIG_HOME for script as well
  • Loading branch information
cwrau committed Jun 13, 2023
commit a0d0c1d17124d5b446b4b11032d2f14df15abdd5
2 changes: 1 addition & 1 deletion cmd/clusterctl/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions cmd/clusterctl/client/config/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Expand Down
36 changes: 22 additions & 14 deletions cmd/clusterctl/client/config/reader_viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions cmd/clusterctl/client/config/reader_viper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())

Expand All @@ -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")
Expand Down
8 changes: 6 additions & 2 deletions cmd/clusterctl/client/repository/overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down
15 changes: 10 additions & 5 deletions cmd/clusterctl/client/repository/overrides_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ 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"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
)

func TestOverrides(t *testing.T) {
configDirectory, err := xdg.ConfigFile(config.ConfigFolderXDG)
NewWithT(t).Expect(err).To(BeNil())

tests := []struct {
name string
configVarClient config.VariablesClient
Expand All @@ -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",
Expand Down Expand Up @@ -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))
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/clusterctl/cmd/config_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions cmd/clusterctl/cmd/config_repositories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions cmd/clusterctl/cmd/generate_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.`),
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion cmd/clusterctl/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 13 additions & 14 deletions cmd/clusterctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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")
}
Expand All @@ -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)
Expand Down Expand Up @@ -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{
Expand Down
15 changes: 10 additions & 5 deletions cmd/clusterctl/cmd/version_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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
Expand Down
Loading