Skip to content

Commit cb66ba8

Browse files
authored
Fix: legacy backend state reading and GC (#338)
1 parent 8620dbb commit cb66ba8

File tree

17 files changed

+633
-279
lines changed

17 files changed

+633
-279
lines changed

.github/workflows/e2e-test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ jobs:
6161
make configuration
6262
env:
6363
TERRAFORM_BACKEND_NAMESPACE: terraform
64+
- name: dump controller logs
65+
if: ${{ always() }}
66+
run: kubectl logs deploy/terraform-controller -n terraform
6467

6568
- name: Upload coverage report
6669
uses: codecov/codecov-action@v2

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ custom: custom-credentials custom-provider
267267

268268

269269
configuration:
270-
go test -coverprofile=e2e-coverage1.xml -v ./e2e/... -count=1
270+
go test -coverprofile=e2e-coverage1.xml -v $(go list ./e2e/...|grep -v controllernamespace) -count=1
271+
go test -v ./e2e/controllernamespace/...
271272

272273
e2e-setup: install-chart alibaba
273274

controllers/configuration/backend/backend.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ var backendInitFuncMap = map[string]backendInitFunc{
5656
}
5757

5858
// ParseConfigurationBackend parses backend Conf from the v1beta2.Configuration
59-
func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient client.Client, credentials map[string]string) (Backend, error) {
59+
func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient client.Client, credentials map[string]string, controllerNSSpecified bool) (Backend, error) {
6060
backend := configuration.Spec.Backend
6161

6262
var (
@@ -68,7 +68,7 @@ func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient c
6868
switch {
6969
case backend == nil || (backend.Inline == "" && backend.BackendType == ""):
7070
// use the default k8s backend
71-
return handleDefaultBackend(configuration, k8sClient)
71+
return handleDefaultBackend(configuration, k8sClient, controllerNSSpecified)
7272

7373
case backend.Inline != "" && backend.BackendType != "":
7474
return nil, errors.New("it's not allowed to set `spec.backend.inline` and `spec.backend.backendType` at the same time")
@@ -79,6 +79,8 @@ func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient c
7979

8080
case backend.BackendType != "":
8181
// In this case, use the explicit custom backend
82+
// we don't change backend secret suffix to UID of configuration here.
83+
// If backend specified, it's user's responsibility to set the right secret suffix, to avoid conflict.
8284
backendType, backendConf, err = handleExplicitBackend(backend)
8385
}
8486
if err != nil {
@@ -92,7 +94,7 @@ func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient c
9294
return initFunc(k8sClient, backendConf, credentials)
9395
}
9496

95-
func handleDefaultBackend(configuration *v1beta2.Configuration, k8sClient client.Client) (Backend, error) {
97+
func handleDefaultBackend(configuration *v1beta2.Configuration, k8sClient client.Client, controllerNSSpecified bool) (Backend, error) {
9698
if configuration.Spec.Backend != nil {
9799
if configuration.Spec.Backend.SecretSuffix == "" {
98100
configuration.Spec.Backend.SecretSuffix = configuration.Name
@@ -104,7 +106,7 @@ func handleDefaultBackend(configuration *v1beta2.Configuration, k8sClient client
104106
InClusterConfig: true,
105107
}
106108
}
107-
return newDefaultK8SBackend(configuration.Spec.Backend.SecretSuffix, k8sClient, configuration.Namespace), nil
109+
return newDefaultK8SBackend(configuration, k8sClient, controllerNSSpecified), nil
108110
}
109111

110112
func handleInlineBackendHCL(hclCode string) (string, interface{}, error) {

controllers/configuration/backend/backend_test.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import (
1313

1414
func TestParseConfigurationBackend(t *testing.T) {
1515
type args struct {
16-
configuration *v1beta2.Configuration
17-
credentials map[string]string
16+
configuration *v1beta2.Configuration
17+
credentials map[string]string
18+
controllerNSSpecified bool
1819
}
1920
type want struct {
2021
backend Backend
@@ -299,17 +300,46 @@ terraform {
299300
errMsg: "it's not allowed to set `spec.backend.inline` and `spec.backend.backendType` at the same time",
300301
},
301302
},
303+
{
304+
name: "backend is nil, specify controller namespace, generate backend with legacy secret suffix",
305+
args: args{
306+
configuration: &v1beta2.Configuration{
307+
ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "ns", UID: "xxxx-xxxx"},
308+
Spec: v1beta2.ConfigurationSpec{
309+
Backend: nil,
310+
},
311+
},
312+
controllerNSSpecified: true,
313+
},
314+
want: want{
315+
backend: &K8SBackend{
316+
LegacySecretSuffix: "name",
317+
SecretNS: "ns",
318+
SecretSuffix: "xxxx-xxxx",
319+
Client: k8sClient,
320+
HCLCode: `
321+
terraform {
322+
backend "kubernetes" {
323+
secret_suffix = "xxxx-xxxx"
324+
in_cluster_config = true
325+
namespace = "ns"
326+
}
327+
}
328+
`,
329+
},
330+
},
331+
},
302332
}
303333

304334
for _, tc := range testcases {
305335
t.Run(tc.name, func(t *testing.T) {
306-
got, err := ParseConfigurationBackend(tc.args.configuration, k8sClient, tc.args.credentials)
336+
got, err := ParseConfigurationBackend(tc.args.configuration, k8sClient, tc.args.credentials, tc.args.controllerNSSpecified)
307337
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
308338
t.Errorf("ValidConfigurationObject() error = %v, wantErr %v", err, tc.want.errMsg)
309339
return
310340
}
311341
if !reflect.DeepEqual(tc.want.backend, got) {
312-
t.Errorf("got %#v, want %#v", got, tc.want.backend)
342+
t.Errorf("\ngot %#v,\nwant %#v", got, tc.want.backend)
313343
}
314344
})
315345
}

controllers/configuration/backend/kubernetes.go

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
"fmt"
2222
"os"
2323

24-
"github.com/oam-dev/terraform-controller/api/v1beta2"
25-
"github.com/oam-dev/terraform-controller/controllers/util"
2624
"github.com/pkg/errors"
2725
v1 "k8s.io/api/core/v1"
2826
"k8s.io/klog/v2"
2927
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
"github.com/oam-dev/terraform-controller/api/v1beta2"
30+
"github.com/oam-dev/terraform-controller/controllers/util"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3032
)
3133

3234
const (
@@ -47,19 +49,31 @@ type K8SBackend struct {
4749
SecretSuffix string
4850
// SecretNS is the namespace of the Terraform backend secret
4951
SecretNS string
52+
// LegacySecretSuffix is the same as SecretSuffix, but only used when `--controller-namespace` is specified
53+
LegacySecretSuffix string
5054
}
5155

52-
func newDefaultK8SBackend(suffix string, client client.Client, namespace string) *K8SBackend {
56+
func newDefaultK8SBackend(configuration *v1beta2.Configuration, client client.Client, controllerNSSpecified bool) *K8SBackend {
5357
ns := os.Getenv("TERRAFORM_BACKEND_NAMESPACE")
5458
if ns == "" {
55-
ns = namespace
59+
ns = configuration.GetNamespace()
60+
}
61+
62+
var (
63+
suffix = configuration.Spec.Backend.SecretSuffix
64+
legacySuffix string
65+
)
66+
if controllerNSSpecified {
67+
legacySuffix = suffix
68+
suffix = string(configuration.GetUID())
5669
}
5770
hcl := renderK8SBackendHCL(suffix, ns)
5871
return &K8SBackend{
59-
Client: client,
60-
HCLCode: hcl,
61-
SecretSuffix: suffix,
62-
SecretNS: ns,
72+
Client: client,
73+
HCLCode: hcl,
74+
SecretSuffix: suffix,
75+
SecretNS: ns,
76+
LegacySecretSuffix: legacySuffix,
6377
}
6478
}
6579

@@ -95,15 +109,23 @@ terraform {
95109
return fmt.Sprintf(fmtStr, suffix, ns)
96110
}
97111

98-
func (k K8SBackend) secretName() string {
112+
func (k *K8SBackend) secretName() string {
99113
return fmt.Sprintf(TFBackendSecret, terraformWorkspace, k.SecretSuffix)
100114
}
101115

116+
func (k *K8SBackend) legacySecretName() string {
117+
return fmt.Sprintf(TFBackendSecret, terraformWorkspace, k.LegacySecretSuffix)
118+
}
119+
102120
// GetTFStateJSON gets Terraform state json from the Terraform kubernetes backend
103121
func (k *K8SBackend) GetTFStateJSON(ctx context.Context) ([]byte, error) {
104122
var s = v1.Secret{}
105-
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.secretName(), Namespace: k.SecretNS}, &s); err != nil {
106-
return nil, errors.Wrap(err, "terraform state file backend secret is not generated")
123+
// Try to get legacy secret first, if it doesn't exist, try to get new secret
124+
err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &s)
125+
if err != nil {
126+
if err = k.Client.Get(ctx, client.ObjectKey{Name: k.secretName(), Namespace: k.SecretNS}, &s); err != nil {
127+
return nil, errors.Wrap(err, "terraform state file backend secret is not generated")
128+
}
107129
}
108130
tfStateData, ok := s.Data[TerraformStateNameInSecret]
109131
if !ok {
@@ -119,8 +141,15 @@ func (k *K8SBackend) GetTFStateJSON(ctx context.Context) ([]byte, error) {
119141

120142
// CleanUp will delete the Terraform kubernetes backend secret when deleting the configuration object
121143
func (k *K8SBackend) CleanUp(ctx context.Context) error {
122-
klog.InfoS("Deleting the secret which stores Kubernetes backend", "Name", k.secretName())
144+
klog.InfoS("Deleting the legacy secret which stores Kubernetes backend", "Name", k.legacySecretName())
123145
var kubernetesBackendSecret v1.Secret
146+
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &kubernetesBackendSecret); err == nil {
147+
if err := k.Client.Delete(ctx, &kubernetesBackendSecret); err != nil {
148+
return err
149+
}
150+
}
151+
152+
klog.InfoS("Deleting the secret which stores Kubernetes backend", "Name", k.secretName())
124153
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.secretName(), Namespace: k.SecretNS}, &kubernetesBackendSecret); err == nil {
125154
if err := k.Client.Delete(ctx, &kubernetesBackendSecret); err != nil {
126155
return err
@@ -131,8 +160,40 @@ func (k *K8SBackend) CleanUp(ctx context.Context) error {
131160

132161
// HCL returns the backend hcl code string
133162
func (k *K8SBackend) HCL() string {
163+
if k.LegacySecretSuffix != "" {
164+
err := k.migrateLegacySecret()
165+
if err != nil {
166+
klog.ErrorS(err, "Failed to migrate legacy secret")
167+
}
168+
}
169+
134170
if k.HCLCode == "" {
135171
k.HCLCode = renderK8SBackendHCL(k.SecretSuffix, k.SecretNS)
136172
}
137173
return k.HCLCode
138174
}
175+
176+
// migrateLegacySecret will migrate the legacy secret to the new secret if the legacy secret exists
177+
// This is needed when the --controller-namespace is specified and restart the controller
178+
func (k *K8SBackend) migrateLegacySecret() error {
179+
ctx := context.TODO()
180+
s := v1.Secret{}
181+
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &s); err == nil {
182+
klog.InfoS("Migrating legacy secret to new secret", "LegacyName", k.legacySecretName(), "NewName", k.secretName(), "Namespace", k.SecretNS)
183+
newSecret := v1.Secret{
184+
ObjectMeta: metav1.ObjectMeta{
185+
Name: k.secretName(),
186+
Namespace: k.SecretNS,
187+
},
188+
Data: s.Data,
189+
}
190+
err = k.Client.Create(ctx, &newSecret)
191+
if err != nil {
192+
return errors.Wrapf(err, "Fail to create new secret, Name: %s, Namespace: %s", k.secretName(), k.SecretNS)
193+
} else if err = k.Client.Delete(ctx, &s); err != nil {
194+
// Only delete the legacy secret if the new secret is successfully created
195+
return errors.Wrapf(err, "Fail to delete legacy secret, Name: %s, Namespace: %s", k.legacySecretName(), k.SecretNS)
196+
}
197+
}
198+
return nil
199+
}

0 commit comments

Comments
 (0)