Skip to content

Commit 36b02c5

Browse files
fix: compute a safe label value for terraform plan resources (flux-iac#1714)
1 parent 0a398fe commit 36b02c5

File tree

9 files changed

+90
-57
lines changed

9 files changed

+90
-57
lines changed

api/plan/plan.go

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,41 @@ import (
1212
)
1313

1414
const (
15-
TFPlanName = "tfplan"
16-
SavedPlanSecretAnnotation = "savedPlan"
15+
// Kubernetes Label names associated with Terraform Plans
16+
TFPlanNameLabel = "infra.contrib.fluxcd.io/plan-name"
17+
TFPlanWorkspaceLabel = "infra.contrib.fluxcd.io/plan-workspace"
18+
19+
// Kubernetes Annotation names associated with Terraform Plans
20+
TFPlanFullNameAnnotation = "infra.contrib.fluxcd.io/plan-full-name"
21+
TFPlanChunkAnnotation = "infra.contrib.fluxcd.io/plan-chunk"
22+
TFPlanHashAnnotation = "infra.contrib.fluxcd.io/plan-hash"
23+
TFPlanSavedAnnotation = "savedPlan"
24+
25+
TFPlanName = "tfplan"
1726

1827
// resourceDataMaxSizeBytes defines the maximum size of data
1928
// that can be stored in a Kubernetes Secret or ConfigMap
2029
resourceDataMaxSizeBytes = 1 * 1024 * 1024 // 1MB
2130
)
2231

32+
// SafeLabelValue returns a string that is safe to use as a Kubernetes label value.
33+
func SafeLabelValue(value string) string {
34+
// Values that are equal to or less than 63 characters are already good
35+
if len(value) <= 63 {
36+
return value
37+
}
38+
39+
// Create haash
40+
checksum := sha256.Sum256([]byte(value))
41+
42+
// Build a prefix to append to end of truncated value
43+
checksumPrefix := fmt.Sprintf("-%x", checksum[:8])
44+
45+
prefix := value[:63-len(checksumPrefix)]
46+
47+
return prefix + checksumPrefix
48+
}
49+
2350
type Plan struct {
2451
name string
2552
namespace string
@@ -57,20 +84,20 @@ func NewFromSecrets(name string, namespace string, uuid string, secrets []v1.Sec
5784

5885
// Grab the chunk index from the secret annotation
5986
chunkIndex := 0
60-
if idxStr, ok := secret.Annotations["infra.contrib.fluxcd.io/plan-chunk"]; ok && idxStr != "" {
87+
if idxStr, ok := secret.Annotations[TFPlanChunkAnnotation]; ok && idxStr != "" {
6188
var err error
6289
chunkIndex, err = strconv.Atoi(idxStr)
6390
if err != nil {
6491
return nil, fmt.Errorf("invalid chunk index annotation found on secret %s: %s", secret.Name, err)
6592
}
6693
}
6794

68-
workspaceName, ok = secret.Labels["infra.contrib.fluxcd.io/plan-workspace"]
95+
workspaceName, ok = secret.Labels[TFPlanWorkspaceLabel]
6996
if !ok {
7097
return nil, fmt.Errorf("missing plan workspace label on secret %s", secret.Name)
7198
}
7299

73-
planID, ok = secret.Annotations[SavedPlanSecretAnnotation]
100+
planID, ok = secret.Annotations[TFPlanSavedAnnotation]
74101
if !ok {
75102
return nil, fmt.Errorf("missing plan ID annotation on secret %s", secret.Name)
76103
}
@@ -120,20 +147,20 @@ func NewFromConfigMaps(name string, namespace string, uuid string, configmaps []
120147

121148
// Grab the chunk index from the configmap annotation
122149
chunkIndex := 0
123-
if idxStr, ok := configmap.Annotations["infra.contrib.fluxcd.io/plan-chunk"]; ok && idxStr != "" {
150+
if idxStr, ok := configmap.Annotations[TFPlanChunkAnnotation]; ok && idxStr != "" {
124151
var err error
125152
chunkIndex, err = strconv.Atoi(idxStr)
126153
if err != nil {
127154
return nil, fmt.Errorf("invalid chunk index annotation found on configmap %s: %s", configmap.Name, err)
128155
}
129156
}
130157

131-
workspaceName, ok = configmap.Labels["infra.contrib.fluxcd.io/plan-workspace"]
158+
workspaceName, ok = configmap.Labels[TFPlanWorkspaceLabel]
132159
if !ok {
133160
return nil, fmt.Errorf("missing plan workspace label on configmap %s", configmap.Name)
134161
}
135162

136-
planID, ok = configmap.Annotations[SavedPlanSecretAnnotation]
163+
planID, ok = configmap.Annotations[TFPlanSavedAnnotation]
137164
if !ok {
138165
return nil, fmt.Errorf("missing plan ID annotation on secret %s", configmap.Name)
139166
}
@@ -183,13 +210,14 @@ func (p *Plan) ToSecret(suffix string) ([]*v1.Secret, error) {
183210
Name: secretIdentifier,
184211
Namespace: p.namespace,
185212
Annotations: map[string]string{
186-
"encoding": "gzip",
187-
SavedPlanSecretAnnotation: p.planID,
188-
"infra.contrib.fluxcd.io/plan-hash": fmt.Sprintf("%x", sha256.Sum256(encoded)),
213+
"encoding": "gzip",
214+
TFPlanFullNameAnnotation: p.name + suffix,
215+
TFPlanSavedAnnotation: p.planID,
216+
TFPlanHashAnnotation: fmt.Sprintf("%x", sha256.Sum256(p.bytes)),
189217
},
190218
Labels: map[string]string{
191-
"infra.contrib.fluxcd.io/plan-name": p.name + suffix,
192-
"infra.contrib.fluxcd.io/plan-workspace": p.workspace,
219+
TFPlanNameLabel: SafeLabelValue(p.name + suffix),
220+
TFPlanWorkspaceLabel: p.workspace,
193221
},
194222
OwnerReferences: []metav1.OwnerReference{
195223
{
@@ -223,14 +251,15 @@ func (p *Plan) ToSecret(suffix string) ([]*v1.Secret, error) {
223251
Name: fmt.Sprintf("%s-%d", secretIdentifier, chunk),
224252
Namespace: p.namespace,
225253
Annotations: map[string]string{
226-
"encoding": "gzip",
227-
SavedPlanSecretAnnotation: p.planID,
228-
"infra.contrib.fluxcd.io/plan-chunk": fmt.Sprintf("%d", chunk),
229-
"infra.contrib.fluxcd.io/plan-hash": fmt.Sprintf("%x", sha256.Sum256(planData)),
254+
"encoding": "gzip",
255+
TFPlanFullNameAnnotation: p.name + suffix,
256+
TFPlanSavedAnnotation: p.planID,
257+
TFPlanChunkAnnotation: fmt.Sprintf("%d", chunk),
258+
TFPlanHashAnnotation: fmt.Sprintf("%x", sha256.Sum256(planData)),
230259
},
231260
Labels: map[string]string{
232-
"infra.contrib.fluxcd.io/plan-name": p.name + suffix,
233-
"infra.contrib.fluxcd.io/plan-workspace": p.workspace,
261+
TFPlanNameLabel: SafeLabelValue(p.name + suffix),
262+
TFPlanWorkspaceLabel: p.workspace,
234263
},
235264
OwnerReferences: []metav1.OwnerReference{
236265
{
@@ -268,12 +297,13 @@ func (p *Plan) ToConfigMap(suffix string) ([]*v1.ConfigMap, error) {
268297
Name: configMapIdentifier,
269298
Namespace: p.namespace,
270299
Annotations: map[string]string{
271-
SavedPlanSecretAnnotation: p.planID,
272-
"infra.contrib.fluxcd.io/plan-hash": fmt.Sprintf("%x", sha256.Sum256(p.bytes)),
300+
TFPlanFullNameAnnotation: p.name + suffix,
301+
TFPlanSavedAnnotation: p.planID,
302+
TFPlanHashAnnotation: fmt.Sprintf("%x", sha256.Sum256(p.bytes)),
273303
},
274304
Labels: map[string]string{
275-
"infra.contrib.fluxcd.io/plan-name": p.name + suffix,
276-
"infra.contrib.fluxcd.io/plan-workspace": p.workspace,
305+
TFPlanNameLabel: SafeLabelValue(p.name + suffix),
306+
TFPlanWorkspaceLabel: p.workspace,
277307
},
278308
OwnerReferences: []metav1.OwnerReference{
279309
{
@@ -307,13 +337,14 @@ func (p *Plan) ToConfigMap(suffix string) ([]*v1.ConfigMap, error) {
307337
Name: fmt.Sprintf("%s-%d", configMapIdentifier, chunk),
308338
Namespace: p.namespace,
309339
Annotations: map[string]string{
310-
SavedPlanSecretAnnotation: p.planID,
311-
"infra.contrib.fluxcd.io/plan-chunk": fmt.Sprintf("%d", chunk),
312-
"infra.contrib.fluxcd.io/plan-hash": fmt.Sprintf("%x", sha256.Sum256([]byte(planData))),
340+
TFPlanFullNameAnnotation: p.name + suffix,
341+
TFPlanSavedAnnotation: p.planID,
342+
TFPlanChunkAnnotation: fmt.Sprintf("%d", chunk),
343+
TFPlanHashAnnotation: fmt.Sprintf("%x", sha256.Sum256([]byte(planData))),
313344
},
314345
Labels: map[string]string{
315-
"infra.contrib.fluxcd.io/plan-name": p.name + suffix,
316-
"infra.contrib.fluxcd.io/plan-workspace": p.workspace,
346+
TFPlanNameLabel: SafeLabelValue(p.name + suffix),
347+
TFPlanWorkspaceLabel: p.workspace,
317348
},
318349
OwnerReferences: []metav1.OwnerReference{
319350
{

internal/informer/branch-planner/informer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ func (i *Informer) getPlan(ctx context.Context, obj *infrav1.Terraform) (string,
284284
configMaps := &v1.ConfigMapList{}
285285

286286
if err := i.client.List(ctx, configMaps, client.InNamespace(obj.GetNamespace()), client.MatchingLabels{
287-
"infra.contrib.fluxcd.io/plan-name": obj.GetName(),
288-
"infra.contrib.fluxcd.io/plan-workspace": obj.WorkspaceName(),
287+
plan.TFPlanNameLabel: plan.SafeLabelValue(obj.GetName()),
288+
plan.TFPlanWorkspaceLabel: obj.WorkspaceName(),
289289
}); err != nil {
290290
return "", fmt.Errorf("unable to list plan configmaps: %s", err)
291291
}

internal/informer/branch-planner/informer_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
gom "github.com/onsi/gomega"
1111
"sigs.k8s.io/controller-runtime/pkg/client"
1212

13+
"github.com/flux-iac/tofu-controller/api/plan"
1314
infrav1 "github.com/flux-iac/tofu-controller/api/v1alpha2"
1415
"github.com/flux-iac/tofu-controller/internal/config"
1516
"github.com/flux-iac/tofu-controller/internal/git/provider/providerfakes"
@@ -66,11 +67,11 @@ func TestInformer(t *testing.T) {
6667
Name: "tfplan-default-helloworld",
6768
Namespace: ns.Name,
6869
Labels: map[string]string{
69-
"infra.contrib.fluxcd.io/plan-name": "helloworld",
70-
"infra.contrib.fluxcd.io/plan-workspace": "default",
70+
plan.TFPlanNameLabel: plan.SafeLabelValue("helloworld"),
71+
plan.TFPlanWorkspaceLabel: "default",
7172
},
7273
Annotations: map[string]string{
73-
"savedPlan": "plan-main-1",
74+
plan.TFPlanSavedAnnotation: "plan-main-1",
7475
},
7576
},
7677
Data: map[string]string{

runner/server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
ctrl "sigs.k8s.io/controller-runtime"
2626
"sigs.k8s.io/controller-runtime/pkg/client"
2727

28+
"github.com/flux-iac/tofu-controller/api/plan"
2829
infrav1 "github.com/flux-iac/tofu-controller/api/v1alpha2"
2930
"github.com/flux-iac/tofu-controller/utils"
3031
)
@@ -527,8 +528,8 @@ func (r *TerraformRunnerServer) FinalizeSecrets(ctx context.Context, req *Finali
527528
secrets := &v1.SecretList{}
528529

529530
if err := r.Client.List(ctx, secrets, client.InNamespace(req.Namespace), client.MatchingLabels{
530-
"infra.contrib.fluxcd.io/plan-name": req.Name,
531-
"infra.contrib.fluxcd.io/plan-workspace": req.Workspace,
531+
plan.TFPlanNameLabel: plan.SafeLabelValue(req.Name),
532+
plan.TFPlanWorkspaceLabel: req.Workspace,
532533
}); err != nil {
533534
log.Error(err, "unable to list existing plan secrets")
534535
return nil, err

runner/server_load_tfplan.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func loadTFPlan(
4444

4545
// List relevant secrets
4646
if err := kubeClient.List(ctx, secrets, client.InNamespace(req.Namespace), client.MatchingLabels{
47-
"infra.contrib.fluxcd.io/plan-name": req.Name,
48-
"infra.contrib.fluxcd.io/plan-workspace": terraform.WorkspaceName(),
47+
plan.TFPlanNameLabel: plan.SafeLabelValue(req.Name),
48+
plan.TFPlanWorkspaceLabel: terraform.WorkspaceName(),
4949
}); err != nil {
5050
log.Error(err, "unable to list existing plan secrets")
5151
return nil, err
@@ -65,8 +65,8 @@ func loadTFPlan(
6565
continue
6666
}
6767

68-
if s.Annotations[plan.SavedPlanSecretAnnotation] != pendingPlanId {
69-
return nil, fmt.Errorf("pending plan %s does not match secret %s (%s)", pendingPlanId, s.Name, s.Annotations[plan.SavedPlanSecretAnnotation])
68+
if s.Annotations[plan.TFPlanSavedAnnotation] != pendingPlanId {
69+
return nil, fmt.Errorf("pending plan %s does not match secret %s (%s)", pendingPlanId, s.Name, s.Annotations[plan.TFPlanSavedAnnotation])
7070
}
7171
}
7272

runner/server_load_tfplan_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ func TestLoadTFPlanWithForceTrue(t *testing.T) {
4848
Namespace: req.Namespace,
4949
Annotations: map[string]string{SavedPlanSecretAnnotation: "plan-not-1"},
5050
Labels: map[string]string{
51-
"infra.contrib.fluxcd.io/plan-name": req.Name,
52-
"infra.contrib.fluxcd.io/plan-workspace": terraform.WorkspaceName(),
51+
plan.TFPlanNameLabel: plan.SafeLabelValue(req.Name),
52+
plan.TFPlanWorkspaceLabel: terraform.WorkspaceName(),
5353
},
5454
},
5555
Data: secretData,
@@ -105,8 +105,8 @@ func TestLoadTFPlanWithForceFalse(t *testing.T) {
105105
Namespace: req.Namespace,
106106
Annotations: map[string]string{SavedPlanSecretAnnotation: "plan-not-1"},
107107
Labels: map[string]string{
108-
"infra.contrib.fluxcd.io/plan-name": req.Name,
109-
"infra.contrib.fluxcd.io/plan-workspace": terraform.WorkspaceName(),
108+
plan.TFPlanNameLabel: plan.SafeLabelValue(req.Name),
109+
plan.TFPlanWorkspaceLabel: terraform.WorkspaceName(),
110110
},
111111
},
112112
Data: secretData,

runner/server_save_tfplan.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ func (r *TerraformRunnerServer) SaveTFPlan(ctx context.Context, req *SaveTFPlanR
9898
return &SaveTFPlanReply{Message: "ok"}, nil
9999
}
100100

101-
func (r *TerraformRunnerServer) writePlanAsSecret(ctx context.Context, name string, namespace string, log logr.Logger, planId string, plan *plan.Plan, suffix string, uuid string) error {
101+
func (r *TerraformRunnerServer) writePlanAsSecret(ctx context.Context, name string, namespace string, log logr.Logger, planId string, tfPlan *plan.Plan, suffix string, uuid string) error {
102102
existingSecrets := &v1.SecretList{}
103103

104104
// Try to get any secrets by using the plan labels
105105
// This covers "chunked" secrets as well as a single secret
106106
if err := r.Client.List(ctx, existingSecrets, client.InNamespace(namespace), client.MatchingLabels{
107-
"infra.contrib.fluxcd.io/plan-name": name + suffix,
108-
"infra.contrib.fluxcd.io/plan-workspace": r.terraform.WorkspaceName(),
107+
plan.TFPlanNameLabel: plan.SafeLabelValue(name + suffix),
108+
plan.TFPlanWorkspaceLabel: r.terraform.WorkspaceName(),
109109
}); err != nil {
110110
log.Error(err, "unable to list existing plan secrets")
111111
return err
@@ -129,7 +129,7 @@ func (r *TerraformRunnerServer) writePlanAsSecret(ctx context.Context, name stri
129129
}
130130
}
131131

132-
secrets, err := plan.ToSecret(suffix)
132+
secrets, err := tfPlan.ToSecret(suffix)
133133
if err != nil {
134134
log.Error(err, "unable to generate plan secrets", "planId", planId)
135135
return err
@@ -149,14 +149,14 @@ func (r *TerraformRunnerServer) writePlanAsSecret(ctx context.Context, name stri
149149
return nil
150150
}
151151

152-
func (r *TerraformRunnerServer) writePlanAsConfigMap(ctx context.Context, name string, namespace string, log logr.Logger, planId string, plan *plan.Plan, suffix string, uuid string) error {
152+
func (r *TerraformRunnerServer) writePlanAsConfigMap(ctx context.Context, name string, namespace string, log logr.Logger, planId string, tfPlan *plan.Plan, suffix string, uuid string) error {
153153
existingConfigMaps := &v1.ConfigMapList{}
154154

155155
// Try to get any ConfigMaps by using the plan labels
156156
// This covers "chunked" ConfigMaps as well as a single ConfigMap
157157
if err := r.Client.List(ctx, existingConfigMaps, client.InNamespace(namespace), client.MatchingLabels{
158-
"infra.contrib.fluxcd.io/plan-name": name + suffix,
159-
"infra.contrib.fluxcd.io/plan-workspace": r.terraform.WorkspaceName(),
158+
plan.TFPlanNameLabel: plan.SafeLabelValue(name + suffix),
159+
plan.TFPlanWorkspaceLabel: r.terraform.WorkspaceName(),
160160
}); err != nil {
161161
log.Error(err, "unable to list existing plan ConfigMaps")
162162
return err
@@ -180,7 +180,7 @@ func (r *TerraformRunnerServer) writePlanAsConfigMap(ctx context.Context, name s
180180
}
181181
}
182182

183-
configMaps, err := plan.ToConfigMap(suffix)
183+
configMaps, err := tfPlan.ToConfigMap(suffix)
184184
if err != nil {
185185
log.Error(err, "unable to generate plan ConfigMaps", "planId", planId)
186186
return err

tfctl/show_plan.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ func readPlanFromConfigmap(ctx context.Context, kubeClient client.Client, resour
7474

7575
// List relevant configmaps
7676
if err := kubeClient.List(ctx, configMaps, client.InNamespace(namespace), client.MatchingLabels{
77-
"infra.contrib.fluxcd.io/plan-name": resource,
78-
"infra.contrib.fluxcd.io/plan-workspace": workspace,
77+
plan.TFPlanNameLabel: plan.SafeLabelValue(resource),
78+
plan.TFPlanWorkspaceLabel: workspace,
7979
}); err != nil {
8080
return "", fmt.Errorf("unable to list existing plan configmaps: %s", err)
8181
}
@@ -98,8 +98,8 @@ func readPlanFromSecret(ctx context.Context, kubeClient client.Client, resource
9898

9999
// List relevant secrets
100100
if err := kubeClient.List(ctx, secrets, client.InNamespace(namespace), client.MatchingLabels{
101-
"infra.contrib.fluxcd.io/plan-name": resource,
102-
"infra.contrib.fluxcd.io/plan-workspace": workspace,
101+
plan.TFPlanNameLabel: plan.SafeLabelValue(resource),
102+
plan.TFPlanWorkspaceLabel: workspace,
103103
}); err != nil {
104104
return "", fmt.Errorf("unable to list existing plan secrets: %s", err)
105105
}

tfctl/show_plan_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ func TestShowPlan(t *testing.T) {
5858
Name: "tfplan-default-hello-world",
5959
Namespace: "default",
6060
Labels: map[string]string{
61-
"infra.contrib.fluxcd.io/plan-name": "hello-world",
62-
"infra.contrib.fluxcd.io/plan-workspace": "default",
61+
plan.TFPlanNameLabel: "hello-world",
62+
plan.TFPlanWorkspaceLabel: "default",
6363
},
6464
},
6565
Data: map[string][]byte{

0 commit comments

Comments
 (0)