Skip to content

Commit 19410e1

Browse files
committed
clusterctl: support envsubst in clusterctl config
Signed-off-by: Stefan Büringer [email protected]
1 parent c6cbae8 commit 19410e1

File tree

7 files changed

+100
-1
lines changed

7 files changed

+100
-1
lines changed

cmd/clusterctl/client/config/cert_manager_client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package config
1818

1919
import (
20+
"os"
2021
"time"
2122

23+
"github.com/drone/envsubst/v2"
2224
"github.com/pkg/errors"
2325
)
2426

@@ -77,6 +79,12 @@ func (p *certManagerClient) Get() (CertManager, error) {
7779
if userCertManager.URL != "" {
7880
url = userCertManager.URL
7981
}
82+
83+
url, err := envsubst.Eval(url, os.Getenv)
84+
if err != nil {
85+
return nil, errors.Wrapf(err, "unable to evaluate url: %q", url)
86+
}
87+
8088
if userCertManager.Version != "" {
8189
version = userCertManager.Version
8290
}

cmd/clusterctl/client/config/cert_manager_client_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package config
1818

1919
import (
20+
"os"
2021
"testing"
2122

2223
. "github.com/onsi/gomega"
@@ -31,6 +32,7 @@ func TestCertManagerGet(t *testing.T) {
3132
tests := []struct {
3233
name string
3334
fields fields
35+
envVars map[string]string
3436
want CertManager
3537
wantErr bool
3638
}{
@@ -50,6 +52,17 @@ func TestCertManagerGet(t *testing.T) {
5052
want: NewCertManager("foo-url", "vX.Y.Z", CertManagerDefaultTimeout.String()),
5153
wantErr: false,
5254
},
55+
{
56+
name: "return custom url with evaluated env vars if defined",
57+
fields: fields{
58+
reader: test.NewFakeReader().WithCertManager("${TEST_REPO_PATH}/foo-url", "vX.Y.Z", ""),
59+
},
60+
envVars: map[string]string{
61+
"TEST_REPO_PATH": "/tmp/test",
62+
},
63+
want: NewCertManager("/tmp/test/foo-url", "vX.Y.Z", CertManagerDefaultTimeout.String()),
64+
wantErr: false,
65+
},
5366
{
5467
name: "return timeout if defined",
5568
fields: fields{
@@ -63,6 +76,14 @@ func TestCertManagerGet(t *testing.T) {
6376
t.Run(tt.name, func(t *testing.T) {
6477
g := NewWithT(t)
6578

79+
for k, v := range tt.envVars {
80+
g.Expect(os.Setenv(k, v)).To(Succeed())
81+
}
82+
defer func() {
83+
for k := range tt.envVars {
84+
g.Expect(os.Unsetenv(k)).To(Succeed())
85+
}
86+
}()
6687
p := &certManagerClient{
6788
reader: tt.fields.reader,
6889
}

cmd/clusterctl/client/config/providers_client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package config
1818

1919
import (
2020
"net/url"
21+
"os"
2122
"sort"
2223
"strings"
2324

25+
"github.com/drone/envsubst/v2"
2426
"github.com/pkg/errors"
2527
"k8s.io/apimachinery/pkg/util/validation"
2628

@@ -295,6 +297,12 @@ func (p *providersClient) List() ([]Provider, error) {
295297
}
296298

297299
for _, u := range userDefinedProviders {
300+
var err error
301+
u.URL, err = envsubst.Eval(u.URL, os.Getenv)
302+
if err != nil {
303+
return nil, errors.Wrapf(err, "unable to evaluate url: %q", u.URL)
304+
}
305+
298306
provider := NewProvider(u.Name, u.URL, u.Type)
299307
if err := validateProvider(provider); err != nil {
300308
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())

cmd/clusterctl/client/config/providers_client_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package config
1818

1919
import (
2020
"fmt"
21+
"os"
2122
"sort"
2223
"testing"
2324

@@ -50,6 +51,7 @@ func Test_providers_List(t *testing.T) {
5051
tests := []struct {
5152
name string
5253
fields fields
54+
envVars map[string]string
5355
want []Provider
5456
wantErr bool
5557
}{
@@ -75,6 +77,23 @@ func Test_providers_List(t *testing.T) {
7577
want: defaultsAndZZZ,
7678
wantErr: false,
7779
},
80+
{
81+
name: "Returns user defined provider configurations with evaluated env vars",
82+
fields: fields{
83+
configGetter: test.NewFakeReader().
84+
WithVar(
85+
ProvidersConfigKey,
86+
"- name: \"zzz\"\n"+
87+
" url: \"${TEST_REPO_PATH}/infrastructure-components.yaml\"\n"+
88+
" type: \"InfrastructureProvider\"\n",
89+
),
90+
},
91+
envVars: map[string]string{
92+
"TEST_REPO_PATH": "https://zzz",
93+
},
94+
want: defaultsAndZZZ,
95+
wantErr: false,
96+
},
7897
{
7998
name: "User defined provider configurations override defaults",
8099
fields: fields{
@@ -120,6 +139,14 @@ func Test_providers_List(t *testing.T) {
120139
t.Run(tt.name, func(t *testing.T) {
121140
g := NewWithT(t)
122141

142+
for k, v := range tt.envVars {
143+
g.Expect(os.Setenv(k, v)).To(Succeed())
144+
}
145+
defer func() {
146+
for k := range tt.envVars {
147+
g.Expect(os.Unsetenv(k)).To(Succeed())
148+
}
149+
}()
123150
p := &providersClient{
124151
reader: tt.fields.configGetter,
125152
}

cmd/clusterctl/client/repository/overrides.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@ limitations under the License.
1717
package repository
1818

1919
import (
20+
"fmt"
2021
"os"
2122
"path/filepath"
2223
"strings"
2324

25+
"github.com/drone/envsubst/v2"
2426
"github.com/pkg/errors"
2527
"k8s.io/client-go/util/homedir"
2628

2729
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
30+
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
2831
)
2932

3033
const (
@@ -69,6 +72,13 @@ func (o *overrides) Path() string {
6972
f, err := o.configVariablesClient.Get(overrideFolderKey)
7073
if err == nil && strings.TrimSpace(f) != "" {
7174
basepath = f
75+
76+
evaluatedBasePath, err := envsubst.Eval(basepath, os.Getenv)
77+
if err != nil {
78+
logf.Log.Info(fmt.Sprintf("⚠️overridesFolder %q could not be evaluated: %v", basepath, err))
79+
} else {
80+
basepath = evaluatedBasePath
81+
}
7282
}
7383

7484
return filepath.Join(

cmd/clusterctl/client/repository/overrides_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestOverrides(t *testing.T) {
3333
tests := []struct {
3434
name string
3535
configVarClient config.VariablesClient
36+
envVars map[string]string
3637
expectedPath string
3738
}{
3839
{
@@ -55,11 +56,28 @@ func TestOverrides(t *testing.T) {
5556
configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, "/Users/foobar/workspace/releases"),
5657
expectedPath: "/Users/foobar/workspace/releases/infrastructure-myinfra/v1.0.1/infra-comp.yaml",
5758
},
59+
{
60+
name: "uses overrides folder from the config variables with evaluated env vars",
61+
configVarClient: test.NewFakeVariableClient().WithVar(overrideFolderKey, "${TEST_REPO_PATH}/releases"),
62+
envVars: map[string]string{
63+
"TEST_REPO_PATH": "/tmp/test",
64+
},
65+
expectedPath: "/tmp/test/releases/infrastructure-myinfra/v1.0.1/infra-comp.yaml",
66+
},
5867
}
5968

6069
for _, tt := range tests {
6170
t.Run(tt.name, func(t *testing.T) {
6271
g := NewWithT(t)
72+
73+
for k, v := range tt.envVars {
74+
g.Expect(os.Setenv(k, v)).To(Succeed())
75+
}
76+
defer func() {
77+
for k := range tt.envVars {
78+
g.Expect(os.Unsetenv(k)).To(Succeed())
79+
}
80+
}()
6381
provider := config.NewProvider("myinfra", "", clusterctlv1.InfrastructureProviderType)
6482
override := newOverride(&newOverrideInput{
6583
configVariablesClient: tt.configVarClient,

docs/book/src/clusterctl/configuration.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ providers:
4141
4242
See [provider contract](provider-contract.md) for instructions about how to set up a provider repository.
4343
44+
**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `url`.
45+
4446
## Variables
4547

4648
When installing a provider `clusterctl` reads a YAML file that is published in the provider repository. While executing
@@ -73,6 +75,8 @@ cert-manager:
7375
url: "/Users/foo/.cluster-api/dev-repository/cert-manager/latest/cert-manager.yaml"
7476
```
7577

78+
**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `url`.
79+
7680
Similarly, it is possible to override the default version installed by clusterctl by configuring:
7781

7882
```yaml
@@ -103,6 +107,7 @@ You may want to migrate to a user-managed cert-manager further down the line, af
103107
```bash
104108
kubectl get all -A --selector=clusterctl.cluster.x-k8s.io/core=cert-manager
105109
```
110+
106111
If you want to manage and install your own cert-manager, you'll need to remove this label from all API resources.
107112

108113
<aside class="note warning">
@@ -113,7 +118,6 @@ Cluster API has a direct dependency on cert-manager. It's possible you could enc
113118

114119
</aside>
115120

116-
117121
## Avoiding GitHub rate limiting
118122

119123
Follow [this](./overview.md#avoiding-github-rate-limiting)
@@ -186,6 +190,7 @@ run,
186190
```bash
187191
clusterctl init --infrastructure aws:v0.5.0 -v5
188192
```
193+
189194
```bash
190195
...
191196
Using Override="infrastructure-components.yaml" Provider="infrastructure-aws" Version="v0.5.0"
@@ -200,6 +205,8 @@ directory in the clusterctl config file as
200205
overridesFolder: /Users/foobar/workspace/dev-releases
201206
```
202207
208+
**Note**: It is possible to use the `${HOME}` and `${CLUSTERCTL_REPOSITORY_PATH}` environment variables in `overridesFolder`.
209+
203210
## Image overrides
204211

205212
<aside class="note warning">

0 commit comments

Comments
 (0)