Skip to content

Commit

Permalink
Merge pull request #17 from zzxwill/master
Browse files Browse the repository at this point in the history
Fix terraform tfstate issue
  • Loading branch information
zzxwill authored Apr 27, 2021
2 parents 41f1569 + 6e90b7a commit 1d003e8
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 76 deletions.
2 changes: 1 addition & 1 deletion api/v1beta1/configuration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type Property struct {
type Backend struct {
// SecretSuffix used when creating secrets. Secrets will be named in the format: tfstate-{workspace}-{secretSuffix}
SecretSuffix string `json:"secretSuffix,omitempty"`
// InClusterConfig Used to authenticate to the cluster from inside a pod.
// InClusterConfig Used to authenticate to the cluster from inside a pod. Only `true` is allowed
InClusterConfig bool `json:"inClusterConfig,omitempty"`
}

Expand Down
5 changes: 2 additions & 3 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
apiVersion: v1
name: terraform-controller
version: 0.1.2
name: terraform-controller-chart
version: 0.1.4
description: A Terraform controller
home: https://github.com/oam-dev/terraform-controller
appVersion: v0.1.2
2 changes: 1 addition & 1 deletion chart/crds/terraform.core.oam.dev_configurations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ spec:
properties:
inClusterConfig:
description: InClusterConfig Used to authenticate to the cluster
from inside a pod.
from inside a pod. Only `true` is allowed
type: boolean
secretSuffix:
description: 'SecretSuffix used when creating secrets. Secrets will
Expand Down
9 changes: 9 additions & 0 deletions chart/templates/tf_controller_role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,12 @@ rules:
- "update"
- "get"
- "delete"
- apiGroups:
- "coordination.k8s.io"
resources:
- "leases"
verbs:
- "create"
- "update"
- "get"
- "delete"
140 changes: 70 additions & 70 deletions controllers/configuration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,75 @@ func (r *ConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
return ctrl.Result{}, nil
}

func terraformApply(ctx context.Context, k8sClient client.Client, namespace string, configuration v1beta1.Configuration, applyJobName, tfInputConfigMapName string) error {
var gotJob batchv1.Job

err := k8sClient.Get(ctx, client.ObjectKey{Name: applyJobName, Namespace: namespace}, &gotJob)
if err == nil {
if gotJob.Status.Succeeded == *pointer.Int32Ptr(1) {
outputs, err := getTFOutputs(ctx, k8sClient, configuration)
if err != nil {
return err
}
configuration.Status.State = "provisioned"
configuration.Status.Outputs = outputs
if err := k8sClient.Update(ctx, &configuration); err != nil {
return err
}
}
return nil
}

if kerrors.IsNotFound(err) {
configurationType, inputConfiguration, err := util.ValidConfiguration(&configuration)
if err != nil {
return err
}
data := prepareTFInputConfigurationData(configurationType, inputConfiguration)
if err = createOrUpdateConfigMap(ctx, k8sClient, namespace, tfInputConfigMapName, data); err != nil {
return err
}

if err := assembleAndTriggerJob(ctx, k8sClient, namespace, configuration.Name, &configuration,
tfInputConfigMapName, TerraformApply); err != nil {
return err
}
return nil
}
return err
}

func terraformDestroy(ctx context.Context, k8sClient client.Client, namespace, configurationName, jobName, tfInputConfigMapsName string) error {
var destroyJob batchv1.Job

if err := k8sClient.Get(ctx, client.ObjectKey{Name: jobName, Namespace: namespace}, &destroyJob); err != nil {
if kerrors.IsNotFound(err) {
if err = assembleAndTriggerJob(ctx, k8sClient, namespace, configurationName, nil, tfInputConfigMapsName,
TerraformDestroy); err != nil {
return err
}
}
}
// When the deletion Job process succeeded, clean up work is starting.
if destroyJob.Status.Succeeded == *pointer.Int32Ptr(1) {
// 1. delete Terraform input Configuration ConfigMap
if err := deleteConfigMap(ctx, k8sClient, namespace, tfInputConfigMapsName); err != nil {
return err
}

// 2. we don't manually delete Terraform state file Secret, as Terraform Kubernetes backend tends to keep the secret

// 3. delete Job itself
if err := k8sClient.Delete(ctx, &destroyJob); err != nil {
return err
}

// TODO(zzxwill) 4. Somehow, destroy pod isn't automatically deleted after its OwnerReference Job is deleted
return nil
}
return errors.New("configuration deletion isn't completed.")
}

func assembleAndTriggerJob(ctx context.Context, k8sClient client.Client, namespace, name string, configuration *v1beta1.Configuration, tfInputConfigMapsName string, executionType TerraformExecutionType) error {
var job *batchv1.Job
jobName := name + "-" + string(executionType)
Expand Down Expand Up @@ -296,7 +365,7 @@ func getTFOutputs(ctx context.Context, k8sClient client.Client, configuration v1
// Secrets will be named in the format: tfstate-{workspace}-{secret_suffix}
k8sBackendSecretName := fmt.Sprintf("tfstate-%s-%s", TerraformWorkspace, backendSecretSuffix)
if err := k8sClient.Get(ctx, client.ObjectKey{Name: k8sBackendSecretName, Namespace: configuration.Namespace}, &s); err != nil {
return nil, err
return nil, errors.Wrap(err, "terraform state file backend secret is not generated")
}
tfStateData, ok := s.Data[TerraformStateNameInSecret]
if !ok {
Expand Down Expand Up @@ -441,75 +510,6 @@ func createOrUpdateConfigMap(ctx context.Context, k8sClient client.Client, names
return errors.Wrap(err, "failed to update TF configuration ConfigMap")
}

func terraformDestroy(ctx context.Context, k8sClient client.Client, namespace, configurationName, jobName, tfInputConfigMapsName string) error {
var destroyJob batchv1.Job

if err := k8sClient.Get(ctx, client.ObjectKey{Name: jobName, Namespace: namespace}, &destroyJob); err != nil {
if kerrors.IsNotFound(err) {
if err = assembleAndTriggerJob(ctx, k8sClient, namespace, configurationName, nil, tfInputConfigMapsName,
TerraformDestroy); err != nil {
return err
}
}
}
// When the deletion Job process succeeded, clean up work is starting.
if destroyJob.Status.Succeeded == *pointer.Int32Ptr(1) {
// 1. delete Terraform input Configuration ConfigMap
if err := deleteConfigMap(ctx, k8sClient, namespace, tfInputConfigMapsName); err != nil {
return err
}

// 2. we don't manually delete Terraform state file Secret, as Terraform Kubernetes backend tends to keep the secret

// 3. delete Job itself
if err := k8sClient.Delete(ctx, &destroyJob); err != nil {
return err
}

// TODO(zzxwill) 4. Somehow, destroy pod isn't automatically deleted after its OwnerReference Job is deleted
return nil
}
return errors.New("configuration deletion isn't completed.")
}

func terraformApply(ctx context.Context, k8sClient client.Client, namespace string, configuration v1beta1.Configuration, applyJobName, tfInputConfigMapName string) error {
var gotJob batchv1.Job

err := k8sClient.Get(ctx, client.ObjectKey{Name: applyJobName, Namespace: namespace}, &gotJob)
if err == nil {
if gotJob.Status.Succeeded == *pointer.Int32Ptr(1) {
outputs, err := getTFOutputs(ctx, k8sClient, configuration)
if err != nil {
return err
}
configuration.Status.State = "provisioned"
configuration.Status.Outputs = outputs
if err := k8sClient.Update(ctx, &configuration); err != nil {
return err
}
return nil
}
}

if kerrors.IsNotFound(err) {
configurationType, inputConfiguration, err := util.ValidConfiguration(configuration)
if err != nil {
return err
}
data := prepareTFInputConfigurationData(configurationType, inputConfiguration)
if err = createOrUpdateConfigMap(ctx, k8sClient, namespace, tfInputConfigMapName, data); err != nil {
return err
}

if err := assembleAndTriggerJob(ctx, k8sClient, namespace, configuration.Name, &configuration,
tfInputConfigMapName, TerraformApply); err != nil {
return err
}
return nil
}
return err
}

func prepareTFInputConfigurationData(configurationType util.ConfigurationType, inputConfiguration string) map[string]string {
var dataName string
switch configurationType {
Expand Down
10 changes: 9 additions & 1 deletion controllers/util/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const (
ConfigurationHCL ConfigurationType = "HCL"
)

func ValidConfiguration(configuration v1beta1.Configuration) (ConfigurationType, string, error) {
func ValidConfiguration(configuration *v1beta1.Configuration) (ConfigurationType, string, error) {
json := configuration.Spec.JSON
hcl := configuration.Spec.HCL
switch {
Expand All @@ -24,6 +24,14 @@ func ValidConfiguration(configuration v1beta1.Configuration) (ConfigurationType,
case json != "":
return ConfigurationJSON, json, nil
case hcl != "":
if configuration.Spec.Backend != nil {
if configuration.Spec.Backend.SecretSuffix == "" {
configuration.Spec.Backend.SecretSuffix = configuration.Name
}
configuration.Spec.Backend.InClusterConfig = true
} else {
configuration.Spec.Backend = &v1beta1.Backend{SecretSuffix: configuration.Name, InClusterConfig: true}
}
backendTF, err := renderTemplate(configuration.Spec.Backend)
if err != nil {
return "", "", errors.Wrap(err, "failed to prepare Terraform backend configuration")
Expand Down

0 comments on commit 1d003e8

Please sign in to comment.