Skip to content

Commit

Permalink
Merge pull request #268 from Nalum/issue-250
Browse files Browse the repository at this point in the history
Terraform Force Unlock
  • Loading branch information
Chanwit Kaewkasi authored Aug 19, 2022
2 parents 31d1688 + 6d3b118 commit 80be24d
Show file tree
Hide file tree
Showing 15 changed files with 1,833 additions and 309 deletions.
78 changes: 74 additions & 4 deletions api/v1alpha1/terraform_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ type TerraformSpec struct {
// EnableInventory enables the object to store resource entries as the inventory for external use.
// +optional
EnableInventory bool `json:"enableInventory,omitempty"`

// +optional
TFState *TFStateSpec `json:"tfstate,omitempty"`
}

type PlanStatus struct {
Expand Down Expand Up @@ -274,10 +277,10 @@ type BackendConfigSpec struct {
Disable bool `json:"disable"`

// +optional
SecretSuffix string `json:"secretSuffix"`
SecretSuffix string `json:"secretSuffix,omitempty"`

// +optional
InClusterConfig bool `json:"inClusterConfig"`
InClusterConfig bool `json:"inClusterConfig,omitempty"`

// +optional
CustomConfiguration string `json:"customConfiguration,omitempty"`
Expand All @@ -289,6 +292,47 @@ type BackendConfigSpec struct {
Labels map[string]string `json:"labels,omitempty"`
}

// TFStateSpec allows the user to set ForceUnlock
type TFStateSpec struct {
// ForceUnlock a Terraform state if it has become locked for any reason.
//
// This is an Enum and has the expected values of:
//
// - auto
// - yes
// - no
//
// WARNING: Only use `auto` in the cases where you are absolutely certain that
// no other system is using this state, you could otherwise end up in a bad place
// See https://www.terraform.io/language/state/locking#force-unlock for more
// information on the terraform state lock and force unlock.
//
// +optional
// +kubebuilder:validation:Enum:=yes;no;auto
// +kubebuilder:default:string=no
ForceUnlock ForceUnlockEnum `json:"forceUnlock,omitempty"`

// LockIdentifier holds the Identifier required by Terraform to unlock the state
// if it ever gets into a locked state.
//
// You'll need to put the Lock Identifier in here while setting ForceUnlock to
// either `true` or `auto`.
//
// Leave this empty to do nothing, set this to the value of the `Lock Info: ID: [value]`,
// e.g. `f2ab685b-f84d-ac0b-a125-378a22877e8d`, to force unlock the state.
//
// +optional
LockIdentifier string `json:"lockIdentifier,omitempty"`
}

type ForceUnlockEnum string

const (
ForceUnlockEnumAuto ForceUnlockEnum = "auto"
ForceUnlockEnumYes ForceUnlockEnum = "yes"
ForceUnlockEnumNo ForceUnlockEnum = "no"
)

const (
TerraformKind = "Terraform"
TerraformFinalizer = "finalizers.tf.contrib.fluxcd.io"
Expand All @@ -299,8 +343,7 @@ const (

// ArtifactFailedReason represents the fact that the
// source artifact download failed.
ArtifactFailedReason = "ArtifactFailed"

ArtifactFailedReason = "ArtifactFailed"
TFExecNewFailedReason = "TFExecNewFailed"
TFExecInitFailedReason = "TFExecInitFailed"
VarsGenerationFailedReason = "VarsGenerationFailed"
Expand All @@ -313,6 +356,7 @@ const (
OutputsWritingFailedReason = "OutputsWritingFailed"
HealthChecksFailedReason = "HealthChecksFailed"
TFExecApplySucceedReason = "TerraformAppliedSucceed"
TFExecLockHeldReason = "LockHeld"
)

// SetTerraformReadiness sets the ReadyCondition, ObservedGeneration, and LastAttemptedRevision, on the Terraform.
Expand Down Expand Up @@ -519,6 +563,32 @@ func TerraformHealthCheckSucceeded(terraform Terraform, message string) Terrafor
return terraform
}

// TerraformForceUnlock will set a new condition on the Terraform resource indicating
// that we are attempting to force unlock it.
func TerraformForceUnlock(terraform Terraform, message string) Terraform {
newCondition := metav1.Condition{
Type: "ForceUnlock",
Status: metav1.ConditionUnknown,
Reason: TFExecLockHeldReason,
Message: trimString(message, MaxConditionMessageLength),
}
apimeta.SetStatusCondition(terraform.GetStatusConditions(), newCondition)
return terraform
}

// TerraformLocked will set a new condition on the Terraform resource indicating
// that the resource has been locked.
func TerraformLocked(terraform Terraform, message string) Terraform {
newCondition := metav1.Condition{
Type: "StateLocked",
Status: metav1.ConditionUnknown,
Reason: TFExecLockHeldReason,
Message: trimString(message, MaxConditionMessageLength),
}
apimeta.SetStatusCondition(terraform.GetStatusConditions(), newCondition)
return terraform
}

// HasDrift returns true if drift has been detected since the last successful apply
func (in Terraform) HasDrift() bool {
for _, condition := range in.Status.Conditions {
Expand Down
21 changes: 21 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 27 additions & 2 deletions cmd/tfctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ func newRootCommand() *cobra.Command {
rootCmd.AddCommand(buildSuspendCmd(app))
rootCmd.AddCommand(buildResumeCmd(app))
rootCmd.AddCommand(buildGetGroup(app))
rootCmd.AddCommand(buildDeleteCommand(app))
rootCmd.AddCommand(buildDeleteCmd(app))
rootCmd.AddCommand(buildCreateCmd(app))
rootCmd.AddCommand(buildForceUnlockCmd(app))

return rootCmd
}
Expand Down Expand Up @@ -256,7 +257,7 @@ var deleteExamples = `
tfctl delete my-resource
`

func buildDeleteCommand(app *tfctl.CLI) *cobra.Command {
func buildDeleteCmd(app *tfctl.CLI) *cobra.Command {
cmd := &cobra.Command{
Use: "delete NAME",
Short: "Delete a Terraform resource",
Expand Down Expand Up @@ -308,3 +309,27 @@ func configureDefaultNamespace() {
kubeconfigArgs.Namespace = &fromEnv
}
}

var forceUnlockExample = `
# Unlock Terraform resource "aws-security-group" with lock id "f2ab685b-f84d-ac0b-a125-378a22877e8d" in the default namespace
tfctl force-unlock aws-security-group -n default --lock-id="f2ab685b-f84d-ac0b-a125-378a22877e8d"
`

func buildForceUnlockCmd(app *tfctl.CLI) *cobra.Command {
forceUnlock := &cobra.Command{
Use: "force-unlock",
Short: "Force unlock a locked Terraform State",
Example: strings.Trim(forceUnlockExample, "\n"),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return app.ForceUnlock(
os.Stdout,
args[0],
viper.GetString("lock-id"),
)
},
}
forceUnlock.Flags().String("lock-id", "", "Set the lock-id that currently holds the lock of the terraform state e.g. f2ab685b-f84d-ac0b-a125-378a22877e8d")
viper.BindPFlags(forceUnlock.Flags())
return forceUnlock
}
27 changes: 27 additions & 0 deletions config/crd/bases/infra.contrib.fluxcd.io_terraforms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,33 @@ spec:
TF executions, it does not apply to already started executions.
Defaults to false.
type: boolean
tfstate:
description: TFStateSpec allows the user to set ForceUnlock
properties:
forceUnlock:
default: "no"
description: "ForceUnlock a Terraform state if it has become locked
for any reason. \n This is an Enum and has the expected values
of: \n - auto - yes - no \n WARNING: Only use `auto` in the
cases where you are absolutely certain that no other system
is using this state, you could otherwise end up in a bad place
See https://www.terraform.io/language/state/locking#force-unlock
for more information on the terraform state lock and force unlock."
enum:
- "yes"
- "no"
- auto
type: string
lockIdentifier:
description: "LockIdentifier holds the Identifier required by
Terraform to unlock the state if it ever gets into a locked
state. \n You'll need to put the Lock Identifier in here while
setting ForceUnlock to either `true` or `auto`. \n Leave this
empty to do nothing, set this to the value of the `Lock Info:
ID: [value]`, e.g. `f2ab685b-f84d-ac0b-a125-378a22877e8d`, to
force unlock the state."
type: string
type: object
vars:
description: List of input variables to set for the Terraform program.
items:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package controllers

import (
"github.com/weaveworks/tf-controller/utils"
"testing"

sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
. "github.com/onsi/gomega"
infrav1 "github.com/weaveworks/tf-controller/api/v1alpha1"
"github.com/weaveworks/tf-controller/utils"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package controllers

import (
"context"
"github.com/weaveworks/tf-controller/utils"
"testing"
"time"

"github.com/weaveworks/tf-controller/utils"

. "github.com/onsi/gomega"

sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
Expand Down
3 changes: 2 additions & 1 deletion controllers/tc000230_drift_detection_only_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package controllers

import (
"context"
"github.com/weaveworks/tf-controller/utils"
"testing"
"time"

"github.com/weaveworks/tf-controller/utils"

. "github.com/onsi/gomega"

sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
Expand Down
Loading

0 comments on commit 80be24d

Please sign in to comment.