Skip to content

Commit f91b032

Browse files
committed
fix: use bootstrap data secret names
Fixes #38 Fixes: * bootstrap provider now uses proper passed in bootstrap data secret name instead of hardcoding it in the code * try to remove finalizer early, so that talosconfig can be deleted even if the owner is gone Tests: * existing tests refactored for readability * added test for full cluster config * added test for custom cluster settings * added test for config patches * talosVersion field is now passed to trigger proper generation * addressed test stability by waiting properly for namespace teardown Signed-off-by: Andrey Smirnov <[email protected]>
1 parent 6bff239 commit f91b032

File tree

9 files changed

+373
-144
lines changed

9 files changed

+373
-144
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ COPY --from=generate-build /src/api /api
5454

5555
FROM build AS integration-test-build
5656
ENV CGO_ENABLED 1
57-
ARG GO_LDFLAGS="-linkmode=external -extldflags '-static'"
57+
ARG TALOS_VERSION
58+
ARG GO_LDFLAGS="-linkmode=external -extldflags '-static' -X github.com/talos-systems/cluster-api-bootstrap-provider-talos/internal/integration.TalosVersion=${TALOS_VERSION}"
5859
RUN --mount=type=cache,target=/.cache go test -race -ldflags "${GO_LDFLAGS}" -coverpkg=./... -v -c ./internal/integration
5960

6061
FROM scratch AS integration-test

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ COMMON_ARGS += --build-arg=PKGS=$(PKGS)
3030
COMMON_ARGS += --build-arg=TOOLS=$(TOOLS)
3131
COMMON_ARGS += --build-arg=CONTROLLER_GEN_VERSION=$(CONTROLLER_GEN_VERSION)
3232
COMMON_ARGS += --build-arg=CONVERSION_GEN_VERSION=$(CONVERSION_GEN_VERSION)
33+
COMMON_ARGS += --build-arg=TALOS_VERSION=$(TALOS_VERSION)
3334

3435
all: manifests container
3536

controllers/secrets.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package controllers
66

