Repository: flux-iac/tofu-controller

Issue:
Terraform State Lock not released
We've hit an issue where the State lock was not released due to the runner pod not shutting down correctly (assumption here) due to a node refresh on an EKS Cluster. So we ended up with a Kubernetes `Lease` that was still held.

```
$ kubectl -n flux-system get lease
NAMESPACE     NAME                      HOLDER                                AGE
...
flux-system   lock-tfstate-default-sg1                                        3d7h
flux-system   lock-tfstate-default-sg2  f2ab685b-f84d-ac0b-a125-378a22877e8d  3d7h
```

The `Terraform` resource was reporting the following:

```
error running Plan: rpc error: code = Unknown desc = error acquiring the state lock: Lock Info: ID: f2ab685b-f84d-ac0b-a125-378a22877e8d Path: Operation: OperationTypePlan Who: controller@sg2-tf-runner Version: 1.1.9 Created: 2022-06-15 22:27:10.244794577 +0000 UTC
```

Which was repeated in the `tf-controller` and `tf-runner` logs. We were able to get things running again by deleting the `Lease` in question.

So the ask here is to support the [Terraform force-unlock](https://www.terraform.io/language/state/locking#force-unlock) feature.

I think the best approach might be to add a new field to the [`TerraformSpec`](https://weaveworks.github.io/tf-controller/References/terraform/#infra.contrib.fluxcd.io/v1alpha1.TerraformSpec), e.g. `forceUnlock`, the value of which should be the `Lock Info: ID: [value]`, in the above example this would be `forceUnlock: f2ab685b-f84d-ac0b-a125-378a22877e8d`. This ID corresponds to the key [`spec.holderIdentity`](https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/lease-v1/#LeaseSpec) on a  `Lease`.

The `tf-controller`/`runner` should be tasked with acting on this, allowing for unlocking other Terraform backends that support locking if they become supported by the `tf-controller`.

This could then be supported in the `tfctl` e.g. `tfctl force-unlock f2ab685b-f84d-ac0b-a125-378a22877e8d` which would set the field on the Terraform resource and force a reconciliation. Is there a potential race condition here?

Hints from issue comments:
@nalum: Another possible solution would be to have the `tf-controller` watching the `Lease`s to see if a `tf-runner` completes but fails to release the `Lease`, the `tf-controller` could then force the release of the `Lease`, this wouldn't require user intervention and could maybe be a configuration of the `tf-controller` or another configuration option of the `Terraform` resource.@nalum: We found two issues that were the cause of this in two separate cases:
```json
{
  "level": "error",
  "ts": "2022-06-15T20:14:34.824Z",
  "logger": "runner.terraform",
  "msg": "error creating the plan",
  "error": "exit status 1
    Error: Error releasing the state lock
    Error message: etcdserver: request timed out
    Terraform acquires a lock when accessing your state to prevent others
    running Terraform to potentially modify the state at the same time. An
    error occurred while releasing this lock. This could mean that the lock
    did or did not release properly. If the lock didn't release properly,
    Terraform may not be able to run future commands since it'll appear as if
    the lock is held.

    In this scenario, please call the \"force-unlock\" command to unlock the
    state manually. This is a very dangerous operation since if it is done
    erroneously it could result in two people modifying state at the same time.
    Only call this command if you're certain that the unlock above failed and
    that no one else is holding a lock."
}
```

And
```
I0615 22:29:32.020709       7 request.go:665] Waited for 1.027123219s due to client-side throttling, not priority and fairness, request: GET:https://172.20.0.1:443/apis/node.k8s.io/v1?timeout=32s
{\"level\":\"info\",\"ts\":\"2022-06-15T22:29:40.658Z\",\"logger\":\"runner.terraform\",\"msg\":\"creating a plan\"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xd165ea]

goroutine 359 [running]:
github.com/hashicorp/terraform-exec/tfexec.(*Terraform).buildTerraformCmd(0x0, {0xc0005e0280, 0x1}, 0x162e028, {0xc000616400, 0x47d9be, 0x0})
  /go/pkg/mod/github.com/tf-controller/terraform-exec@v0.15.1-0.20220114135154-c06c64609ee0/tfexec/cmd.go:174 +0x2a
github.com/hashicorp/terraform-exec/tfexec.(*Terraform).planCmd(0xc0003fc120, {0x1847728, 0xc00063a3c0}, {0xc0003fc130, 0x1, 0x10})
  /go/pkg/mod/github.com/tf-controller/terraform-exec@v0.15.1-0.20220114135154-c06c64609ee0/tfexec/plan.go:179 +0xb6f
github.com/hashicorp/terraform-exec/tfexec.(*Terraform).Plan(0x1498420, {0x1847728, 0xc00063a3c0}, {0xc0003fc130, 0xf, 0x0})
  /go/pkg/mod/github.com/tf-controller/terraform-exec@v0.15.1-0.20220114135154-c06c64609ee0/tfexec/plan.go:100 +0x31
github.com/weaveworks/tf-controller/runner.(*TerraformRunnerServer).Plan(0xc000ad63f0, {0x18477d0, 0xc0005247e0}, 0xc0003c3270)
  /workspace/runner/server.go:384 +0x4e6
github.com/weaveworks/tf-controller/runner._Runner_Plan_Handler({0x15dcdc0, 0xc000ad63f0}, {0x18477d0, 0xc0005247e0}, 0xc0005c15c0, 0x0)
  /workspace/runner/runner_grpc.pb.go:489 +0x170
google.golang.org/grpc.(*Server).processUnaryRPC(0xc0005c8000, {0x1873270, 0xc000603380}, 0xc0004c0000, 0xc000614cc0, 0x23c99c0, 0x0)
  /go/pkg/mod/google.golang.org/grpc@v1.45.0/server.go:1282 +0xccf
google.golang.org/grpc.(*Server).handleStream(0xc0005c8000, {0x1873270, 0xc000603380}, 0xc0004c0000, 0x0)
  /go/pkg/mod/google.golang.org/grpc@v1.45.0/server.go:1619 +0xa2a
google.golang.org/grpc.(*Server).serveStreams.func1.2()
  /go/pkg/mod/google.golang.org/grpc@v1.45.0/server.go:921 +0x98
created by google.golang.org/grpc.(*Server).serveStreams.func1
  /go/pkg/mod/google.golang.org/grpc@v1.45.0/server.go:919 +0x294
```@nalum: To expand on the above I'm looking at adding the following to the `spec`:

```yaml
spec:
  backendConfig:
    state:
      forceUnlock: [holder-id]
```

By default, and following the same path as Terraform, this would require manual intervention by an operator, who would need to put this `[holder-id]` in place in the the `Terraform` resource. This can be done by changing the resource in the Flux `Source` or by running `tfctl force-unlock [terraform-resource] [holder-id]`, which would set the value and then trigger a reconcile for that resource.

Does `tfctl reconcile` do the reconcile right away or is it queued up and take place `X` time after you run the command? Ideally we'd like the `tfctl force-unlock` to trigger a reconciliation immediately so that the `Terraform` resource doesn't get reset by Flux before the reconcile happens.

I think it also makes sense to allow an automated in which case you would set `spec.backendConfig.state.forceUnlock: auto`, this allows for the use case where the assumption is that nothing else will ever be using this state, so we are okay with the `tf-controller` automatically unlocking the state. Obviously putting all the warnings around this in the documentation.

@chanwit @tomhuang12 @phoban01 what do you think about the above?@nalum: I have changed the above noted structure as it impacts the `backendConfig` in a way that seems to complicate it more than I'd like.

So I've moved `spec.backendConfig.state` out to `spec.terraformState` and changed the structure of this a little to the following:

```yaml
spec:
  terraformState:
    forceUnlock: (yes|no|auto)
    lockIdentifier: [holder-id]
```

`spec.terraformState.forceUnlock` defaults to `no`@chanwit: Thank you @Nalum 
This design is excellent. It gives us auditable activities for force unlocking.

```
spec:
  terraformState:
    forceUnlock: (yes|no|auto)
    lockIdentifier: [holder-id]
```

There are a few things to improve UX.
  - I would change `terraformState` to `tfstate` so users would be able to reason that they're working with `tfstate` at the moment.
  - What are the semantics of `forceUnlock=yes` and `forceUnlock=auto`? How are they different?

@nalum: Thanks for the feedback 👍

> There are a few things to improve UX.
>   - I would change `terraformState` to `tfstate` so users would be able to reason that they're working with `tfstate` at the moment.

Ah yes, this makes sense, will make this change 👍

>   - What are the semantics of `forceUnlock=yes` and `forceUnlock=auto`? How are they different?

The purpose of `auto` is to have the `tf-controller` automatically unlock the state, in the case where the user is happy for this to happen, and `yes` is a manual trigger, the expectation being that it is triggered from the `tfctl`.

This has just made me think about some of the implementation and I need to revisit it.@nalum: Closing as #268 has been merged!@artiommocrenco: this is still an issue @nalum 

I periodically have to do `kubectl delete lease lock-tfstate-default-myapp -n flux-system`@nalum: @artiommocrenco I would recommend creating a new issue referencing this and including a minimal example of the configuration and any logs / events to help in debugging the issue you are having.

Solution patch:
diff --git a/api/v1alpha1/terraform_types.go b/api/v1alpha1/terraform_types.go
index f0d419f..c5fb1d2 100644
--- a/api/v1alpha1/terraform_types.go
+++ b/api/v1alpha1/terraform_types.go
@@ -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 {
@@ -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"`
@@ -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"
@@ -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"
@@ -313,6 +356,7 @@ const (
 	OutputsWritingFailedReason = "OutputsWritingFailed"
 	HealthChecksFailedReason   = "HealthChecksFailed"
 	TFExecApplySucceedReason   = "TerraformAppliedSucceed"
+	TFExecLockHeldReason       = "LockHeld"
 )
 
 // SetTerraformReadiness sets the ReadyCondition, ObservedGeneration, and LastAttemptedRevision, on the Terraform.
@@ -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 {
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 906bbb5..ee06e81 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1,3 +1,4 @@
+//go:build !ignore_autogenerated
 // +build !ignore_autogenerated
 
 /*
@@ -229,6 +230,21 @@ func (in *RunnerPodTemplate) DeepCopy() *RunnerPodTemplate {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TFStateSpec) DeepCopyInto(out *TFStateSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TFStateSpec.
+func (in *TFStateSpec) DeepCopy() *TFStateSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(TFStateSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Terraform) DeepCopyInto(out *Terraform) {
 	*out = *in
@@ -352,6 +368,11 @@ func (in *TerraformSpec) DeepCopyInto(out *TerraformSpec) {
 		**out = **in
 	}
 	in.RunnerPodTemplate.DeepCopyInto(&out.RunnerPodTemplate)
+	if in.TFState != nil {
+		in, out := &in.TFState, &out.TFState
+		*out = new(TFStateSpec)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerraformSpec.
diff --git a/cmd/tfctl/main.go b/cmd/tfctl/main.go
index 29584c7..1d924b9 100644
--- a/cmd/tfctl/main.go
+++ b/cmd/tfctl/main.go
@@ -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
 }
@@ -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",
@@ -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
+}
diff --git a/config/crd/bases/infra.contrib.fluxcd.io_terraforms.yaml b/config/crd/bases/infra.contrib.fluxcd.io_terraforms.yaml
index 906c412..4c9e8a9 100644
--- a/config/crd/bases/infra.contrib.fluxcd.io_terraforms.yaml
+++ b/config/crd/bases/infra.contrib.fluxcd.io_terraforms.yaml
@@ -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:
diff --git a/controllers/terraform_controller.go b/controllers/terraform_controller.go
index 88f3168..152f0a6 100644
--- a/controllers/terraform_controller.go
+++ b/controllers/terraform_controller.go
@@ -836,7 +836,18 @@ terraform {
 
 	initReply, err := runnerClient.Init(ctx, initRequest)
 	if err != nil {
+		if st, ok := status.FromError(err); ok {
+			for _, detail := range st.Details() {
+				if reply, ok := detail.(*runner.InitReply); ok {
+					if fuErr := r.setForceUnlock(ctx, objectKey, reply.StateLockIdentifier); fuErr != nil {
+						err = fmt.Errorf("error running setForceUnlock: %s :: %s", fuErr, err)
+					}
+				}
+			}
+		}
+
 		err = fmt.Errorf("error running Init: %s", err)
+
 		return infrav1.TerraformNotReady(
 			terraform,
 			revision,
@@ -863,6 +874,41 @@ terraform {
 	log.Info(fmt.Sprintf("generate vars from tf: %s", generateVarsForTFReply.Message))
 
 	log.Info("generated var files from spec")
+
+	// This variable is going to be used to force unlock the state if it is locked
+	lockIdentifier := ""
+
+	// If we have a lock id we want to force unlock the state
+	if terraform.Spec.TFState != nil {
+		if terraform.Spec.TFState.ForceUnlock == infrav1.ForceUnlockEnumYes || terraform.Spec.TFState.ForceUnlock == infrav1.ForceUnlockEnumAuto {
+			lockIdentifier = terraform.Spec.TFState.LockIdentifier
+		}
+	}
+
+	// If we have a lock id need to force unlock it
+	if lockIdentifier != "" {
+		_, err := runnerClient.ForceUnlock(context.Background(), &runner.ForceUnlockRequest{
+			LockIdentifier: lockIdentifier,
+		})
+
+		if err != nil {
+			return terraform, tfInstance, tmpDir, err
+		}
+
+		terraform = infrav1.TerraformForceUnlock(terraform, fmt.Sprintf("Terraform Force Unlock with Lock Identifier: %s", lockIdentifier))
+
+		if err := r.patchStatus(ctx, objectKey, terraform.Status); err != nil {
+			log.Error(err, "unable to update status before Terraform force unlock")
+			return terraform, tfInstance, tmpDir, err
+		}
+
+		err = r.setForceUnlock(ctx, objectKey, "")
+
+		if err != nil {
+			return terraform, tfInstance, tmpDir, err
+		}
+	}
+
 	return terraform, tfInstance, tmpDir, nil
 }
 
@@ -888,7 +934,18 @@ func (r *TerraformReconciler) detectDrift(ctx context.Context, terraform infrav1
 
 	planReply, err := runnerClient.Plan(ctx, planRequest)
 	if err != nil {
+		if st, ok := status.FromError(err); ok {
+			for _, detail := range st.Details() {
+				if reply, ok := detail.(*runner.PlanReply); ok {
+					if fuErr := r.setForceUnlock(ctx, types.NamespacedName{Namespace: terraform.GetNamespace(), Name: terraform.GetName()}, reply.StateLockIdentifier); fuErr != nil {
+						err = fmt.Errorf("error running setForceUnlock: %s :: %s", fuErr, err)
+					}
+				}
+			}
+		}
+
 		err = fmt.Errorf("error running Plan: %s", err)
+
 		return infrav1.TerraformNotReady(
 			terraform,
 			revision,
@@ -965,7 +1022,18 @@ func (r *TerraformReconciler) plan(ctx context.Context, terraform infrav1.Terraf
 
 	planReply, err := runnerClient.Plan(ctx, planRequest)
 	if err != nil {
+		if st, ok := status.FromError(err); ok {
+			for _, detail := range st.Details() {
+				if reply, ok := detail.(*runner.PlanReply); ok {
+					if fuErr := r.setForceUnlock(ctx, types.NamespacedName{Namespace: terraform.GetNamespace(), Name: terraform.GetName()}, reply.StateLockIdentifier); fuErr != nil {
+						err = fmt.Errorf("error running setForceUnlock: %s :: %s", fuErr, err)
+					}
+				}
+			}
+		}
+
 		err = fmt.Errorf("error running Plan: %s", err)
+
 		return infrav1.TerraformNotReady(
 			terraform,
 			revision,
@@ -1069,7 +1137,18 @@ func (r *TerraformReconciler) apply(ctx context.Context, terraform infrav1.Terra
 		})
 		log.Info(fmt.Sprintf("destroy: %s", destroyReply.Message))
 		if err != nil {
+			if st, ok := status.FromError(err); ok {
+				for _, detail := range st.Details() {
+					if reply, ok := detail.(*runner.DestroyReply); ok {
+						if fuErr := r.setForceUnlock(ctx, types.NamespacedName{Namespace: terraform.GetNamespace(), Name: terraform.GetName()}, reply.StateLockIdentifier); fuErr != nil {
+							err = fmt.Errorf("error running setForceUnlock: %s :: %s", fuErr, err)
+						}
+					}
+				}
+			}
+
 			err = fmt.Errorf("error running Destroy: %s", err)
+
 			return infrav1.TerraformAppliedFailResetPlanAndNotReady(
 				terraform,
 				revision,
@@ -1081,7 +1160,18 @@ func (r *TerraformReconciler) apply(ctx context.Context, terraform infrav1.Terra
 	} else {
 		applyReply, err := runnerClient.Apply(ctx, applyRequest)
 		if err != nil {
+			if st, ok := status.FromError(err); ok {
+				for _, detail := range st.Details() {
+					if reply, ok := detail.(*runner.ApplyReply); ok {
+						if fuErr := r.setForceUnlock(ctx, types.NamespacedName{Namespace: terraform.GetNamespace(), Name: terraform.GetName()}, reply.StateLockIdentifier); fuErr != nil {
+							err = fmt.Errorf("error running setForceUnlock: %s :: %s", fuErr, err)
+						}
+					}
+				}
+			}
+
 			err = fmt.Errorf("error running Apply: %s", err)
+
 			return infrav1.TerraformAppliedFailResetPlanAndNotReady(
 				terraform,
 				revision,
@@ -1659,17 +1749,17 @@ func (r *TerraformReconciler) getRunnerConnection(ctx context.Context, tlsSecret
 	}
 
 	const retryPolicy = `{
-		"methodConfig": [{
-		  "name": [{"service": "runner.Runner"}],
-		  "waitForReady": true,
-		  "retryPolicy": {
-			  "MaxAttempts": 4,
-			  "InitialBackoff": ".01s",
-			  "MaxBackoff": ".01s",
-			  "BackoffMultiplier": 1.0,
-			  "RetryableStatusCodes": [ "UNAVAILABLE" ]
-		  }
-		}]}`
+"methodConfig": [{
+  "name": [{"service": "runner.Runner"}],
+  "waitForReady": true,
+  "retryPolicy": {
+    "MaxAttempts": 4,
+    "InitialBackoff": ".01s",
+    "MaxBackoff": ".01s",
+    "BackoffMultiplier": 1.0,
+    "RetryableStatusCodes": [ "UNAVAILABLE" ]
+  }
+}]}`
 
 	return grpc.DialContext(ctx, addr,
 		grpc.WithTransportCredentials(credentials),
@@ -2138,3 +2228,58 @@ func (r *TerraformReconciler) outputsMayBeDrifted(ctx context.Context, terraform
 
 	return false, nil
 }
+
+func (r *TerraformReconciler) setForceUnlock(ctx context.Context, objectKey types.NamespacedName, lockID string) error {
+	log := ctrl.LoggerFrom(ctx)
+	var terraform infrav1.Terraform
+	err := r.Get(ctx, objectKey, &terraform)
+
+	if err != nil {
+		log.Error(err, "unable to get object")
+		return err
+	}
+
+	patch := client.MergeFrom(terraform.DeepCopy())
+
+	if lockID != "" {
+		terraform = infrav1.TerraformLocked(terraform, fmt.Sprintf("Terraform Locked with Lock Identifier: %s", lockID))
+
+		if err := r.patchStatus(ctx, objectKey, terraform.Status); err != nil {
+			log.Error(err, "unable to patch object status")
+			return err
+		}
+
+		err := r.Get(ctx, objectKey, &terraform)
+
+		if err != nil {
+			log.Error(err, "unable to get object")
+			return err
+		}
+
+		patch = client.MergeFrom(terraform.DeepCopy())
+
+		if terraform.Spec.TFState != nil {
+			terraform.Spec.TFState.LockIdentifier = lockID
+		} else {
+			terraform.Spec.TFState = &infrav1.TFStateSpec{
+				ForceUnlock:    infrav1.ForceUnlockEnumNo,
+				LockIdentifier: lockID,
+			}
+		}
+	} else {
+		if terraform.Spec.TFState.ForceUnlock != infrav1.ForceUnlockEnumAuto {
+			terraform.Spec.TFState = nil
+		} else {
+			terraform.Spec.TFState.LockIdentifier = ""
+		}
+	}
+
+	err = r.Patch(ctx, &terraform, patch)
+
+	if err != nil {
+		log.Error(err, "unable to patch object")
+		return err
+	}
+
+	return nil
+}
diff --git a/docs/References/terraform.md b/docs/References/terraform.md
index 7b18612..f5760f0 100644
--- a/docs/References/terraform.md
+++ b/docs/References/terraform.md
@@ -233,6 +233,12 @@ string
 </table>
 </div>
 </div>
+<h3 id="infra.contrib.fluxcd.io/v1alpha1.ForceUnlockEnum">ForceUnlockEnum
+(<code>string</code> alias)</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#infra.contrib.fluxcd.io/v1alpha1.TFStateSpec">TFStateSpec</a>)
+</p>
 <h3 id="infra.contrib.fluxcd.io/v1alpha1.HealthCheck">HealthCheck
 </h3>
 <p>
@@ -681,6 +687,68 @@ Cannot be updated.</p>
 </table>
 </div>
 </div>
+<h3 id="infra.contrib.fluxcd.io/v1alpha1.TFStateSpec">TFStateSpec
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#infra.contrib.fluxcd.io/v1alpha1.TerraformSpec">TerraformSpec</a>)
+</p>
+<p>TFStateSpec allows the user to set ForceUnlock</p>
+<div class="md-typeset__scrollwrap">
+<div class="md-typeset__table">
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>forceUnlock</code><br>
+<em>
+<a href="#infra.contrib.fluxcd.io/v1alpha1.ForceUnlockEnum">
+ForceUnlockEnum
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ForceUnlock a Terraform state if it has become locked for any reason.</p>
+<p>This is an Enum and has the expected values of:</p>
+<ul>
+<li>auto</li>
+<li>yes</li>
+<li>no</li>
+</ul>
+<p>WARNING: Only use <code>auto</code> 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 <a href="https://www.terraform.io/language/state/locking#force-unlock">https://www.terraform.io/language/state/locking#force-unlock</a> for more
+information on the terraform state lock and force unlock.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>lockIdentifier</code><br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>LockIdentifier holds the Identifier required by Terraform to unlock the state
+if it ever gets into a locked state.</p>
+<p>You&rsquo;ll need to put the Lock Identifier in here while setting ForceUnlock to
+either <code>true</code> or <code>auto</code>.</p>
+<p>Leave this empty to do nothing, set this to the value of the <code>Lock Info: ID: [value]</code>,
+e.g. <code>f2ab685b-f84d-ac0b-a125-378a22877e8d</code>, to force unlock the state.</p>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
 <h3 id="infra.contrib.fluxcd.io/v1alpha1.Terraform">Terraform
 </h3>
 <p>Terraform is the Schema for the terraforms API</p>
@@ -1026,6 +1094,19 @@ bool
 <p>EnableInventory enables the object to store resource entries as the inventory for external use.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>tfstate</code><br>
+<em>
+<a href="#infra.contrib.fluxcd.io/v1alpha1.TFStateSpec">
+TFStateSpec
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
 </table>
 </td>
 </tr>
@@ -1367,6 +1448,19 @@ bool
 <p>EnableInventory enables the object to store resource entries as the inventory for external use.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>tfstate</code><br>
+<em>
+<a href="#infra.contrib.fluxcd.io/v1alpha1.TFStateSpec">
+TFStateSpec
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
 </tbody>
 </table>
 </div>
diff --git a/runner/runner.pb.go b/runner/runner.pb.go
index 0507c2f..6d20525 100644
--- a/runner/runner.pb.go
+++ b/runner/runner.pb.go
@@ -920,8 +920,9 @@ type PlanReply struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Drifted bool   `protobuf:"varint,1,opt,name=drifted,proto3" json:"drifted,omitempty"`
-	Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+	Drifted             bool   `protobuf:"varint,1,opt,name=drifted,proto3" json:"drifted,omitempty"`
+	Message             string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+	StateLockIdentifier string `protobuf:"bytes,3,opt,name=stateLockIdentifier,proto3" json:"stateLockIdentifier,omitempty"`
 }
 
 func (x *PlanReply) Reset() {
@@ -970,6 +971,13 @@ func (x *PlanReply) GetMessage() string {
 	return ""
 }
 
+func (x *PlanReply) GetStateLockIdentifier() string {
+	if x != nil {
+		return x.StateLockIdentifier
+	}
+	return ""
+}
+
 type ShowPlanFileRawRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1400,7 +1408,8 @@ type ApplyReply struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Message             string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	StateLockIdentifier string `protobuf:"bytes,2,opt,name=stateLockIdentifier,proto3" json:"stateLockIdentifier,omitempty"`
 }
 
 func (x *ApplyReply) Reset() {
@@ -1442,6 +1451,13 @@ func (x *ApplyReply) GetMessage() string {
 	return ""
 }
 
+func (x *ApplyReply) GetStateLockIdentifier() string {
+	if x != nil {
+		return x.StateLockIdentifier
+	}
+	return ""
+}
+
 type GetInventoryRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1651,7 +1667,8 @@ type DestroyReply struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Message             string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	StateLockIdentifier string `protobuf:"bytes,2,opt,name=stateLockIdentifier,proto3" json:"stateLockIdentifier,omitempty"`
 }
 
 func (x *DestroyReply) Reset() {
@@ -1693,6 +1710,13 @@ func (x *DestroyReply) GetMessage() string {
 	return ""
 }
 
+func (x *DestroyReply) GetStateLockIdentifier() string {
+	if x != nil {
+		return x.StateLockIdentifier
+	}
+	return ""
+}
+
 type OutputRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2154,7 +2178,8 @@ type InitReply struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Message             string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	StateLockIdentifier string `protobuf:"bytes,2,opt,name=stateLockIdentifier,proto3" json:"stateLockIdentifier,omitempty"`
 }
 
 func (x *InitReply) Reset() {
@@ -2196,6 +2221,13 @@ func (x *InitReply) GetMessage() string {
 	return ""
 }
 
+func (x *InitReply) GetStateLockIdentifier() string {
+	if x != nil {
+		return x.StateLockIdentifier
+	}
+	return ""
+}
+
 type UploadRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2416,6 +2448,108 @@ func (x *FinalizeSecretsReply) GetNotFound() bool {
 	return false
 }
 
+type ForceUnlockRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	LockIdentifier string `protobuf:"bytes,1,opt,name=lockIdentifier,proto3" json:"lockIdentifier,omitempty"`
+}
+
+func (x *ForceUnlockRequest) Reset() {
+	*x = ForceUnlockRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_runner_runner_proto_msgTypes[44]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForceUnlockRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForceUnlockRequest) ProtoMessage() {}
+
+func (x *ForceUnlockRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_runner_runner_proto_msgTypes[44]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForceUnlockRequest.ProtoReflect.Descriptor instead.
+func (*ForceUnlockRequest) Descriptor() ([]byte, []int) {
+	return file_runner_runner_proto_rawDescGZIP(), []int{44}
+}
+
+func (x *ForceUnlockRequest) GetLockIdentifier() string {
+	if x != nil {
+		return x.LockIdentifier
+	}
+	return ""
+}
+
+type ForceUnlockReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Success bool   `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
+}
+
+func (x *ForceUnlockReply) Reset() {
+	*x = ForceUnlockReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_runner_runner_proto_msgTypes[45]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForceUnlockReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForceUnlockReply) ProtoMessage() {}
+
+func (x *ForceUnlockReply) ProtoReflect() protoreflect.Message {
+	mi := &file_runner_runner_proto_msgTypes[45]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForceUnlockReply.ProtoReflect.Descriptor instead.
+func (*ForceUnlockReply) Descriptor() ([]byte, []int) {
+	return file_runner_runner_proto_rawDescGZIP(), []int{45}
+}
+
+func (x *ForceUnlockReply) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *ForceUnlockReply) GetSuccess() bool {
+	if x != nil {
+		return x.Success
+	}
+	return false
+}
+
 var File_runner_runner_proto protoreflect.FileDescriptor
 
 var file_runner_runner_proto_rawDesc = []byte{
@@ -2497,256 +2631,281 @@ var file_runner_runner_proto_rawDesc = []byte{
 	0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03,
 	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x18, 0x0a,
 	0x07, 0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
-	0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x22, 0x3f, 0x0a, 0x09, 0x50, 0x6c, 0x61, 0x6e, 0x52,
+	0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x22, 0x71, 0x0a, 0x09, 0x50, 0x6c, 0x61, 0x6e, 0x52,
 	0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x72, 0x69, 0x66, 0x74, 0x65, 0x64, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x72, 0x69, 0x66, 0x74, 0x65, 0x64, 0x12, 0x18,
 	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x54, 0x0a, 0x16, 0x53, 0x68, 0x6f, 0x77,
-	0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
-	0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x34,
-	0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61,
-	0x77, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x61, 0x77, 0x4f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x61, 0x77, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x11, 0x53, 0x61, 0x76, 0x65, 0x54, 0x46, 0x50,
-	0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66,
+	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x73, 0x74, 0x61, 0x74,
+	0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b,
+	0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x54, 0x0a, 0x16, 0x53, 0x68,
+	0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61, 0x77, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
+	0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74,
+	0x61, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
+	0x22, 0x34, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65,
+	0x52, 0x61, 0x77, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x61, 0x77, 0x4f,
+	0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x61, 0x77,
+	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x11, 0x53, 0x61, 0x76, 0x65, 0x54,
+	0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a,
+	0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x18,
+	0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c,
+	0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18,
+	0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c,
+	0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75,
+	0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x1a,
+	0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x0f, 0x53, 0x61,
+	0x76, 0x65, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a,
+	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
+	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x6f, 0x61, 0x64,
+	0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a,
+	0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3a, 0x0a,
+	0x18, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
+	0x6c, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x18, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
+	0x6c, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a,
+	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70,
+	0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x6e, 0x22, 0x2b, 0x0a,
+	0x0f, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x0c, 0x41, 0x70,
+	0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66,
 	0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
-	0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x18, 0x62, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, 0x44,
-	0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x62, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, 0x44,
-	0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61,
-	0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e,
-	0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08,
-	0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
-	0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2b, 0x0a, 0x0f, 0x53, 0x61, 0x76, 0x65,
-	0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
-	0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc3, 0x01, 0x0a, 0x11, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46,
-	0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74,
-	0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x18, 0x62,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79,
-	0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x62,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79,
-	0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
-	0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x6e,
-	0x64, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
-	0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x6e, 0x22, 0x2b, 0x0a, 0x0f, 0x4c,
-	0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18,
-	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c,
-	0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e,
-	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66,
-	0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x4f,
-	0x72, 0x50, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72,
-	0x4f, 0x72, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73,
-	0x68, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x42, 0x65, 0x66, 0x6f, 0x72,
-	0x65, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0x26, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52,
-	0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x35,
-	0x0a, 0x13, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61,
-	0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73,
-	0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x48, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65,
-	0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x33, 0x0a, 0x0b, 0x69, 0x6e,
-	0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x11, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f,
-	0x72, 0x79, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22,
-	0x53, 0x0a, 0x09, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-	0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69,
-	0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
-	0x66, 0x69, 0x65, 0x72, 0x22, 0x30, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74,
-	0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e,
-	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f,
+	0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69,
+	0x72, 0x4f, 0x72, 0x50, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64,
+	0x69, 0x72, 0x4f, 0x72, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x66, 0x72,
+	0x65, 0x73, 0x68, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x42, 0x65, 0x66,
+	0x6f, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0x58, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6c,
 	0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x22, 0x2f, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
-	0x65, 0x22, 0x99, 0x01, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c,
-	0x79, 0x12, 0x3a, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70,
-	0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45,
-	0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x4e, 0x0a,
-	0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
-	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
-	0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
-	0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x65,
-	0x74, 0x61, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a,
-	0x0a, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x73,
-	0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
-	0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x22, 0xef, 0x01, 0x0a, 0x13, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
-	0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
-	0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a,
-	0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69,
-	0x64, 0x12, 0x39, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x25, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74,
-	0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09,
-	0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x11, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65,
+	0x12, 0x30, 0x0a, 0x13, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65,
+	0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73,
+	0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69,
+	0x65, 0x72, 0x22, 0x35, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f,
+	0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49,
+	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74,
+	0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x48, 0x0a, 0x11, 0x47, 0x65, 0x74,
+	0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x33,
+	0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x76,
+	0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72,
+	0x69, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x09, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e,
+	0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64,
+	0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x30, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x74,
+	0x72, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66,
+	0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
+	0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x5a, 0x0a, 0x0c, 0x44, 0x65,
+	0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65,
 	0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75,
-	0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d,
-	0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61,
-	0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65,
-	0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x63,
-	0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3e, 0x0a, 0x07, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72,
-	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
-	0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74,
-	0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
-	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x83, 0x01, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74,
+	0x73, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63,
+	0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x13, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e,
+	0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x0d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74,
 	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73,
 	0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x66, 0x49,
-	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,
-	0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64,
-	0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x12,
-	0x1c, 0x0a, 0x09, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x0c, 0x52, 0x09, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x25, 0x0a,
-	0x09, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65,
-	0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x22, 0x23, 0x0a, 0x0d, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x22, 0x27, 0x0a, 0x0b, 0x55, 0x70, 0x6c,
-	0x6f, 0x61, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x16, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53,
-	0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
-	0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-	0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-	0x3a, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x18, 0x68, 0x61, 0x73, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63,
-	0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4c, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c,
-	0x69, 0x7a, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12,
-	0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x74,
-	0x46, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74,
-	0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0xb7, 0x0b, 0x0a, 0x06, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72,
-	0x12, 0x3c, 0x0a, 0x08, 0x4c, 0x6f, 0x6f, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x12, 0x17, 0x2e, 0x72,
-	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4c,
-	0x6f, 0x6f, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48,
-	0x0a, 0x0c, 0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1b,
-	0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61,
-	0x66, 0x6f, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x75,
-	0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72,
-	0x6d, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x53, 0x65, 0x74, 0x45,
-	0x6e, 0x76, 0x12, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x45,
-	0x6e, 0x76, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x75, 0x6e, 0x6e,
-	0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
-	0x12, 0x54, 0x0a, 0x10, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74,
-	0x72, 0x61, 0x63, 0x74, 0x12, 0x1f, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70,
-	0x6c, 0x6f, 0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55,
-	0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52,
-	0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75,
-	0x70, 0x44, 0x69, 0x72, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6c,
-	0x65, 0x61, 0x6e, 0x75, 0x70, 0x44, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70,
-	0x44, 0x69, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x12, 0x57, 0x72,
-	0x69, 0x74, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x12, 0x21, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69,
-	0x74, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
-	0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-	0x73, 0x43, 0x6c, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x2e, 0x72, 0x75, 0x6e,
-	0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6c, 0x69, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x75,
-	0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6c, 0x69, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11,
-	0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54,
-	0x46, 0x12, 0x20, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72,
-	0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54, 0x46, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6e,
-	0x65, 0x72, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54, 0x46, 0x52, 0x65,
-	0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x13, 0x2e,
-	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x11, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e,
-	0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0f, 0x53, 0x68, 0x6f, 0x77, 0x50,
-	0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61, 0x77, 0x12, 0x1e, 0x2e, 0x72, 0x75, 0x6e,
-	0x6e, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65,
-	0x52, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x75, 0x6e,
-	0x6e, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65,
-	0x52, 0x61, 0x77, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a, 0x53, 0x61,
-	0x76, 0x65, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
-	0x72, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x61, 0x76,
-	0x65, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42,
-	0x0a, 0x0a, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x19, 0x2e, 0x72,
-	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72,
-	0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79,
-	0x22, 0x00, 0x12, 0x33, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x2e, 0x72, 0x75,
-	0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x12, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79,
-	0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x49, 0x6e,
-	0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72,
-	0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65,
-	0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
-	0x00, 0x12, 0x39, 0x0a, 0x07, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x12, 0x16, 0x2e, 0x72,
-	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65,
-	0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06,
-	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e,
-	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e,
-	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70,
-	0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0c, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72,
+	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x99, 0x01, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x70,
+	0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3a, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70,
+	0x75, 0x74, 0x73, 0x1a, 0x4e, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
+	0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x0a, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x65, 0x74,
+	0x61, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74,
+	0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xef, 0x01, 0x0a, 0x13, 0x57, 0x72,
 	0x69, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65,
-	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42,
-	0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x72,
-	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72,
-	0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
-	0x22, 0x00, 0x12, 0x30, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x13, 0x2e, 0x72, 0x75, 0x6e,
-	0x6e, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x11, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x70,
-	0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x15,
-	0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55,
-	0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0f,
-	0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12,
-	0x1e, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a,
-	0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x1c, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a,
-	0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42,
-	0x08, 0x5a, 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d,
+	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e,
+	0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18,
+	0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57,
+	0x72, 0x69, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61,
+	0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
+	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
+	0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
+	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x11, 0x57,
+	0x72, 0x69, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x11, 0x47, 0x65,
+	0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a,
+	0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8d, 0x01,
+	0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c,
+	0x79, 0x12, 0x3e, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f,
+	0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4f, 0x75, 0x74, 0x70,
+	0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x83, 0x01,
+	0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a,
+	0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x74, 0x66, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a,
+	0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
+	0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x63, 0x65,
+	0x43, 0x6f, 0x70, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x63,
+	0x65, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f,
+	0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66,
+	0x6f, 0x72, 0x6d, 0x22, 0x57, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x73, 0x74,
+	0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65,
+	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4c, 0x6f,
+	0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x23, 0x0a, 0x0d,
+	0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
+	0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6c, 0x6f,
+	0x62, 0x22, 0x27, 0x0a, 0x0b, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x16, 0x46,
+	0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x18, 0x68, 0x61, 0x73, 0x53, 0x70,
+	0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63,
+	0x72, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x68, 0x61, 0x73, 0x53, 0x70,
+	0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63,
+	0x72, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63,
+	0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f,
+	0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22,
+	0x4c, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65,
+	0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x3c, 0x0a,
+	0x12, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74,
+	0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x6f, 0x63,
+	0x6b, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0x46, 0x0a, 0x10, 0x46,
+	0x6f, 0x72, 0x63, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12,
+	0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63,
+	0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63,
+	0x65, 0x73, 0x73, 0x32, 0xfe, 0x0b, 0x0a, 0x06, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x3c,
+	0x0a, 0x08, 0x4c, 0x6f, 0x6f, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x12, 0x17, 0x2e, 0x72, 0x75, 0x6e,
+	0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x6f,
+	0x6b, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0c,
+	0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1b, 0x2e, 0x72,
+	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f,
+	0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e,
+	0x65, 0x72, 0x2e, 0x4e, 0x65, 0x77, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x52,
+	0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x76,
+	0x12, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x76,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72,
+	0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x54,
+	0x0a, 0x10, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74, 0x72, 0x61,
+	0x63, 0x74, 0x12, 0x1f, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x6c, 0x6f,
+	0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x6c,
+	0x6f, 0x61, 0x64, 0x41, 0x6e, 0x64, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x44,
+	0x69, 0x72, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x65, 0x61,
+	0x6e, 0x75, 0x70, 0x44, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
+	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x44, 0x69,
+	0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x12, 0x57, 0x72, 0x69, 0x74,
+	0x65, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21,
+	0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x61, 0x63,
+	0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65,
+	0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43,
+	0x6c, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6c, 0x69, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x75, 0x6e, 0x6e,
+	0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6c, 0x69, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11, 0x47, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54, 0x46, 0x12,
+	0x20, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
+	0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54, 0x46, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72,
+	0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x54, 0x46, 0x52, 0x65, 0x70, 0x6c,
+	0x79, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x13, 0x2e, 0x72, 0x75,
+	0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x11, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65,
+	0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0f, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61,
+	0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61, 0x77, 0x12, 0x1e, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61,
+	0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x6c, 0x61, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x61,
+	0x77, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a, 0x53, 0x61, 0x76, 0x65,
+	0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e,
+	0x53, 0x61, 0x76, 0x65, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x54,
+	0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a,
+	0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e,
+	0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4c,
+	0x6f, 0x61, 0x64, 0x54, 0x46, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
+	0x12, 0x33, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x2e, 0x72, 0x75, 0x6e, 0x6e,
+	0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x12, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65,
+	0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x76, 0x65,
+	0x6e, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47,
+	0x65, 0x74, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x49,
+	0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12,
+	0x39, 0x0a, 0x07, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x12, 0x16, 0x2e, 0x72, 0x75, 0x6e,
+	0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x73, 0x74,
+	0x72, 0x6f, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x75,
+	0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x22, 0x00, 0x12, 0x48, 0x0a, 0x0c, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x73, 0x12, 0x1b, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74,
+	0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x19, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x0a,
+	0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x72, 0x75, 0x6e,
+	0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x47,
+	0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
+	0x12, 0x30, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x13, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e,
+	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72,
+	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x6c,
+	0x6f, 0x61, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0f, 0x46, 0x69,
+	0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x1e, 0x2e,
+	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
+	0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53,
+	0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x45, 0x0a,
+	0x0b, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1a, 0x2e, 0x72,
+	0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63,
+	0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x75, 0x6e, 0x6e, 0x65,
+	0x72, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x70,
+	0x6c, 0x79, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2761,7 +2920,7 @@ func file_runner_runner_proto_rawDescGZIP() []byte {
 	return file_runner_runner_proto_rawDescData
 }
 
-var file_runner_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 48)
+var file_runner_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 50)
 var file_runner_runner_proto_goTypes = []interface{}{
 	(*LookPathRequest)(nil),           // 0: runner.LookPathRequest
 	(*LookPathReply)(nil),             // 1: runner.LookPathReply
@@ -2807,17 +2966,19 @@ var file_runner_runner_proto_goTypes = []interface{}{
 	(*UploadReply)(nil),               // 41: runner.UploadReply
 	(*FinalizeSecretsRequest)(nil),    // 42: runner.FinalizeSecretsRequest
 	(*FinalizeSecretsReply)(nil),      // 43: runner.FinalizeSecretsReply
-	nil,                               // 44: runner.SetEnvRequest.EnvsEntry
-	nil,                               // 45: runner.OutputReply.OutputsEntry
-	nil,                               // 46: runner.WriteOutputsRequest.DataEntry
-	nil,                               // 47: runner.GetOutputsReply.OutputsEntry
+	(*ForceUnlockRequest)(nil),        // 44: runner.ForceUnlockRequest
+	(*ForceUnlockReply)(nil),          // 45: runner.ForceUnlockReply
+	nil,                               // 46: runner.SetEnvRequest.EnvsEntry
+	nil,                               // 47: runner.OutputReply.OutputsEntry
+	nil,                               // 48: runner.WriteOutputsRequest.DataEntry
+	nil,                               // 49: runner.GetOutputsReply.OutputsEntry
 }
 var file_runner_runner_proto_depIdxs = []int32{
-	44, // 0: runner.SetEnvRequest.envs:type_name -> runner.SetEnvRequest.EnvsEntry
+	46, // 0: runner.SetEnvRequest.envs:type_name -> runner.SetEnvRequest.EnvsEntry
 	28, // 1: runner.GetInventoryReply.inventories:type_name -> runner.Inventory
-	45, // 2: runner.OutputReply.outputs:type_name -> runner.OutputReply.OutputsEntry
-	46, // 3: runner.WriteOutputsRequest.data:type_name -> runner.WriteOutputsRequest.DataEntry
-	47, // 4: runner.GetOutputsReply.outputs:type_name -> runner.GetOutputsReply.OutputsEntry
+	47, // 2: runner.OutputReply.outputs:type_name -> runner.OutputReply.OutputsEntry
+	48, // 3: runner.WriteOutputsRequest.data:type_name -> runner.WriteOutputsRequest.DataEntry
+	49, // 4: runner.GetOutputsReply.outputs:type_name -> runner.GetOutputsReply.OutputsEntry
 	33, // 5: runner.OutputReply.OutputsEntry.value:type_name -> runner.OutputMeta
 	0,  // 6: runner.Runner.LookPath:input_type -> runner.LookPathRequest
 	2,  // 7: runner.Runner.NewTerraform:input_type -> runner.NewTerraformRequest
@@ -2840,29 +3001,31 @@ var file_runner_runner_proto_depIdxs = []int32{
 	38, // 24: runner.Runner.Init:input_type -> runner.InitRequest
 	40, // 25: runner.Runner.Upload:input_type -> runner.UploadRequest
 	42, // 26: runner.Runner.FinalizeSecrets:input_type -> runner.FinalizeSecretsRequest
-	1,  // 27: runner.Runner.LookPath:output_type -> runner.LookPathReply
-	3,  // 28: runner.Runner.NewTerraform:output_type -> runner.NewTerraformReply
-	5,  // 29: runner.Runner.SetEnv:output_type -> runner.SetEnvReply
-	7,  // 30: runner.Runner.UploadAndExtract:output_type -> runner.UploadAndExtractReply
-	9,  // 31: runner.Runner.CleanupDir:output_type -> runner.CleanupDirReply
-	11, // 32: runner.Runner.WriteBackendConfig:output_type -> runner.WriteBackendConfigReply
-	13, // 33: runner.Runner.ProcessCliConfig:output_type -> runner.ProcessCliConfigReply
-	15, // 34: runner.Runner.GenerateVarsForTF:output_type -> runner.GenerateVarsForTFReply
-	17, // 35: runner.Runner.Plan:output_type -> runner.PlanReply
-	19, // 36: runner.Runner.ShowPlanFileRaw:output_type -> runner.ShowPlanFileRawReply
-	21, // 37: runner.Runner.SaveTFPlan:output_type -> runner.SaveTFPlanReply
-	23, // 38: runner.Runner.LoadTFPlan:output_type -> runner.LoadTFPlanReply
-	25, // 39: runner.Runner.Apply:output_type -> runner.ApplyReply
-	27, // 40: runner.Runner.GetInventory:output_type -> runner.GetInventoryReply
-	30, // 41: runner.Runner.Destroy:output_type -> runner.DestroyReply
-	32, // 42: runner.Runner.Output:output_type -> runner.OutputReply
-	35, // 43: runner.Runner.WriteOutputs:output_type -> runner.WriteOutputsReply
-	37, // 44: runner.Runner.GetOutputs:output_type -> runner.GetOutputsReply
-	39, // 45: runner.Runner.Init:output_type -> runner.InitReply
-	41, // 46: runner.Runner.Upload:output_type -> runner.UploadReply
-	43, // 47: runner.Runner.FinalizeSecrets:output_type -> runner.FinalizeSecretsReply
-	27, // [27:48] is the sub-list for method output_type
-	6,  // [6:27] is the sub-list for method input_type
+	44, // 27: runner.Runner.ForceUnlock:input_type -> runner.ForceUnlockRequest
+	1,  // 28: runner.Runner.LookPath:output_type -> runner.LookPathReply
+	3,  // 29: runner.Runner.NewTerraform:output_type -> runner.NewTerraformReply
+	5,  // 30: runner.Runner.SetEnv:output_type -> runner.SetEnvReply
+	7,  // 31: runner.Runner.UploadAndExtract:output_type -> runner.UploadAndExtractReply
+	9,  // 32: runner.Runner.CleanupDir:output_type -> runner.CleanupDirReply
+	11, // 33: runner.Runner.WriteBackendConfig:output_type -> runner.WriteBackendConfigReply
+	13, // 34: runner.Runner.ProcessCliConfig:output_type -> runner.ProcessCliConfigReply
+	15, // 35: runner.Runner.GenerateVarsForTF:output_type -> runner.GenerateVarsForTFReply
+	17, // 36: runner.Runner.Plan:output_type -> runner.PlanReply
+	19, // 37: runner.Runner.ShowPlanFileRaw:output_type -> runner.ShowPlanFileRawReply
+	21, // 38: runner.Runner.SaveTFPlan:output_type -> runner.SaveTFPlanReply
+	23, // 39: runner.Runner.LoadTFPlan:output_type -> runner.LoadTFPlanReply
+	25, // 40: runner.Runner.Apply:output_type -> runner.ApplyReply
+	27, // 41: runner.Runner.GetInventory:output_type -> runner.GetInventoryReply
+	30, // 42: runner.Runner.Destroy:output_type -> runner.DestroyReply
+	32, // 43: runner.Runner.Output:output_type -> runner.OutputReply
+	35, // 44: runner.Runner.WriteOutputs:output_type -> runner.WriteOutputsReply
+	37, // 45: runner.Runner.GetOutputs:output_type -> runner.GetOutputsReply
+	39, // 46: runner.Runner.Init:output_type -> runner.InitReply
+	41, // 47: runner.Runner.Upload:output_type -> runner.UploadReply
+	43, // 48: runner.Runner.FinalizeSecrets:output_type -> runner.FinalizeSecretsReply
+	45, // 49: runner.Runner.ForceUnlock:output_type -> runner.ForceUnlockReply
+	28, // [28:50] is the sub-list for method output_type
+	6,  // [6:28] is the sub-list for method input_type
 	6,  // [6:6] is the sub-list for extension type_name
 	6,  // [6:6] is the sub-list for extension extendee
 	0,  // [0:6] is the sub-list for field type_name
@@ -3402,6 +3565,30 @@ func file_runner_runner_proto_init() {
 				return nil
 			}
 		}
+		file_runner_runner_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForceUnlockRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_runner_runner_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForceUnlockReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -3409,7 +3596,7 @@ func file_runner_runner_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_runner_runner_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   48,
+			NumMessages:   50,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/runner/runner.proto b/runner/runner.proto
index 211c90b..751f28d 100644
--- a/runner/runner.proto
+++ b/runner/runner.proto
@@ -30,6 +30,7 @@ service Runner {
   rpc Upload(UploadRequest) returns (UploadReply) {}
 
   rpc FinalizeSecrets(FinalizeSecretsRequest) returns (FinalizeSecretsReply) {}
+  rpc ForceUnlock(ForceUnlockRequest) returns (ForceUnlockReply) {}
 }
 
 message LookPathRequest {
@@ -115,6 +116,7 @@ message PlanRequest {
 message PlanReply {
   bool drifted = 1;
   string message = 2;
+  string stateLockIdentifier = 3;
 }
 
 message ShowPlanFileRawRequest {
@@ -159,6 +161,7 @@ message ApplyRequest {
 
 message ApplyReply {
   string message = 1;
+  string stateLockIdentifier = 2;
 }
 
 message GetInventoryRequest {
@@ -181,6 +184,7 @@ message DestroyRequest {
 
 message DestroyReply {
   string message = 1;
+  string stateLockIdentifier = 2;
 }
 
 message OutputRequest {
@@ -227,6 +231,7 @@ message InitRequest {
 
 message InitReply {
   string message = 1;
+  string stateLockIdentifier = 2;
 }
 
 message UploadRequest {
@@ -248,3 +253,12 @@ message FinalizeSecretsReply {
   string message = 1;
   bool   notFound = 2;
 }
+
+message ForceUnlockRequest {
+  string lockIdentifier = 1;
+}
+
+message ForceUnlockReply {
+  string message = 1;
+  bool   success = 2;
+}
diff --git a/runner/runner_grpc.pb.go b/runner/runner_grpc.pb.go
index c6f456e..955361f 100644
--- a/runner/runner_grpc.pb.go
+++ b/runner/runner_grpc.pb.go
@@ -43,6 +43,7 @@ type RunnerClient interface {
 	Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*InitReply, error)
 	Upload(ctx context.Context, in *UploadRequest, opts ...grpc.CallOption) (*UploadReply, error)
 	FinalizeSecrets(ctx context.Context, in *FinalizeSecretsRequest, opts ...grpc.CallOption) (*FinalizeSecretsReply, error)
+	ForceUnlock(ctx context.Context, in *ForceUnlockRequest, opts ...grpc.CallOption) (*ForceUnlockReply, error)
 }
 
 type runnerClient struct {
@@ -242,6 +243,15 @@ func (c *runnerClient) FinalizeSecrets(ctx context.Context, in *FinalizeSecretsR
 	return out, nil
 }
 
+func (c *runnerClient) ForceUnlock(ctx context.Context, in *ForceUnlockRequest, opts ...grpc.CallOption) (*ForceUnlockReply, error) {
+	out := new(ForceUnlockReply)
+	err := c.cc.Invoke(ctx, "/runner.Runner/ForceUnlock", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // RunnerServer is the server API for Runner service.
 // All implementations must embed UnimplementedRunnerServer
 // for forward compatibility
@@ -267,6 +277,7 @@ type RunnerServer interface {
 	Init(context.Context, *InitRequest) (*InitReply, error)
 	Upload(context.Context, *UploadRequest) (*UploadReply, error)
 	FinalizeSecrets(context.Context, *FinalizeSecretsRequest) (*FinalizeSecretsReply, error)
+	ForceUnlock(context.Context, *ForceUnlockRequest) (*ForceUnlockReply, error)
 	mustEmbedUnimplementedRunnerServer()
 }
 
@@ -337,6 +348,9 @@ func (UnimplementedRunnerServer) Upload(context.Context, *UploadRequest) (*Uploa
 func (UnimplementedRunnerServer) FinalizeSecrets(context.Context, *FinalizeSecretsRequest) (*FinalizeSecretsReply, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method FinalizeSecrets not implemented")
 }
+func (UnimplementedRunnerServer) ForceUnlock(context.Context, *ForceUnlockRequest) (*ForceUnlockReply, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ForceUnlock not implemented")
+}
 func (UnimplementedRunnerServer) mustEmbedUnimplementedRunnerServer() {}
 
 // UnsafeRunnerServer may be embedded to opt out of forward compatibility for this service.
@@ -728,6 +742,24 @@ func _Runner_FinalizeSecrets_Handler(srv interface{}, ctx context.Context, dec f
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Runner_ForceUnlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ForceUnlockRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(RunnerServer).ForceUnlock(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/runner.Runner/ForceUnlock",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(RunnerServer).ForceUnlock(ctx, req.(*ForceUnlockRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 // Runner_ServiceDesc is the grpc.ServiceDesc for Runner service.
 // It's only intended for direct use with grpc.RegisterService,
 // and not to be introspected or modified (even as a copy)
@@ -819,6 +851,10 @@ var Runner_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "FinalizeSecrets",
 			Handler:    _Runner_FinalizeSecrets_Handler,
 		},
+		{
+			MethodName: "ForceUnlock",
+			Handler:    _Runner_ForceUnlock_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "runner/runner.proto",
diff --git a/runner/server.go b/runner/server.go
index 4a861f8..06b7e32 100644
--- a/runner/server.go
+++ b/runner/server.go
@@ -5,13 +5,16 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	tfjson "github.com/hashicorp/terraform-json"
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
 
+	tfjson "github.com/hashicorp/terraform-json"
+
+	"errors"
+
 	securejoin "github.com/cyphar/filepath-securejoin"
 	"github.com/fluxcd/pkg/untar"
 	"github.com/go-logr/logr"
@@ -22,7 +25,6 @@ import (
 	"google.golang.org/grpc/status"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -289,8 +291,19 @@ func (r *TerraformRunnerServer) Init(ctx context.Context, req *InitRequest) (*In
 	initOpts := []tfexec.InitOption{tfexec.Upgrade(req.Upgrade), tfexec.ForceCopy(req.ForceCopy)}
 	initOpts = append(initOpts, backendConfigsOpts...)
 	if err := r.tf.Init(ctx, initOpts...); err != nil {
+		st := status.New(codes.Internal, err.Error())
+		var stateErr *tfexec.ErrStateLocked
+
+		if errors.As(err, &stateErr) {
+			st, err = st.WithDetails(&InitReply{Message: "not ok", StateLockIdentifier: stateErr.ID})
+
+			if err != nil {
+				return nil, err
+			}
+		}
+
 		log.Error(err, "unable to initialize")
-		return nil, err
+		return nil, st.Err()
 	}
 
 	return &InitReply{Message: "ok"}, nil
@@ -444,13 +457,22 @@ func (r *TerraformRunnerServer) Plan(ctx context.Context, req *PlanRequest) (*Pl
 
 	drifted, err := r.tf.Plan(ctx, planOpt...)
 	if err != nil {
+		st := status.New(codes.Internal, err.Error())
+		var stateErr *tfexec.ErrStateLocked
+
+		if errors.As(err, &stateErr) {
+			st, err = st.WithDetails(&PlanReply{Message: "not ok", StateLockIdentifier: stateErr.ID})
+
+			if err != nil {
+				return nil, err
+			}
+		}
+
 		log.Error(err, "error creating the plan")
-		return nil, err
+		return nil, st.Err()
 	}
-	return &PlanReply{
-		Drifted: drifted,
-		Message: "ok",
-	}, nil
+
+	return &PlanReply{Message: "ok", Drifted: drifted}, nil
 }
 
 func (r *TerraformRunnerServer) ShowPlanFileRaw(ctx context.Context, req *ShowPlanFileRawRequest) (*ShowPlanFileRawReply, error) {
@@ -497,7 +519,7 @@ func (r *TerraformRunnerServer) SaveTFPlan(ctx context.Context, req *SaveTFPlanR
 	var tfplanSecret corev1.Secret
 	tfplanSecretExists := true
 	if err := r.Client.Get(ctx, tfplanObjectKey, &tfplanSecret); err != nil {
-		if errors.IsNotFound(err) {
+		if apierrors.IsNotFound(err) {
 			tfplanSecretExists = false
 		} else {
 			err = fmt.Errorf("error getting tfplanSecret: %s", err)
@@ -623,8 +645,19 @@ func (r *TerraformRunnerServer) Destroy(ctx context.Context, req *DestroyRequest
 	}
 
 	if err := r.tf.Destroy(ctx); err != nil {
+		st := status.New(codes.Internal, err.Error())
+		var stateErr *tfexec.ErrStateLocked
+
+		if errors.As(err, &stateErr) {
+			st, err = st.WithDetails(&DestroyReply{Message: "not ok", StateLockIdentifier: stateErr.ID})
+
+			if err != nil {
+				return nil, err
+			}
+		}
+
 		log.Error(err, "unable to destroy")
-		return nil, err
+		return nil, st.Err()
 	}
 
 	return &DestroyReply{Message: "ok"}, nil
@@ -658,8 +691,19 @@ func (r *TerraformRunnerServer) Apply(ctx context.Context, req *ApplyRequest) (*
 	}
 
 	if err := r.tf.Apply(ctx, applyOpt...); err != nil {
+		st := status.New(codes.Internal, err.Error())
+		var stateErr *tfexec.ErrStateLocked
+
+		if errors.As(err, &stateErr) {
+			st, err = st.WithDetails(&ApplyReply{Message: "not ok", StateLockIdentifier: stateErr.ID})
+
+			if err != nil {
+				return nil, err
+			}
+		}
+
 		log.Error(err, "unable to apply plan")
-		return nil, err
+		return nil, st.Err()
 	}
 
 	return &ApplyReply{Message: "ok"}, nil
@@ -854,3 +898,18 @@ func (r *TerraformRunnerServer) FinalizeSecrets(ctx context.Context, req *Finali
 
 	return &FinalizeSecretsReply{Message: "ok"}, nil
 }
+
+func (r *TerraformRunnerServer) ForceUnlock(ctx context.Context, req *ForceUnlockRequest) (*ForceUnlockReply, error) {
+	reply := &ForceUnlockReply{
+		Success: true,
+		Message: fmt.Sprintf("Successfully unlocked state with lock identifier: %s", req.GetLockIdentifier()),
+	}
+	err := r.tf.ForceUnlock(ctx, req.LockIdentifier)
+
+	if err != nil {
+		reply.Success = false
+		reply.Message = fmt.Sprintf("Error unlocking the state: %s", err)
+	}
+
+	return reply, err
+}
diff --git a/tfctl/forceUnlock.go b/tfctl/forceUnlock.go
new file mode 100644
index 0000000..cd7cd5f
--- /dev/null
+++ b/tfctl/forceUnlock.go
@@ -0,0 +1,70 @@
+package tfctl
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"time"
+
+	"github.com/fluxcd/pkg/apis/meta"
+	infrav1 "github.com/weaveworks/tf-controller/api/v1alpha1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/util/retry"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// ForceUnlock will set the corresponding field and request a reconciliation for
+// the corresponding resource.
+func (c *CLI) ForceUnlock(out io.Writer, resource, lockID string) error {
+	key := types.NamespacedName{
+		Name:      resource,
+		Namespace: c.namespace,
+	}
+
+	err := c.setForceUnlockAndReconcile(context.TODO(), c.client, out, key, lockID)
+
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(out, " %s/%s Patched and Reconcile requested\n", c.namespace, resource)
+	return nil
+}
+
+func (c *CLI) setForceUnlockAndReconcile(ctx context.Context, kubeClient client.Client, out io.Writer, namespacedName types.NamespacedName, lockID string) error {
+	return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		terraform := &infrav1.Terraform{}
+
+		if err := kubeClient.Get(ctx, namespacedName, terraform); err != nil {
+			return err
+		}
+
+		patch := client.MergeFrom(terraform.DeepCopy())
+
+		if terraform.Spec.TFState == nil {
+			terraform.Spec.TFState = &infrav1.TFStateSpec{
+				ForceUnlock:    infrav1.ForceUnlockEnumYes,
+				LockIdentifier: lockID,
+			}
+		} else {
+			terraform.Spec.TFState.LockIdentifier = lockID
+
+			if terraform.Spec.TFState.ForceUnlock != infrav1.ForceUnlockEnumAuto {
+				terraform.Spec.TFState.ForceUnlock = infrav1.ForceUnlockEnumYes
+			}
+		}
+
+		fmt.Fprintf(out, " Setting LockIdentifier to '%s' on resource %s/%s\n", lockID, c.namespace, namespacedName.Name)
+
+		if ann := terraform.GetAnnotations(); ann == nil {
+			terraform.SetAnnotations(map[string]string{
+				meta.ReconcileRequestAnnotation: time.Now().Format(time.RFC3339Nano),
+			})
+		} else {
+			ann[meta.ReconcileRequestAnnotation] = time.Now().Format(time.RFC3339Nano)
+			terraform.SetAnnotations(ann)
+		}
+
+		return kubeClient.Patch(ctx, terraform, patch)
+	})
+}



Test patch:
diff --git a/controllers/tc000052_plan_and_manual_approve_with_files_test.go b/controllers/tc000052_plan_and_manual_approve_with_files_test.go
index c839fbf..1bdb7e8 100644
--- a/controllers/tc000052_plan_and_manual_approve_with_files_test.go
+++ b/controllers/tc000052_plan_and_manual_approve_with_files_test.go
@@ -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"
diff --git a/controllers/tc000170_if_apply_error_we_should_delete_the_plan_and_start_over_test.go b/controllers/tc000170_if_apply_error_we_should_delete_the_plan_and_start_over_test.go
index 0ee8ec7..bfa2c66 100644
--- a/controllers/tc000170_if_apply_error_we_should_delete_the_plan_and_start_over_test.go
+++ b/controllers/tc000170_if_apply_error_we_should_delete_the_plan_and_start_over_test.go
@@ -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"
diff --git a/controllers/tc000230_drift_detection_only_mode_test.go b/controllers/tc000230_drift_detection_only_mode_test.go
index 7ee7686..f9bddb0 100644
--- a/controllers/tc000230_drift_detection_only_mode_test.go
+++ b/controllers/tc000230_drift_detection_only_mode_test.go
@@ -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"
diff --git a/controllers/tc000290_force_unlock_test.go b/controllers/tc000290_force_unlock_test.go
new file mode 100644
index 0000000..a871aca
--- /dev/null
+++ b/controllers/tc000290_force_unlock_test.go
@@ -0,0 +1,774 @@
+package controllers
+
+import (
+	"context"
+	"fmt"
+	"testing"
+	"time"
+
+	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"
+	coordinationv1 "k8s.io/api/coordination/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func Test_000290_force_unlock_lock_identifier_test(t *testing.T) {
+	Spec("This spec describes the behaviour of a Terraform resource with inventory enabled.")
+	It("should be reconciled successfully with a set of inventory entries shown in the status.")
+
+	const (
+		sourceName    = "gr-force-unlock-lock-identifier"
+		terraformName = "tf-force-unlock-lock-identifier"
+	)
+	tfstateLeaseHolderIdentity := "f2ab685b-f84d-ac0b-a125-378a22877e8d"
+	g := NewWithT(t)
+	ctx := context.Background()
+
+	Given("a GitRepository")
+	By("defining a new GitRepository object")
+	testRepo := sourcev1.GitRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      sourceName,
+			Namespace: "flux-system",
+		},
+		Spec: sourcev1.GitRepositorySpec{
+			URL: "https://github.com/openshift-fluxv2-poc/podinfo",
+			Reference: &sourcev1.GitRepositoryRef{
+				Branch: "master",
+			},
+			Interval:          metav1.Duration{Duration: time.Second * 30},
+			GitImplementation: "go-git",
+		},
+	}
+	By("creating the GitRepository object")
+	g.Expect(k8sClient.Create(ctx, &testRepo)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &testRepo)).Should(Succeed()) }()
+
+	Given("that the GitRepository got reconciled")
+	By("setting the GitRepository's status, with the BLOB's URL, and the correct checksum")
+	updatedTime := time.Now()
+	testRepo.Status = sourcev1.GitRepositoryStatus{
+		ObservedGeneration: int64(1),
+		Conditions: []metav1.Condition{
+			{
+				Type:               "Ready",
+				Status:             metav1.ConditionTrue,
+				LastTransitionTime: metav1.Time{Time: updatedTime},
+				Reason:             "GitOperationSucceed",
+				Message:            "Fetched revision: master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			},
+		},
+		URL: server.URL() + "/tf-k8s-configmap.tar.gz",
+		Artifact: &sourcev1.Artifact{
+			Path:           "gitrepository/flux-system/test-tf-controller/b8e362c206e3d0cbb7ed22ced771a0056455a2fb.tar.gz",
+			URL:            server.URL() + "/tf-k8s-configmap.tar.gz",
+			Revision:       "master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			Checksum:       "c3bf30bad9621b5110a3761a70754170d1dae6c525a63098b6ec9919efac3555", // must be the real checksum value
+			LastUpdateTime: metav1.Time{Time: updatedTime},
+		},
+	}
+	g.Expect(k8sClient.Status().Update(ctx, &testRepo)).Should(Succeed())
+
+	testEnvKubeConfigPath, err := findKubeConfig(testEnv)
+	g.Expect(err).Should(BeNil())
+
+	Given("a Terraform object with auto approve, and attaching it to the GitRepository object")
+	By("creating a new TF resource and attaching to the repo via sourceRef")
+	helloWorldTF := infrav1.Terraform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      terraformName,
+			Namespace: "flux-system",
+		},
+		Spec: infrav1.TerraformSpec{
+			BackendConfig: &infrav1.BackendConfigSpec{
+				SecretSuffix:    terraformName,
+				InClusterConfig: false,
+				ConfigPath:      testEnvKubeConfigPath,
+			},
+			ApprovePlan: "auto",
+			Interval:    metav1.Duration{Duration: 5 * time.Second},
+			Path:        "./tf-k8s-configmap",
+			SourceRef: infrav1.CrossNamespaceSourceReference{
+				Kind:      "GitRepository",
+				Name:      sourceName,
+				Namespace: "flux-system",
+			},
+			Vars: []infrav1.Variable{
+				{
+					Name:  "kubeconfig",
+					Value: utils.MustJSONEncodeBytes(t, []byte(testEnvKubeConfigPath)),
+				},
+				{
+					Name:  "context",
+					Value: utils.MustJSONEncodeBytes(t, []byte("envtest")),
+				},
+				{
+					Name:  "config_name",
+					Value: utils.MustJSONEncodeBytes(t, []byte("cm-"+terraformName)),
+				},
+			},
+		},
+	}
+	g.Expect(k8sClient.Create(ctx, &helloWorldTF)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &helloWorldTF)).Should(Succeed()) }()
+
+	It("should be created")
+	By("checking that the hello world TF got created")
+	helloWorldTFKey := types.NamespacedName{Namespace: "flux-system", Name: terraformName}
+	createdHelloWorldTF := infrav1.Terraform{}
+	g.Eventually(func() bool {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return false
+		}
+		return true
+	}, timeout, interval).Should(BeTrue())
+
+	It("should have conditions reconciled")
+	By("checking that the hello world TF's status conditions has some elements")
+	g.Eventually(func() int {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return -1
+		}
+		return len(createdHelloWorldTF.Status.Conditions)
+	}, timeout, interval).ShouldNot(BeZero())
+
+	It("should have its plan reconciled")
+	By("checking that the Plan's Status of the TF program is Planned Succeed.")
+	g.Eventually(func() interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Plan" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+				}
+			}
+		}
+		return createdHelloWorldTF.Status
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":    "Plan",
+		"Reason":  "TerraformPlannedWithChanges",
+		"Message": "Plan generated",
+	}))
+
+	It("should generate the Secret containing the plan named with branch and commit id")
+	By("checking that the Secret contains plan-master-b8e362c206 in its labels")
+	tfplanKey := types.NamespacedName{Namespace: "flux-system", Name: "tfplan-default-" + terraformName}
+	tfplanSecret := corev1.Secret{}
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, tfplanKey, &tfplanSecret)
+		if err != nil {
+			return nil
+		}
+		return map[string]interface{}{
+			"SavedPlan":             tfplanSecret.Annotations["savedPlan"],
+			"Is TFPlan empty ?":     string(tfplanSecret.Data["tfplan"]) == "",
+			"HasEncodingAnnotation": tfplanSecret.Annotations["encoding"] == "gzip",
+		}
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"SavedPlan":             "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+		"Is TFPlan empty ?":     false,
+		"HasEncodingAnnotation": true,
+	}))
+
+	By("checking that the applied status of the TF program Successfully, and plan-master-b8e3 is applied")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Apply" {
+				return map[string]interface{}{
+					"Type":            c.Type,
+					"Reason":          c.Reason,
+					"Message":         c.Message,
+					"LastAppliedPlan": createdHelloWorldTF.Status.Plan.LastApplied,
+				}
+			}
+		}
+		return nil
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":            "Apply",
+		"Reason":          infrav1.TFExecApplySucceedReason,
+		"Message":         "Applied successfully",
+		"LastAppliedPlan": "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+	}))
+
+	var tfstateLease coordinationv1.Lease
+	tfstateLeaseKey := types.NamespacedName{Namespace: "flux-system", Name: fmt.Sprintf("lock-tfstate-default-%s", terraformName)}
+	err = k8sClient.Get(ctx, tfstateLeaseKey, &tfstateLease)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{
+	"metadata": {
+		"annotations": {
+			"app.terraform.io/lock-info": "{\"ID\": \"%s\",\"Operation\": \"OperationTypeApply\", \"Info\": \"\", \"Who\":\"%s\", \"Version\": \"1.2.7\", \"Created\": \"2022-08-17T15:44:07.0Z\", \"Path\": \"\"}"
+		}
+	},
+	"spec": {
+		"holderIdentity": "%s"
+	}
+}`, tfstateLeaseHolderIdentity, terraformName, tfstateLeaseHolderIdentity)))
+	err = k8sClient.Patch(ctx, &tfstateLease, patch)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	It("should fail to reconcile")
+	By("checking that the LockedState condition exists")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			//t.Logf("====\n\n%#v\n\n====", c)
+			if c.Type == "StateLocked" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+					"LID":     createdHelloWorldTF.Spec.TFState.LockIdentifier,
+				}
+			}
+		}
+		return nil
+	}, timeout*2, interval).Should(Equal(map[string]interface{}{
+		"Type":    "StateLocked",
+		"Reason":  infrav1.TFExecLockHeldReason,
+		"Message": fmt.Sprintf("Terraform Locked with Lock Identifier: %s", tfstateLeaseHolderIdentity),
+		"LID":     tfstateLeaseHolderIdentity,
+	}))
+}
+
+func Test_000290_force_unlock_yes_unlock_test(t *testing.T) {
+	Spec("This spec describes the behaviour of a Terraform resource with inventory enabled.")
+	It("should be reconciled successfully with a set of inventory entries shown in the status.")
+
+	const (
+		sourceName    = "gr-force-unlock-yes-unlock-test"
+		terraformName = "tf-force-unlock-yes-unlock-test"
+	)
+	tfstateLeaseHolderIdentity := "f2ab685b-f84d-ac0b-a125-378a22877e8d"
+	g := NewWithT(t)
+	ctx := context.Background()
+
+	Given("a GitRepository")
+	By("defining a new GitRepository object")
+	testRepo := sourcev1.GitRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      sourceName,
+			Namespace: "flux-system",
+		},
+		Spec: sourcev1.GitRepositorySpec{
+			URL: "https://github.com/openshift-fluxv2-poc/podinfo",
+			Reference: &sourcev1.GitRepositoryRef{
+				Branch: "master",
+			},
+			Interval:          metav1.Duration{Duration: time.Second * 30},
+			GitImplementation: "go-git",
+		},
+	}
+	By("creating the GitRepository object")
+	g.Expect(k8sClient.Create(ctx, &testRepo)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &testRepo)).Should(Succeed()) }()
+
+	Given("that the GitRepository got reconciled")
+	By("setting the GitRepository's status, with the BLOB's URL, and the correct checksum")
+	updatedTime := time.Now()
+	testRepo.Status = sourcev1.GitRepositoryStatus{
+		ObservedGeneration: int64(1),
+		Conditions: []metav1.Condition{
+			{
+				Type:               "Ready",
+				Status:             metav1.ConditionTrue,
+				LastTransitionTime: metav1.Time{Time: updatedTime},
+				Reason:             "GitOperationSucceed",
+				Message:            "Fetched revision: master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			},
+		},
+		URL: server.URL() + "/tf-k8s-configmap.tar.gz",
+		Artifact: &sourcev1.Artifact{
+			Path:           "gitrepository/flux-system/test-tf-controller/b8e362c206e3d0cbb7ed22ced771a0056455a2fb.tar.gz",
+			URL:            server.URL() + "/tf-k8s-configmap.tar.gz",
+			Revision:       "master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			Checksum:       "c3bf30bad9621b5110a3761a70754170d1dae6c525a63098b6ec9919efac3555", // must be the real checksum value
+			LastUpdateTime: metav1.Time{Time: updatedTime},
+		},
+	}
+	g.Expect(k8sClient.Status().Update(ctx, &testRepo)).Should(Succeed())
+
+	testEnvKubeConfigPath, err := findKubeConfig(testEnv)
+	g.Expect(err).Should(BeNil())
+
+	Given("a Terraform object with auto approve, and attaching it to the GitRepository object")
+	By("creating a new TF resource and attaching to the repo via sourceRef")
+	helloWorldTF := infrav1.Terraform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      terraformName,
+			Namespace: "flux-system",
+		},
+		Spec: infrav1.TerraformSpec{
+			BackendConfig: &infrav1.BackendConfigSpec{
+				SecretSuffix:    terraformName,
+				InClusterConfig: false,
+				ConfigPath:      testEnvKubeConfigPath,
+			},
+			ApprovePlan: "auto",
+			Interval:    metav1.Duration{Duration: 5 * time.Second},
+			Path:        "./tf-k8s-configmap",
+			SourceRef: infrav1.CrossNamespaceSourceReference{
+				Kind:      "GitRepository",
+				Name:      sourceName,
+				Namespace: "flux-system",
+			},
+			Vars: []infrav1.Variable{
+				{
+					Name:  "kubeconfig",
+					Value: utils.MustJSONEncodeBytes(t, []byte(testEnvKubeConfigPath)),
+				},
+				{
+					Name:  "context",
+					Value: utils.MustJSONEncodeBytes(t, []byte("envtest")),
+				},
+				{
+					Name:  "config_name",
+					Value: utils.MustJSONEncodeBytes(t, []byte("cm-"+terraformName)),
+				},
+			},
+		},
+	}
+	g.Expect(k8sClient.Create(ctx, &helloWorldTF)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &helloWorldTF)).Should(Succeed()) }()
+
+	It("should be created")
+	By("checking that the hello world TF got created")
+	helloWorldTFKey := types.NamespacedName{Namespace: "flux-system", Name: terraformName}
+	createdHelloWorldTF := infrav1.Terraform{}
+	g.Eventually(func() bool {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return false
+		}
+		return true
+	}, timeout, interval).Should(BeTrue())
+
+	It("should have conditions reconciled")
+	By("checking that the hello world TF's status conditions has some elements")
+	g.Eventually(func() int {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return -1
+		}
+		return len(createdHelloWorldTF.Status.Conditions)
+	}, timeout, interval).ShouldNot(BeZero())
+
+	It("should have its plan reconciled")
+	By("checking that the Plan's Status of the TF program is Planned Succeed.")
+	g.Eventually(func() interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Plan" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+				}
+			}
+		}
+		return createdHelloWorldTF.Status
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":    "Plan",
+		"Reason":  "TerraformPlannedWithChanges",
+		"Message": "Plan generated",
+	}))
+
+	It("should generate the Secret containing the plan named with branch and commit id")
+	By("checking that the Secret contains plan-master-b8e362c206 in its labels")
+	tfplanKey := types.NamespacedName{Namespace: "flux-system", Name: "tfplan-default-" + terraformName}
+	tfplanSecret := corev1.Secret{}
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, tfplanKey, &tfplanSecret)
+		if err != nil {
+			return nil
+		}
+		return map[string]interface{}{
+			"SavedPlan":             tfplanSecret.Annotations["savedPlan"],
+			"Is TFPlan empty ?":     string(tfplanSecret.Data["tfplan"]) == "",
+			"HasEncodingAnnotation": tfplanSecret.Annotations["encoding"] == "gzip",
+		}
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"SavedPlan":             "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+		"Is TFPlan empty ?":     false,
+		"HasEncodingAnnotation": true,
+	}))
+
+	By("checking that the applied status of the TF program Successfully, and plan-master-b8e3 is applied")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Apply" {
+				return map[string]interface{}{
+					"Type":            c.Type,
+					"Reason":          c.Reason,
+					"Message":         c.Message,
+					"LastAppliedPlan": createdHelloWorldTF.Status.Plan.LastApplied,
+				}
+			}
+		}
+		return nil
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":            "Apply",
+		"Reason":          infrav1.TFExecApplySucceedReason,
+		"Message":         "Applied successfully",
+		"LastAppliedPlan": "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+	}))
+
+	var tfstateLease coordinationv1.Lease
+	tfstateLeaseKey := types.NamespacedName{Namespace: "flux-system", Name: fmt.Sprintf("lock-tfstate-default-%s", terraformName)}
+	err = k8sClient.Get(ctx, tfstateLeaseKey, &tfstateLease)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{
+	"metadata": {
+		"annotations": {
+			"app.terraform.io/lock-info": "{\"ID\": \"%s\",\"Operation\": \"OperationTypeApply\", \"Info\": \"\", \"Who\":\"%s\", \"Version\": \"1.2.7\", \"Created\": \"2022-08-17T15:44:07.0Z\", \"Path\": \"\"}"
+		}
+	},
+	"spec": {
+		"holderIdentity": "%s"
+	}
+}`, tfstateLeaseHolderIdentity, terraformName, tfstateLeaseHolderIdentity)))
+	err = k8sClient.Patch(ctx, &tfstateLease, patch)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	It("should fail to reconcile")
+	By("checking that the LockedState condition exists")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			//t.Logf("====\n\n%#v\n\n====", c)
+			if c.Type == "StateLocked" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+					"LID":     createdHelloWorldTF.Spec.TFState.LockIdentifier,
+				}
+			}
+		}
+		return nil
+	}, timeout*2, interval).Should(Equal(map[string]interface{}{
+		"Type":    "StateLocked",
+		"Reason":  infrav1.TFExecLockHeldReason,
+		"Message": fmt.Sprintf("Terraform Locked with Lock Identifier: %s", tfstateLeaseHolderIdentity),
+		"LID":     tfstateLeaseHolderIdentity,
+	}))
+
+	patch = client.MergeFrom(createdHelloWorldTF.DeepCopy())
+	createdHelloWorldTF.Spec.TFState.ForceUnlock = infrav1.ForceUnlockEnumYes
+	k8sClient.Patch(ctx, &createdHelloWorldTF, patch)
+	var nilTFState *infrav1.TFStateSpec
+
+	It("should reconcile")
+	By("checking that the ForceUnlock condition exists")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			//t.Logf("====\n\n%#v\n\n====", c)
+			if c.Type == "ForceUnlock" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+					"TFState": createdHelloWorldTF.Spec.TFState,
+				}
+			}
+		}
+		return nil
+	}, timeout*2, interval).Should(Equal(map[string]interface{}{
+		"Type":    "ForceUnlock",
+		"Reason":  infrav1.TFExecLockHeldReason,
+		"Message": fmt.Sprintf("Terraform Force Unlock with Lock Identifier: %s", tfstateLeaseHolderIdentity),
+		"TFState": nilTFState,
+	}))
+}
+
+func Test_000290_force_unlock_auto_unlock_test(t *testing.T) {
+	Spec("This spec describes the behaviour of a Terraform resource with inventory enabled.")
+	It("should be reconciled successfully with a set of inventory entries shown in the status.")
+
+	const (
+		sourceName    = "gr-force-unlock-auto-unlock-test"
+		terraformName = "tf-force-unlock-auto-unlock-test"
+	)
+	tfstateLeaseHolderIdentity := "f2ab685b-f84d-ac0b-a125-378a22877e8d"
+	g := NewWithT(t)
+	ctx := context.Background()
+
+	Given("a GitRepository")
+	By("defining a new GitRepository object")
+	testRepo := sourcev1.GitRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      sourceName,
+			Namespace: "flux-system",
+		},
+		Spec: sourcev1.GitRepositorySpec{
+			URL: "https://github.com/openshift-fluxv2-poc/podinfo",
+			Reference: &sourcev1.GitRepositoryRef{
+				Branch: "master",
+			},
+			Interval:          metav1.Duration{Duration: time.Second * 30},
+			GitImplementation: "go-git",
+		},
+	}
+	By("creating the GitRepository object")
+	g.Expect(k8sClient.Create(ctx, &testRepo)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &testRepo)).Should(Succeed()) }()
+
+	Given("that the GitRepository got reconciled")
+	By("setting the GitRepository's status, with the BLOB's URL, and the correct checksum")
+	updatedTime := time.Now()
+	testRepo.Status = sourcev1.GitRepositoryStatus{
+		ObservedGeneration: int64(1),
+		Conditions: []metav1.Condition{
+			{
+				Type:               "Ready",
+				Status:             metav1.ConditionTrue,
+				LastTransitionTime: metav1.Time{Time: updatedTime},
+				Reason:             "GitOperationSucceed",
+				Message:            "Fetched revision: master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			},
+		},
+		URL: server.URL() + "/tf-k8s-configmap.tar.gz",
+		Artifact: &sourcev1.Artifact{
+			Path:           "gitrepository/flux-system/test-tf-controller/b8e362c206e3d0cbb7ed22ced771a0056455a2fb.tar.gz",
+			URL:            server.URL() + "/tf-k8s-configmap.tar.gz",
+			Revision:       "master/b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+			Checksum:       "c3bf30bad9621b5110a3761a70754170d1dae6c525a63098b6ec9919efac3555", // must be the real checksum value
+			LastUpdateTime: metav1.Time{Time: updatedTime},
+		},
+	}
+	g.Expect(k8sClient.Status().Update(ctx, &testRepo)).Should(Succeed())
+
+	testEnvKubeConfigPath, err := findKubeConfig(testEnv)
+	g.Expect(err).Should(BeNil())
+
+	Given("a Terraform object with auto approve, and attaching it to the GitRepository object")
+	By("creating a new TF resource and attaching to the repo via sourceRef")
+	helloWorldTF := infrav1.Terraform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      terraformName,
+			Namespace: "flux-system",
+		},
+		Spec: infrav1.TerraformSpec{
+			BackendConfig: &infrav1.BackendConfigSpec{
+				SecretSuffix:    terraformName,
+				InClusterConfig: false,
+				ConfigPath:      testEnvKubeConfigPath,
+			},
+			ApprovePlan: "auto",
+			Interval:    metav1.Duration{Duration: 5 * time.Second},
+			Path:        "./tf-k8s-configmap",
+			TFState: &infrav1.TFStateSpec{
+				ForceUnlock: infrav1.ForceUnlockEnumAuto,
+			},
+			SourceRef: infrav1.CrossNamespaceSourceReference{
+				Kind:      "GitRepository",
+				Name:      sourceName,
+				Namespace: "flux-system",
+			},
+			Vars: []infrav1.Variable{
+				{
+					Name:  "kubeconfig",
+					Value: utils.MustJSONEncodeBytes(t, []byte(testEnvKubeConfigPath)),
+				},
+				{
+					Name:  "context",
+					Value: utils.MustJSONEncodeBytes(t, []byte("envtest")),
+				},
+				{
+					Name:  "config_name",
+					Value: utils.MustJSONEncodeBytes(t, []byte("cm-"+terraformName)),
+				},
+			},
+		},
+	}
+	g.Expect(k8sClient.Create(ctx, &helloWorldTF)).Should(Succeed())
+	defer func() { g.Expect(k8sClient.Delete(ctx, &helloWorldTF)).Should(Succeed()) }()
+
+	It("should be created")
+	By("checking that the hello world TF got created")
+	helloWorldTFKey := types.NamespacedName{Namespace: "flux-system", Name: terraformName}
+	createdHelloWorldTF := infrav1.Terraform{}
+	g.Eventually(func() bool {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return false
+		}
+		return true
+	}, timeout, interval).Should(BeTrue())
+
+	It("should have conditions reconciled")
+	By("checking that the hello world TF's status conditions has some elements")
+	g.Eventually(func() int {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return -1
+		}
+		return len(createdHelloWorldTF.Status.Conditions)
+	}, timeout, interval).ShouldNot(BeZero())
+
+	It("should have its plan reconciled")
+	By("checking that the Plan's Status of the TF program is Planned Succeed.")
+	g.Eventually(func() interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Plan" {
+				return map[string]interface{}{
+					"Type":    c.Type,
+					"Reason":  c.Reason,
+					"Message": c.Message,
+				}
+			}
+		}
+		return createdHelloWorldTF.Status
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":    "Plan",
+		"Reason":  "TerraformPlannedWithChanges",
+		"Message": "Plan generated",
+	}))
+
+	It("should generate the Secret containing the plan named with branch and commit id")
+	By("checking that the Secret contains plan-master-b8e362c206 in its labels")
+	tfplanKey := types.NamespacedName{Namespace: "flux-system", Name: "tfplan-default-" + terraformName}
+	tfplanSecret := corev1.Secret{}
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, tfplanKey, &tfplanSecret)
+		if err != nil {
+			return nil
+		}
+		return map[string]interface{}{
+			"SavedPlan":             tfplanSecret.Annotations["savedPlan"],
+			"Is TFPlan empty ?":     string(tfplanSecret.Data["tfplan"]) == "",
+			"HasEncodingAnnotation": tfplanSecret.Annotations["encoding"] == "gzip",
+		}
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"SavedPlan":             "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+		"Is TFPlan empty ?":     false,
+		"HasEncodingAnnotation": true,
+	}))
+
+	By("checking that the applied status of the TF program Successfully, and plan-master-b8e3 is applied")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			if c.Type == "Apply" {
+				return map[string]interface{}{
+					"Type":            c.Type,
+					"Reason":          c.Reason,
+					"Message":         c.Message,
+					"LastAppliedPlan": createdHelloWorldTF.Status.Plan.LastApplied,
+				}
+			}
+		}
+		return nil
+	}, timeout, interval).Should(Equal(map[string]interface{}{
+		"Type":            "Apply",
+		"Reason":          infrav1.TFExecApplySucceedReason,
+		"Message":         "Applied successfully",
+		"LastAppliedPlan": "plan-master-b8e362c206e3d0cbb7ed22ced771a0056455a2fb",
+	}))
+
+	var tfstateLease coordinationv1.Lease
+	tfstateLeaseKey := types.NamespacedName{Namespace: "flux-system", Name: fmt.Sprintf("lock-tfstate-default-%s", terraformName)}
+	err = k8sClient.Get(ctx, tfstateLeaseKey, &tfstateLease)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{
+	"metadata": {
+		"annotations": {
+			"app.terraform.io/lock-info": "{\"ID\": \"%s\",\"Operation\": \"OperationTypeApply\", \"Info\": \"\", \"Who\":\"%s\", \"Version\": \"1.2.7\", \"Created\": \"2022-08-17T15:44:07.0Z\", \"Path\": \"\"}"
+		}
+	},
+	"spec": {
+		"holderIdentity": "%s"
+	}
+}`, tfstateLeaseHolderIdentity, terraformName, tfstateLeaseHolderIdentity)))
+	err = k8sClient.Patch(ctx, &tfstateLease, patch)
+
+	if err != nil {
+		t.Error(err)
+	}
+
+	It("should reconcile")
+	By("checking that the ForceUnlock condition exists")
+	g.Eventually(func() map[string]interface{} {
+		err := k8sClient.Get(ctx, helloWorldTFKey, &createdHelloWorldTF)
+		if err != nil {
+			return nil
+		}
+		for _, c := range createdHelloWorldTF.Status.Conditions {
+			//t.Logf("====\n\n%#v\n\n====", c)
+			if c.Type == "ForceUnlock" {
+				return map[string]interface{}{
+					"Type":           c.Type,
+					"Reason":         c.Reason,
+					"Message":        c.Message,
+					"LockIdentifier": createdHelloWorldTF.Spec.TFState.LockIdentifier,
+					"ForceUnlock":    createdHelloWorldTF.Spec.TFState.ForceUnlock,
+				}
+			}
+		}
+		return nil
+	}, timeout*2, interval).Should(Equal(map[string]interface{}{
+		"Type":           "ForceUnlock",
+		"Reason":         infrav1.TFExecLockHeldReason,
+		"Message":        fmt.Sprintf("Terraform Force Unlock with Lock Identifier: %s", tfstateLeaseHolderIdentity),
+		"LockIdentifier": "",
+		"ForceUnlock":    infrav1.ForceUnlockEnumAuto,
+	}))
+}