diff --git a/cmd/clusterctl/client/config/cert_manager_client.go b/cmd/clusterctl/client/config/cert_manager_client.go index a232829409f7..08b78753ce79 100644 --- a/cmd/clusterctl/client/config/cert_manager_client.go +++ b/cmd/clusterctl/client/config/cert_manager_client.go @@ -17,8 +17,10 @@ limitations under the License. package config import ( + "os" "time" + "github.com/drone/envsubst/v2" "github.com/pkg/errors" ) @@ -77,6 +79,12 @@ func (p *certManagerClient) Get() (CertManager, error) { if userCertManager.URL != "" { url = userCertManager.URL } + + url, err := envsubst.Eval(url, os.Getenv) + if err != nil { + return nil, errors.Wrapf(err, "unable to evaluate url: %q", url) + } + if userCertManager.Version != "" { version = userCertManager.Version } diff --git a/cmd/clusterctl/client/config/cert_manager_client_test.go b/cmd/clusterctl/client/config/cert_manager_client_test.go index 0f0d5e8f1faa..698c955cf3fc 100644 --- a/cmd/clusterctl/client/config/cert_manager_client_test.go +++ b/cmd/clusterctl/client/config/cert_manager_client_test.go @@ -17,6 +17,7 @@ limitations under the License. package config import ( + "os" "testing" . "github.com/onsi/gomega" @@ -31,6 +32,7 @@ func TestCertManagerGet(t *testing.T) { tests := []struct { name string fields fields + envVars map[string]string want CertManager wantErr bool }{ @@ -50,6 +52,17 @@ func TestCertManagerGet(t *testing.T) { want: NewCertManager("foo-url", "vX.Y.Z", CertManagerDefaultTimeout.String()), wantErr: false, }, + { + name: "return custom url with evaluated env vars if defined", + fields: fields{ + reader: test.NewFakeReader().WithCertManager("${TEST_REPO_PATH}/foo-url", "vX.Y.Z", ""), + }, + envVars: map[string]string{ + "TEST_REPO_PATH": "/tmp/test", + }, + want: NewCertManager("/tmp/test/foo-url", "vX.Y.Z", CertManagerDefaultTimeout.String()), + wantErr: false, + }, { name: "return timeout if defined", fields: fields{ @@ -63,6 +76,14 @@ func TestCertManagerGet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) + for k, v := range tt.envVars { + g.Expect(os.Setenv(k, v)).To(Succeed()) + } + defer func() { + for k := range tt.envVars { + g.Expect(os.Unsetenv(k)).To(Succeed()) + } + }() p := &certManagerClient{ reader: tt.fields.reader, } diff --git a/cmd/clusterctl/client/config/providers_client.go b/cmd/clusterctl/client/config/providers_client.go index a1e5f570a652..055cdfd3f701 100644 --- a/cmd/clusterctl/client/config/providers_client.go +++ b/cmd/clusterctl/client/config/providers_client.go @@ -18,9 +18,11 @@ package config import ( "net/url" + "os" "sort" "strings" + "github.com/drone/envsubst/v2" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/util/validation" @@ -295,6 +297,12 @@ func (p *providersClient) List() ([]Provider, error) { } for _, u := range userDefinedProviders { + var err error + u.URL, err = envsubst.Eval(u.URL, os.Getenv) + if err != nil { + return nil, errors.Wrapf(err, "unable to evaluate url: %q", u.URL) + } + provider := NewProvider(u.Name, u.URL, u.Type) if err := validateProvider(provider); err != nil { return nil, errors.Wrapf(err, "error validating configuration for the %s with name %s. Please fix the providers value in clusterctl configuration file", provider.Type(), provider.Name()) diff --git a/cmd/clusterctl/client/config/providers_client_test.go b/cmd/clusterctl/client/config/providers_client_test.go index 1296f3a8c217..a54f3de3f061 100644 --- a/cmd/clusterctl/client/config/providers_client_test.go +++ b/cmd/clusterctl/client/config/providers_client_test.go @@ -18,6 +18,7 @@ package config import ( "fmt" + "os" "sort" "testing" @@ -50,6 +51,7 @@ func Test_providers_List(t *testing.T) { tests := []struct { name string fields fields + envVars map[string]string want []Provider wantErr bool }{ @@ -75,6 +77,23 @@ func Test_providers_List(t *testing.T) { want: defaultsAndZZZ, wantErr: false, }, + { + name: "Returns user defined provider configurations with evaluated env vars", + fields: fields{ + configGetter: test.NewFakeReader(). + WithVar( + ProvidersConfigKey, + "- name: \"zzz\"\n"+ + " url: \"${TEST_REPO_PATH}/infrastructure-components.yaml\"\n"+ + " type: \"InfrastructureProvider\"\n", + ), + }, + envVars: map[string]string{ + "TEST_REPO_PATH": "https://zzz", + }, + want: defaultsAndZZZ, + wantErr: false, + }, { name: "User defined provider configurations override defaults", fields: fields{ @@ -120,6 +139,14 @@ func Test_providers_List(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) + for k, v := range tt.envVars { + g.Expect(os.Setenv(k, v)).To(Succeed()) + } + defer func() { + for k := range tt.envVars { + g.Expect(os.Unsetenv(k)).To(Succeed()) + } + }() p := &providersClient{ reader: tt.fields.configGetter, } diff --git a/cmd/clusterctl/client/repository/overrides.go b/cmd/clusterctl/client/repository/overrides.go index 473e16a76eee..07e0184283e0 100644 --- a/cmd/clusterctl/client/repository/overrides.go +++ b/cmd/clusterctl/client/repository/overrides.go @@ -17,14 +17,17 @@ limitations under the License. package repository import ( + "fmt" "os" "path/filepath" "strings" + "github.com/drone/envsubst/v2" "github.com/pkg/errors" "k8s.io/client-go/util/homedir" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" + logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" ) const ( @@ -69,6 +72,13 @@ func (o *overrides) Path() string { f, err := o.configVariablesClient.Get(overrideFolderKey) if err == nil && strings.TrimSpace(f) != "" { basepath = f + + evaluatedBasePath, err := envsubst.Eval(basepath, os.Getenv) + if err != nil { + logf.Log.Info(fmt.Sprintf("⚠️overridesFolder %q could not be evaluated: %v", basepath, err)) + } else { + basepath = evaluatedBasePath + } } return filepath.Join( diff --git a/cmd/clusterctl/client/repository/overrides_test.go b/cmd/clusterctl/client/repository/overrides_test.go index b552ddee8f8b..b68e2c72bf6d 100644 --- a/cmd/clusterctl/client/repository/overrides_test.go +++ b/cmd/clusterctl/client/repository/overrides_test.go @@ -33,6 +33,7 @@ func TestOverrides(t *testing.T) { tests := []struct { name string configVarClient config.VariablesClient + envVars map[string]string expectedPath string }{ { @@ -55,11 +56,28 @@ func TestOverrides(t *testing.T) { configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, "/Users/foobar/workspace/releases"), expectedPath: "/Users/foobar/workspace/releases/infrastructure-myinfra/v1.0.1/infra-comp.yaml", }, + { + name: "uses overrides folder from the config variables with evaluated env vars", + configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, "${TEST_REPO_PATH}/releases"), + envVars: map[string]string{ + "TEST_REPO_PATH": "/tmp/test", + }, + expectedPath: "/tmp/test/releases/infrastructure-myinfra/v1.0.1/infra-comp.yaml", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) + + for k, v := range tt.envVars { + g.Expect(os.Setenv(k, v)).To(Succeed()) + } + defer func() { + for k := range tt.envVars { + g.Expect(os.Unsetenv(k)).To(Succeed()) + } + }() provider := config.NewProvider("myinfra", "", clusterctlv1.InfrastructureProviderType) override := newOverride(&newOverrideInput{ configVariablesClient: tt.configVarClient, diff --git a/docs/book/src/clusterctl/configuration.md b/docs/book/src/clusterctl/configuration.md index ae3a28d73d99..dea982fcf832 100644 --- a/docs/book/src/clusterctl/configuration.md +++ b/docs/book/src/clusterctl/configuration.md @@ -41,6 +41,8 @@ providers: See [provider contract](provider-contract.md) for instructions about how to set up a provider repository. +**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `url`. + ## Variables When installing a provider `clusterctl` reads a YAML file that is published in the provider repository. While executing @@ -73,6 +75,8 @@ cert-manager: url: "/Users/foo/.cluster-api/dev-repository/cert-manager/latest/cert-manager.yaml" ``` +**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `url`. + Similarly, it is possible to override the default version installed by clusterctl by configuring: ```yaml @@ -103,6 +107,7 @@ You may want to migrate to a user-managed cert-manager further down the line, af ```bash kubectl get all -A --selector=clusterctl.cluster.x-k8s.io/core=cert-manager ``` + If you want to manage and install your own cert-manager, you'll need to remove this label from all API resources. - ## Avoiding GitHub rate limiting Follow [this](./overview.md#avoiding-github-rate-limiting) @@ -186,6 +190,7 @@ run, ```bash clusterctl init --infrastructure aws:v0.5.0 -v5 ``` + ```bash ... Using Override="infrastructure-components.yaml" Provider="infrastructure-aws" Version="v0.5.0" @@ -200,6 +205,8 @@ directory in the clusterctl config file as overridesFolder: /Users/foobar/workspace/dev-releases ``` +**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `overridesFolder`. + ## Image overrides