77
import (
88
"context"
9+
"fmt"
910

1011
"github.com/talos-systems/crypto/x509"
1112
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
@@ -112,14 +113,18 @@ func (r *TalosConfigReconciler) writeBootstrapData(ctx context.Context, scope *T
112113
// Create ca secret only if it doesn't already exist
113114
ownerName := scope.ConfigOwner.GetName()
114115

116+
if scope.ConfigOwner.DataSecretName() == nil {
117+
return fmt.Errorf("config owner data secret name is nil")
118+
}
119+
115120
r.Log.Info("handling bootstrap data for ", "owner", ownerName)
116121

117-
_, err := r.fetchSecret(ctx, scope.Config, ownerName+"-bootstrap-data")
122+
_, err := r.fetchSecret(ctx, scope.Config, *scope.ConfigOwner.DataSecretName())
118123
if k8serrors.IsNotFound(err) {
119124
certSecret := &corev1.Secret{
120125
ObjectMeta: metav1.ObjectMeta{
121126
Namespace: scope.Config.Namespace,
122-
Name: ownerName + "-bootstrap-data",
127+
Name: *scope.ConfigOwner.DataSecretName(),
123128
Labels: map[string]string{
124129
capiv1.ClusterLabelName: scope.Cluster.Name,
125130
},

controllers/talosconfig_controller.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
apierrors "k8s.io/apimachinery/pkg/api/errors"
2626
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2727
"k8s.io/apimachinery/pkg/runtime"
28-
"k8s.io/utils/pointer"
2928
capiv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
3029
bsutil "sigs.k8s.io/cluster-api/bootstrap/util"
3130
expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha3"
@@ -145,25 +144,6 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
145144
return ctrl.Result{}, err
146145
}
147146

148-
// Look up the resource that owns this talosconfig if there is one
149-
owner, err := bsutil.GetConfigOwner(ctx, r.Client, config)
150-
if err != nil {
151-
log.Error(err, "could not get owner resource")
152-
return ctrl.Result{}, err
153-
}
154-
if owner == nil {
155-
log.Info("Waiting for OwnerRef on the talosconfig")
156-
return ctrl.Result{}, errors.New("no owner ref")
157-
}
158-
log = log.WithName(fmt.Sprintf("owner-name=%s", owner.GetName()))
159-
160-
// Lookup the cluster the machine is associated with
161-
cluster, err := util.GetClusterByName(ctx, r.Client, owner.GetNamespace(), owner.ClusterName())
162-
if err != nil {
163-
log.Error(err, "could not get cluster by machine metadata")
164-
return ctrl.Result{}, err
165-
}
166-
167147
// Initialize the patch helper
168148
patchHelper, err := patch.NewHelper(config, r.Client)
169149
if err != nil {
@@ -187,6 +167,25 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
187167
return r.reconcileDelete(ctx, config)
188168
}
189169

170+
// Look up the resource that owns this talosconfig if there is one
171+
owner, err := bsutil.GetConfigOwner(ctx, r.Client, config)
172+
if err != nil {
173+
log.Error(err, "could not get owner resource")
174+
return ctrl.Result{}, err
175+
}
176+
if owner == nil {
177+
log.Info("Waiting for OwnerRef on the talosconfig")
178+
return ctrl.Result{}, errors.New("no owner ref")
179+
}
180+
log = log.WithName(fmt.Sprintf("owner-name=%s", owner.GetName()))
181+
182+
// Lookup the cluster the machine is associated with
183+
cluster, err := util.GetClusterByName(ctx, r.Client, owner.GetNamespace(), owner.ClusterName())
184+
if err != nil {
185+
log.Error(err, "could not get cluster by machine metadata")
186+
return ctrl.Result{}, err
187+
}
188+
190189
// bail super early if it's already ready
191190
if config.Status.Ready {
192191
log.Info("ignoring an already ready config")
@@ -275,7 +274,7 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
275274
return ctrl.Result{}, err
276275
}
277276

278-
config.Status.DataSecretName = pointer.StringPtr(tcScope.ConfigOwner.GetName() + "-bootstrap-data")
277+
config.Status.DataSecretName = tcScope.ConfigOwner.DataSecretName()
279278
config.Status.TalosConfig = retData.TalosConfig
280279
config.Status.Ready = true
281280

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package integration
6+
7+
import (
8+
"context"
9+
"testing"
10+
11+
"github.com/AlekSi/pointer"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
bootstrapv1alpha3 "github.com/talos-systems/cluster-api-bootstrap-provider-talos/api/v1alpha3"
15+
talosclientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
16+
machineconfig "github.com/talos-systems/talos/pkg/machinery/config"
17+
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
18+
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
19+
"gopkg.in/yaml.v2"
20+
corev1 "k8s.io/api/core/v1"
21+
"k8s.io/apimachinery/pkg/types"
22+
capiv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
23+
"sigs.k8s.io/controller-runtime/pkg/client"
24+
)
25+
26+
// assertClientConfig checks that Talos client config as part of TalosConfig resource is valid.
27+
func assertClientConfig(t *testing.T, talosConfig *bootstrapv1alpha3.TalosConfig) {
28+
t.Helper()
29+
30+
clientConfig, err := talosclientconfig.FromString(talosConfig.Status.TalosConfig)
31+
require.NoError(t, err)
32+
validateClientConfig(t, clientConfig)
33+
}
34+
35+
// assertMachineConfiguration checks that generated bootstrap data is a valid Talos machine configuration.
36+
func assertMachineConfiguration(ctx context.Context, t *testing.T, c client.Client, talosConfig *bootstrapv1alpha3.TalosConfig) machineconfig.Provider {
37+
var bootstrapDataSecret corev1.Secret
38+
39+
key := types.NamespacedName{
40+
Namespace: talosConfig.Namespace,
41+
Name: pointer.GetString(talosConfig.Status.DataSecretName),
42+
}
43+
require.NoError(t, c.Get(ctx, key, &bootstrapDataSecret))
44+
45+
assert.Len(t, bootstrapDataSecret.Data, 1)
46+
47+
provider, err := configloader.NewFromBytes(bootstrapDataSecret.Data["value"])
48+
require.NoError(t, err)
49+
50+
_, err = provider.Validate(runtimeMode{false}, machineconfig.WithStrict())
51+
assert.NoError(t, err)
52+
53+
return provider
54+
}
55+
56+
// assertClusterCA checks that generated cluster CA secret matches secrets in machine config (machine config from controlplane node required).
57+
func assertClusterCA(ctx context.Context, t *testing.T, c client.Client, cluster *capiv1.Cluster, provider machineconfig.Provider) {
58+
var caSecret corev1.Secret
59+
60+
key := types.NamespacedName{
61+
Namespace: cluster.Namespace,
62+
Name: cluster.Name + "-ca",
63+
}
64+
require.NoError(t, c.Get(ctx, key, &caSecret))
65+
66+
assert.Len(t, caSecret.Data, 2)
67+
assert.Equal(t, corev1.SecretTypeOpaque, caSecret.Type) // TODO why not SecretTypeTLS?
68+
69+
assert.NotEmpty(t, caSecret.Data[corev1.TLSCertKey])
70+
assert.NotEmpty(t, caSecret.Data[corev1.TLSPrivateKeyKey])
71+
72+
assert.Equal(t, provider.Cluster().CA().Crt, caSecret.Data[corev1.TLSCertKey])
73+
assert.Equal(t, provider.Cluster().CA().Key, caSecret.Data[corev1.TLSPrivateKeyKey])
74+
}
75+
76+
// assertControllerSecret checks that persisted controller secret (used to bootstrap more machines with same secrets) maches generated controlplane config.
77+
func assertControllerSecret(ctx context.Context, t *testing.T, c client.Client, cluster *capiv1.Cluster, provider machineconfig.Provider) {
78+
var talosSecret corev1.Secret
79+
key := types.NamespacedName{
80+
Namespace: cluster.Namespace,
81+
Name: cluster.Name + "-talos",
82+
}
83+
require.NoError(t, c.Get(ctx, key, &talosSecret))
84+
85+
assert.Len(t, talosSecret.Data, 3)
86+
assert.NotEmpty(t, talosSecret.Data["certs"])
87+
assert.NotEmpty(t, talosSecret.Data["kubeSecrets"])
88+
assert.NotEmpty(t, talosSecret.Data["trustdInfo"])
89+
90+
// cross-checks
91+
secretsBundle := generate.NewSecretsBundleFromConfig(generate.NewClock(), provider)
92+
93+
var certs generate.Certs
94+
require.NoError(t, yaml.Unmarshal(talosSecret.Data["certs"], &certs))
95+
assert.NotEmpty(t, certs.Admin)
96+
certs.Admin = nil
97+
assert.Equal(t, secretsBundle.Certs, &certs)
98+
99+
var kubeSecrets generate.Secrets
100+
require.NoError(t, yaml.Unmarshal(talosSecret.Data["kubeSecrets"], &kubeSecrets))
101+
assert.Equal(t, secretsBundle.Secrets, &kubeSecrets)
102+
103+
var trustdInfo generate.TrustdInfo
104+
require.NoError(t, yaml.Unmarshal(talosSecret.Data["trustdInfo"], &trustdInfo))
105+
assert.Equal(t, secretsBundle.TrustdInfo, &trustdInfo)
106+
}
107+
108+
// assertSameMachineConfigSecrets checks that control plane configs share same set of secrets.
109+
func assertSameMachineConfigSecrets(ctx context.Context, t *testing.T, c client.Client, talosConfigs ...*bootstrapv1alpha3.TalosConfig) {
110+
providers := make([]machineconfig.Provider, len(talosConfigs))
111+
112+
for i := range providers {
113+
providers[i] = assertMachineConfiguration(ctx, t, c, talosConfigs[i])
114+
}
115+
116+
secretsBundle0 := generate.NewSecretsBundleFromConfig(generate.NewClock(), providers[0])
117+
118+
for _, provider := range providers[1:] {
119+
assert.Equal(t, secretsBundle0, generate.NewSecretsBundleFromConfig(generate.NewClock(), provider))
120+
}
121+
}
122+
123+
// assertCompatibleMachineConfigs checks that configs share same set of core secrets so that nodes can build a cluster.
124+
func assertCompatibleMachineConfigs(ctx context.Context, t *testing.T, c client.Client, talosConfigs ...*bootstrapv1alpha3.TalosConfig) {
125+
providers := make([]machineconfig.Provider, len(talosConfigs))
126+
127+
for i := range providers {
128+
providers[i] = assertMachineConfiguration(ctx, t, c, talosConfigs[i])
129+
}
130+
131+
checks := []func(p machineconfig.Provider) interface{}{
132+
func(p machineconfig.Provider) interface{} { return p.Machine().Security().Token() },
133+
// TODO: uncomment me with Talos 0.12: func(p machineconfig.Provider) interface{} { return p.Cluster().ID() },
134+
// TODO: uncomment me with Talos 0.12: func(p machineconfig.Provider) interface{} { return p.Cluster().Secret() },
135+
func(p machineconfig.Provider) interface{} { return p.Cluster().Endpoint().String() },
136+
func(p machineconfig.Provider) interface{} { return p.Cluster().Token().ID() },
137+
func(p machineconfig.Provider) interface{} { return p.Cluster().Token().Secret() },
138+
func(p machineconfig.Provider) interface{} { return p.Cluster().CA().Crt },
139+
}
140+
141+
for _, check := range checks {
142+
value0 := check(providers[0])
143+
144+
for _, provider := range providers[1:] {
145+
assert.Equal(t, value0, check(provider))
146+
}
147+
}
148+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package integration
6+
7+
// TalosVersion is set by the build process.
8+
var TalosVersion string

internal/integration/helpers_test.go

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ package integration
66

77
import (
88
"context"
9-
"crypto/x509"
10-
"encoding/pem"
119
"flag"
1210
"fmt"
1311
"os"
@@ -16,6 +14,7 @@ import (
1614
"testing"
1715
"time"
1816

17+
"github.com/AlekSi/pointer"
1918
"github.com/stretchr/testify/assert"
2019
"github.com/stretchr/testify/require"
2120
talosclient "github.com/talos-systems/talos/pkg/machinery/client"
@@ -25,6 +24,7 @@ import (
2524
"k8s.io/apimachinery/pkg/types"
2625
"k8s.io/client-go/kubernetes/scheme"
2726
capiv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
27+
bsutil "sigs.k8s.io/cluster-api/bootstrap/util"
2828
"sigs.k8s.io/controller-runtime/pkg/client"
2929
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3030

@@ -61,22 +61,27 @@ func generateName(t *testing.T, kind string) string {
6161
}
6262

6363
// createCluster creates a Cluster with "ready" infrastructure.
64-
func createCluster(ctx context.Context, t *testing.T, c client.Client, namespaceName string) *capiv1.Cluster {
64+
func createCluster(ctx context.Context, t *testing.T, c client.Client, namespaceName string, spec *capiv1.ClusterSpec) *capiv1.Cluster {
6565
t.Helper()
6666

6767
clusterName := generateName(t, "cluster")
68-
cluster := &capiv1.Cluster{
69-
ObjectMeta: metav1.ObjectMeta{
70-
Namespace: namespaceName,
71-
Name: clusterName,
72-
},
73-
Spec: capiv1.ClusterSpec{
68+
69+
if spec == nil {
70+
spec = &capiv1.ClusterSpec{
7471
ClusterNetwork: &capiv1.ClusterNetwork{},
7572
ControlPlaneEndpoint: capiv1.APIEndpoint{
7673
Host: clusterName + ".host",
7774
Port: 12345,
7875
},
76+
}
77+
}
78+
79+
cluster := &capiv1.Cluster{
80+
ObjectMeta: metav1.ObjectMeta{
81+
Namespace: namespaceName,
82+
Name: clusterName,
7983
},
84+
Spec: *spec,
8085
}
8186

8287
require.NoError(t, c.Create(ctx, cluster), "can't create a cluster")
@@ -92,7 +97,7 @@ func createMachine(ctx context.Context, t *testing.T, c client.Client, cluster *
9297
t.Helper()
9398

9499
machineName := generateName(t, "machine")
95-
dataSecretName := "my-test-secret"
100+
dataSecretName := fmt.Sprintf("%s-bootstrap-data", machineName)
96101
machine := &capiv1.Machine{
97102
ObjectMeta: metav1.ObjectMeta{
98103
Namespace: cluster.Namespace,
@@ -101,7 +106,7 @@ func createMachine(ctx context.Context, t *testing.T, c client.Client, cluster *
101106
Spec: capiv1.MachineSpec{
102107
ClusterName: cluster.Name,
103108
Bootstrap: capiv1.Bootstrap{
104-
DataSecretName: &dataSecretName, // TODO
109+
DataSecretName: pointer.ToString(dataSecretName),
105110
},
106111
},
107112
}
@@ -114,7 +119,7 @@ func createMachine(ctx context.Context, t *testing.T, c client.Client, cluster *
114119
}
115120

116121
// createTalosConfig creates a TalosConfig owned by the Machine.
117-
func createTalosConfig(ctx context.Context, t *testing.T, c client.Client, machine *capiv1.Machine) *bootstrapv1alpha3.TalosConfig {
122+
func createTalosConfig(ctx context.Context, t *testing.T, c client.Client, machine *capiv1.Machine, spec bootstrapv1alpha3.TalosConfigSpec) *bootstrapv1alpha3.TalosConfig {
118123
t.Helper()
119124

120125
talosConfigName := generateName(t, "talosconfig")
@@ -123,9 +128,7 @@ func createTalosConfig(ctx context.Context, t *testing.T, c client.Client, machi
123128
Namespace: machine.Namespace,
124129
Name: talosConfigName,
125130
},
126-
Spec: bootstrapv1alpha3.TalosConfigSpec{
127-
GenerateType: "init",
128-
},
131+
Spec: spec,
129132
}
130133

131134
require.NoError(t, controllerutil.SetOwnerReference(machine, talosConfig, scheme.Scheme))
@@ -163,16 +166,12 @@ func waitForReady(ctx context.Context, t *testing.T, c client.Client, talosConfi
163166
t.Log("Waiting ...")
164167
sleepCtx(ctx, 3*time.Second)
165168
}
166-
}
167169

168-
// parsePEMCertificate parses PEM-encoded x509 certificate.
169-
func parsePEMCertificate(t *testing.T, b []byte) *x509.Certificate {
170-
block, rest := pem.Decode(b)
171-
assert.Empty(t, rest)
172-
require.NotEmpty(t, block.Bytes)
173-
cert, err := x509.ParseCertificate(block.Bytes)
170+
owner, err := bsutil.GetConfigOwner(ctx, c, talosConfig)
174171
require.NoError(t, err)
175-
return cert
172+
173+
assert.Equal(t, pointer.GetString(owner.DataSecretName()), pointer.GetString(talosConfig.Status.DataSecretName), "%+v", talosConfig)
174+
176175
}
177176

178177
// validateClientConfig validates talosctl configuration.

0 commit comments

Comments
 (0)