Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add finalizer to the user secret and configmaps #65

Merged
merged 1 commit into from
Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ci/pipeline_definitions
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ terraformer:
PROVIDER: packet
steps:
verify:
image: 'eu.gcr.io/gardener-project/3rd/golang:1.15.3'
image: 'eu.gcr.io/gardener-project/3rd/golang:1.15.5'
jobs:
head-update:
traits:
Expand Down
2 changes: 1 addition & 1 deletion .test-defs/pod-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ spec:
ACCESS_KEY_ID_FILE=<(echo $ACCESS_KEY_ID)
SECRET_ACCESS_KEY_FILE=<(echo $SECRET_ACCESS_KEY)
IMAGE_TAG=$IMAGE_TAG
image: eu.gcr.io/gardener-project/3rd/golang:1.15.3
image: eu.gcr.io/gardener-project/3rd/golang:1.15.5
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ARG PROVIDER=all
RUN make install PROVIDER=$PROVIDER

############# terraformer
FROM eu.gcr.io/gardener-project/3rd/alpine:3.12.1 AS terraformer
FROM eu.gcr.io/gardener-project/3rd/alpine:3.12.3 AS terraformer

RUN apk add --update curl tzdata

Expand Down
118 changes: 116 additions & 2 deletions pkg/terraformer/terraformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,33 @@ import (
"time"

"github.com/go-logr/logr"
"github.com/hashicorp/go-multierror"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
runtimelog "sigs.k8s.io/controller-runtime/pkg/log"

"github.com/gardener/terraformer/pkg/utils"
)

const (
// maxPatchRetries define the maximum number of attempts to patch a resource in case of conflict
maxPatchRetries = 2
)

var (
// TerraformBinary is the name of the terraform binary, it allows to overwrite it for testing purposes
TerraformBinary = "terraform"

// allow redirecting output in tests
Stdout, Stderr io.Writer = os.Stdout, os.Stderr
// Stdout alias to os.Stdout allowing output redirection in tests
Stdout io.Writer = os.Stdout

// Stderr alias to os.Stderr allowing output redirection in tests
Stderr io.Writer = os.Stderr

// SignalNotify allows mocking signal.Notify in tests
SignalNotify = signal.Notify
Expand Down Expand Up @@ -128,6 +141,10 @@ func (t *Terraformer) execute(command Command) (rErr error) {
// stop file watcher and wait for it to be finished
defer shutdownFileWatcher()

if err := t.addFinalizer(ctx); err != nil {
return fmt.Errorf("error adding finalizers: %w", err)
}

// initialize terraform plugins
if err := t.executeTerraform(ctx, Init); err != nil {
return fmt.Errorf("error executing terraform %s: %w", Init, err)
Expand All @@ -144,6 +161,13 @@ func (t *Terraformer) execute(command Command) (rErr error) {
}
}

// after a successful execution of destroy command, remove the finalizers from the resources
if command == Destroy {
if err := t.removeFinalizer(ctx); err != nil {
return fmt.Errorf("error removing finalizers: %w", err)
}
}

return nil
}

Expand Down Expand Up @@ -205,3 +229,93 @@ func (t *Terraformer) executeTerraform(ctx context.Context, command Command) err
log.Info("terraform process finished successfully", "command", command)
return nil
}

func (t *Terraformer) addFinalizer(ctx context.Context) error {
logger := t.stepLogger("add-finalizer")
return t.updateObjects(ctx, logger, controllerutil.AddFinalizer)

}

func (t *Terraformer) removeFinalizer(ctx context.Context) error {
logger := t.stepLogger("remove-finalizer")
return t.updateObjects(ctx, logger, controllerutil.RemoveFinalizer)
}

func (t *Terraformer) terraformObjects() []controllerutil.Object {
return []controllerutil.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: t.config.Namespace,
Name: t.config.VariablesSecretName,
},
},
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: t.config.Namespace,
Name: t.config.ConfigurationConfigMapName,
},
},
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: t.config.Namespace,
Name: t.config.StateConfigMapName,
},
},
vpnachev marked this conversation as resolved.
Show resolved Hide resolved
}
}

func (t *Terraformer) updateObjects(ctx context.Context, log logr.Logger, patchObj func(controllerutil.Object, string)) error {
allErrors := &multierror.Error{
ErrorFormat: utils.NewErrorFormatFuncWithPrefix("failed to update object finalizer"),
}

log.Info("updating finalizers for terraform resources")
for _, obj := range t.terraformObjects() {
if err := t.updateObjectFinalizers(ctx, log, obj, patchObj); err != nil {
allErrors = multierror.Append(allErrors, err)
}
}

err := allErrors.ErrorOrNil()
if err != nil {
log.Error(err, "failed to updated finalizers for all terraform resources")
} else {
log.Info("successfully updated finalizers for terraform resources")
}
return err
}

func (t *Terraformer) updateObjectFinalizers(ctx context.Context, log logr.Logger, obj controllerutil.Object, patchObj func(controllerutil.Object, string)) error {
key, err := client.ObjectKeyFromObject(obj)
if err != nil {
log.Error(err, "failed to construct key", "object", obj)
return err
}

for i := 0; i < maxPatchRetries; i++ {
err = t.client.Get(ctx, key, obj)
if err != nil {
if apierrors.IsNotFound(err) {
log.V(1).Info("create empty object", "key", key)
patchObj(obj, TerraformerFinalizer)
return t.client.Create(ctx, obj)
rfranzke marked this conversation as resolved.
Show resolved Hide resolved
}
log.Error(err, "failed to get object", "key", key)
return err
}

old := obj.DeepCopyObject()
patchObj(obj, TerraformerFinalizer)
err = t.client.Patch(ctx, obj, client.MergeFromWithOptions(old, client.MergeFromWithOptimisticLock{}))
if !apierrors.IsConflict(err) {
break
}
}

if client.IgnoreNotFound(err) != nil {
log.Error(err, "failed to update object in the store", "key", key)
return err
}

return nil
}
15 changes: 15 additions & 0 deletions pkg/terraformer/terraformer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,26 @@ var _ = Describe("Terraformer", func() {
Expect(tf.Run(terraformer.Apply)).To(Succeed())
Eventually(logBuffer).Should(gbytes.Say("some terraform output"))
Eventually(logBuffer).Should(gbytes.Say("terraform process finished successfully"))
testObjs.Refresh()
Expect(testObjs.ConfigurationConfigMap.Finalizers).To(ContainElement(terraformer.TerraformerFinalizer))
Expect(testObjs.StateConfigMap.Finalizers).To(ContainElement(terraformer.TerraformerFinalizer))
Expect(testObjs.VariablesSecret.Finalizers).To(ContainElement(terraformer.TerraformerFinalizer))
})
It("should run Destroy successfully", func() {
Expect(tf.Run(terraformer.Destroy)).To(Succeed())
Eventually(logBuffer).Should(gbytes.Say("some terraform output"))
Eventually(logBuffer).Should(gbytes.Say("terraform process finished successfully"))
testObjs.Refresh()
Expect(testObjs.ConfigurationConfigMap.Finalizers).ToNot(ContainElement(terraformer.TerraformerFinalizer))
Expect(testObjs.StateConfigMap.Finalizers).ToNot(ContainElement(terraformer.TerraformerFinalizer))
Expect(testObjs.VariablesSecret.Finalizers).ToNot(ContainElement(terraformer.TerraformerFinalizer))
})
It("should create non-existing objects successfully on Apply", func() {
Expect(testClient.Delete(ctx, testObjs.StateConfigMap)).To(Succeed())
Expect(testClient.Get(ctx, testutils.ObjectKeyFromObject(testObjs.StateConfigMap), testObjs.StateConfigMap)).ToNot(Succeed())
Expect(tf.Run(terraformer.Apply)).To(Succeed())
Expect(testClient.Get(ctx, testutils.ObjectKeyFromObject(testObjs.StateConfigMap), testObjs.StateConfigMap)).To(Succeed())
Expect(testObjs.StateConfigMap.Finalizers).To(ContainElement(terraformer.TerraformerFinalizer))
})
It("should run Validate successfully", func() {
Expect(tf.Run(terraformer.Validate)).To(Succeed())
Expand Down
2 changes: 2 additions & 0 deletions pkg/terraformer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const (
Validate Command = "validate"
// Plan is the terraform `plan` command.
Plan Command = "plan"
// TerraformerFinalizer is the finalizer used by the terraformer on the terraform configmaps and secrets
TerraformerFinalizer = "gardener.cloud/terraformer"
)

// SupportedCommands contains the set of supported terraform commands, that can be run as `terraformer <command>`.
Expand Down
34 changes: 34 additions & 0 deletions test/utils/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/gardener/terraformer/pkg/terraformer"
)

// TestObjects models a set of API objects used in tests
Expand Down Expand Up @@ -40,6 +43,7 @@ func PrepareTestObjects(ctx context.Context, c client.Client, namespacePrefix st

var handle CleanupActionHandle
handle = AddCleanupAction(func() {
o.CleanupTestObjects(ctx)
Expect(client.IgnoreNotFound(o.client.Delete(ctx, ns))).To(Succeed())
RemoveCleanupAction(handle)
})
Expand Down Expand Up @@ -85,6 +89,36 @@ func PrepareTestObjects(ctx context.Context, c client.Client, namespacePrefix st
return o
}

// CleanupTestObjects take care to remove the finalizers of the secret and configmaps
func (o *TestObjects) CleanupTestObjects(ctx context.Context) {
configurationConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: o.ConfigurationConfigMap.Name, Namespace: o.Namespace},
}
Expect(client.IgnoreNotFound(o.client.Get(ctx, ObjectKeyFromObject(configurationConfigMap), configurationConfigMap))).To(Succeed())
copyConfigurationConfigMap := configurationConfigMap.DeepCopy()
controllerutil.RemoveFinalizer(copyConfigurationConfigMap, terraformer.TerraformerFinalizer)
Expect(client.IgnoreNotFound(o.client.Patch(ctx, copyConfigurationConfigMap, client.MergeFrom(configurationConfigMap)))).To(Succeed())
Expect(client.IgnoreNotFound(o.client.Delete(ctx, copyConfigurationConfigMap))).To(Succeed())

stateConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: o.StateConfigMap.Name, Namespace: o.Namespace},
}
Expect(client.IgnoreNotFound(o.client.Get(ctx, ObjectKeyFromObject(stateConfigMap), stateConfigMap))).To(Succeed())
copyStateConfigMap := stateConfigMap.DeepCopy()
controllerutil.RemoveFinalizer(copyStateConfigMap, terraformer.TerraformerFinalizer)
Expect(client.IgnoreNotFound(o.client.Patch(ctx, copyStateConfigMap, client.MergeFrom(stateConfigMap)))).To(Succeed())
Expect(client.IgnoreNotFound(o.client.Delete(ctx, copyStateConfigMap))).To(Succeed())

variablesSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: o.VariablesSecret.Name, Namespace: o.Namespace},
}
Expect(client.IgnoreNotFound(o.client.Get(ctx, ObjectKeyFromObject(variablesSecret), variablesSecret))).To(Succeed())
copyVariablesSecret := variablesSecret.DeepCopy()
controllerutil.RemoveFinalizer(copyVariablesSecret, terraformer.TerraformerFinalizer)
Expect(client.IgnoreNotFound(o.client.Patch(ctx, copyVariablesSecret, client.MergeFrom(variablesSecret)))).To(Succeed())
Expect(client.IgnoreNotFound(o.client.Delete(ctx, copyVariablesSecret))).To(Succeed())
}

// Refresh retrieves a fresh copy of the objects from the API server, so that tests can make assertions on them.
func (o *TestObjects) Refresh() {
Expect(o.client.Get(o.ctx, ObjectKeyFromObject(o.ConfigurationConfigMap), o.ConfigurationConfigMap)).To(Succeed())
Expand Down