Skip to content

Commit

Permalink
Update FAQ, fix typos, and update API reference
Browse files Browse the repository at this point in the history
  • Loading branch information
arybolovlev committed Aug 20, 2024
1 parent 62affc6 commit 1253f22
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 20 deletions.
9 changes: 9 additions & 0 deletions api/v1alpha2/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,15 @@ type SSHKey struct {
// +kubebuilder:validation:Pattern:="^[A-Za-z0-9][A-Za-z0-9:_-]*$"
type Tag string

// DeletionPolicy defines the strategies for resource deletion in the Kubernetes operator.
// It controls how the operator should handle the deletion of resources when triggered by
// a user action or system event.
//
// There are four possible values:
// - `retain`: When the custom resource is deleted, the associated workspace is retained.
// - `soft`: Attempts to delete the associated workspace only if it does not contain any managed resources.
// - `destroy`: Executes a destroy operation to remove all resources managed by the associated workspace. Once the destruction of these resources is successful, the workspace itself is deleted, followed by the removal of the custom resource.
// - `force`: Forcefully and immediately deletes the workspace and the custom resource.
type DeletionPolicy string

const (
Expand Down
8 changes: 4 additions & 4 deletions controllers/agentpool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ func (r *AgentPoolReconciler) removeFinalizer(ctx context.Context, ap *agentPool

err := r.Update(ctx, &ap.instance)
if err != nil {
ap.log.Error(err, "Reconcile Agent Pool", "msg", fmt.Sprintf("failed to remove finazlier %s", agentPoolFinalizer))
r.Recorder.Eventf(&ap.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finazlier %s", agentPoolFinalizer)
ap.log.Error(err, "Reconcile Agent Pool", "msg", fmt.Sprintf("failed to remove finalizer %s", agentPoolFinalizer))
r.Recorder.Eventf(&ap.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finalizer %s", agentPoolFinalizer)
}

return err
Expand Down Expand Up @@ -214,7 +214,7 @@ func (r *AgentPoolReconciler) updateAgentPool(ctx context.Context, ap *agentPool

func (r *AgentPoolReconciler) deleteAgentPool(ctx context.Context, ap *agentPoolInstance) error {
if ap.instance.Status.AgentPoolID == "" {
ap.log.Info("Reconcile Agent Pool", "msg", fmt.Sprintf("status.agentPoolID is empty, remove finazlier %s", agentPoolFinalizer))
ap.log.Info("Reconcile Agent Pool", "msg", fmt.Sprintf("status.agentPoolID is empty, remove finalizer %s", agentPoolFinalizer))
return r.removeFinalizer(ctx, ap)
}
err := ap.tfClient.Client.AgentPools.Delete(ctx, ap.instance.Status.AgentPoolID)
Expand All @@ -230,7 +230,7 @@ func (r *AgentPoolReconciler) deleteAgentPool(ctx context.Context, ap *agentPool
return err
}

ap.log.Info("Reconcile Agent Pool", "msg", fmt.Sprintf("agent pool ID %s has been deleted, remove finazlier", ap.instance.Status.AgentPoolID))
ap.log.Info("Reconcile Agent Pool", "msg", fmt.Sprintf("agent pool ID %s has been deleted, remove finalizer", ap.instance.Status.AgentPoolID))
return r.removeFinalizer(ctx, ap)
}

Expand Down
4 changes: 2 additions & 2 deletions controllers/module_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ func (r *ModuleReconciler) removeFinalizer(ctx context.Context, m *moduleInstanc

err := r.Update(ctx, &m.instance)
if err != nil {
m.log.Error(err, "Reconcile Module", "msg", fmt.Sprintf("failed to remove finazlier %s", moduleFinalizer))
r.Recorder.Eventf(&m.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finazlier %s", moduleFinalizer)
m.log.Error(err, "Reconcile Module", "msg", fmt.Sprintf("failed to remove finalizer %s", moduleFinalizer))
r.Recorder.Eventf(&m.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finalizer %s", moduleFinalizer)
}

return err
Expand Down
10 changes: 5 additions & 5 deletions controllers/project_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ func (r *ProjectReconciler) removeFinalizer(ctx context.Context, p *projectInsta

err := r.Update(ctx, &p.instance)
if err != nil {
p.log.Error(err, "Reconcile Project", "msg", fmt.Sprintf("failed to remove finazlier %s", projectFinalizer))
r.Recorder.Eventf(&p.instance, corev1.EventTypeWarning, "RemoveProject", "Failed to remove finazlier %s", projectFinalizer)
p.log.Error(err, "Reconcile Project", "msg", fmt.Sprintf("failed to remove finalizer %s", projectFinalizer))
r.Recorder.Eventf(&p.instance, corev1.EventTypeWarning, "RemoveProject", "Failed to remove finalizer %s", projectFinalizer)
}

return err
Expand Down Expand Up @@ -228,23 +228,23 @@ func (r *ProjectReconciler) deleteProject(ctx context.Context, p *projectInstanc
// if the Kubernetes object doesn't have project ID, it means it a project was never created
// in this case, remove the finalizer and let Kubernetes remove the object permanently
if p.instance.Status.ID == "" {
p.log.Info("Reconcile Project", "msg", fmt.Sprintf("status.ID is empty, remove finazlier %s", projectFinalizer))
p.log.Info("Reconcile Project", "msg", fmt.Sprintf("status.ID is empty, remove finalizer %s", projectFinalizer))
return r.removeFinalizer(ctx, p)
}
err := p.tfClient.Client.Projects.Delete(ctx, p.instance.Status.ID)
if err != nil {
// if project wasn't found, it means it was deleted from the TF Cloud bypass the operator
// in this case, remove the finalizer and let Kubernetes remove the object permanently
if err == tfc.ErrResourceNotFound {
p.log.Info("Reconcile Project", "msg", fmt.Sprintf("Project ID %s not found, remove finazlier", p.instance.Status.ID))
p.log.Info("Reconcile Project", "msg", fmt.Sprintf("Project ID %s not found, remove finalizer", p.instance.Status.ID))
return r.removeFinalizer(ctx, p)
}
p.log.Error(err, "Reconcile Project", "msg", fmt.Sprintf("failed to delete Project ID %s, retry later", projectFinalizer))
r.Recorder.Eventf(&p.instance, corev1.EventTypeWarning, "ReconcileProject", "Failed to delete Project ID %s, retry later", p.instance.Status.ID)
return err
}

p.log.Info("Reconcile Project", "msg", fmt.Sprintf("project ID %s has been deleted, remove finazlier", p.instance.Status.ID))
p.log.Info("Reconcile Project", "msg", fmt.Sprintf("project ID %s has been deleted, remove finalizer", p.instance.Status.ID))
return r.removeFinalizer(ctx, p)
}

Expand Down
4 changes: 2 additions & 2 deletions controllers/workspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ func (r *WorkspaceReconciler) removeFinalizer(ctx context.Context, w *workspaceI

err := r.Update(ctx, &w.instance)
if err != nil {
w.log.Error(err, "Reconcile Workspace", "msg", fmt.Sprintf("failed to remove finazlier %s", workspaceFinalizer))
r.Recorder.Eventf(&w.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finazlier %s", workspaceFinalizer)
w.log.Error(err, "Reconcile Workspace", "msg", fmt.Sprintf("failed to remove finalizer %s", workspaceFinalizer))
r.Recorder.Eventf(&w.instance, corev1.EventTypeWarning, "RemoveFinalizer", "Failed to remove finalizer %s", workspaceFinalizer)
}

return err
Expand Down
14 changes: 7 additions & 7 deletions controllers/workspace_controller_deletion_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ func (r *WorkspaceReconciler) deleteWorkspace(ctx context.Context, w *workspaceI
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("deletion policy is %s", w.instance.Spec.DeletionPolicy))

if w.instance.Status.WorkspaceID == "" {
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("status.WorkspaceID is empty, remove finazlier %s", workspaceFinalizer))
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("status.WorkspaceID is empty, remove finalizer %s", workspaceFinalizer))
return r.removeFinalizer(ctx, w)
}

switch w.instance.Spec.DeletionPolicy {
case appv1alpha2.DeletionPolicyRetain:
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("remove finazlier %s", workspaceFinalizer))
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("remove finalizer %s", workspaceFinalizer))
return r.removeFinalizer(ctx, w)
case appv1alpha2.DeletionPolicySoft:
err := w.tfClient.Client.Workspaces.SafeDeleteByID(ctx, w.instance.Status.WorkspaceID)
if err != nil {
if err == tfc.ErrResourceNotFound {
w.log.Info("Reconcile Workspace", "msg", "Workspace was not found, remove finazlier")
w.log.Info("Reconcile Workspace", "msg", "Workspace was not found, remove finalizer")
return r.removeFinalizer(ctx, w)
}
if err == tfc.ErrWorkspaceNotSafeToDelete {
Expand All @@ -40,24 +40,24 @@ func (r *WorkspaceReconciler) deleteWorkspace(ctx context.Context, w *workspaceI
r.Recorder.Eventf(&w.instance, corev1.EventTypeWarning, "ReconcileWorkspace", "Failed to safe delete Workspace ID %s, retry later", w.instance.Status.WorkspaceID)
return err
}
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("workspace ID %s has been deleted, remove finazlier", w.instance.Status.WorkspaceID))
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("workspace ID %s has been deleted, remove finalizer", w.instance.Status.WorkspaceID))
return r.removeFinalizer(ctx, w)
case appv1alpha2.DeletionPolicyDestroy:
// Not yet implemented
// TODO: Implement the destroy logic
return nil
case appv1alpha2.DeletionPolicyForce:
err := w.tfClient.Client.Workspaces.DeleteByID(ctx, w.instance.Status.WorkspaceID)
if err != nil {
if err == tfc.ErrResourceNotFound {
w.log.Info("Reconcile Workspace", "msg", "Workspace was not found, remove finazlier")
w.log.Info("Reconcile Workspace", "msg", "Workspace was not found, remove finalizer")
return r.removeFinalizer(ctx, w)
}
w.log.Error(err, "Reconcile Workspace", "msg", fmt.Sprintf("failed to force delete Workspace ID %s, retry later", w.instance.Status.WorkspaceID))
r.Recorder.Eventf(&w.instance, corev1.EventTypeWarning, "ReconcileWorkspace", "Failed to force delete Workspace ID %s, retry later", w.instance.Status.WorkspaceID)
return err
}

w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("workspace ID %s has been deleted, remove finazlier", w.instance.Status.WorkspaceID))
w.log.Info("Reconcile Workspace", "msg", fmt.Sprintf("workspace ID %s has been deleted, remove finalizer", w.instance.Status.WorkspaceID))
return r.removeFinalizer(ctx, w)
}

Expand Down
9 changes: 9 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,17 @@ _Appears in:_

_Underlying type:_ _string_

DeletionPolicy defines the strategies for resource deletion in the Kubernetes operator.
It controls how the operator should handle the deletion of resources when triggered by
a user action or system event.


There are four possible values:
- `retain`: When the custom resource is deleted, the associated workspace is retained.
- `soft`: Attempts to delete the associated workspace only if it does not contain any managed resources.
- `destroy`: Executes a destroy operation to remove all resources managed by the associated workspace. Once the destruction of these resources is successful, the workspace itself is deleted, followed by the removal of the custom resource.
- `force`: Forcefully and immediately deletes the workspace and the custom resource.

_Appears in:_
- [WorkspaceSpec](#workspacespec)

Expand Down
39 changes: 39 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,42 @@
- If this involves migrating an existing workspace and the referred project doesn’t exist, the workspace will remain within the same project, and a corresponding error/event message will be provided.

If the `spec.project` field is not specified, the workspace will be created or moved to the default project.

- **How can I migrate workspace CR to a different cluster?**

Ensure that the `spec.deletionPolicy` of the workspace CR you are migrating is set to `retain`. This way, when you remove the workspace CR from the Kubernetes cluster, the operator will not delete the managed HCP Terraform workspace.

We do not recommend having two or more installations of the operator managing the same HCP Terraform workspace to avoid conflicts. Please ensure that you remove the workspace CR from the source cluster once the migration is complete.

You might consider "disabling" the operator on the source cluster during migration. To do this, set the number of replicas to `0`.

Here are steps to migrsate a workspace CR from one cluster to another:

- **Backup the existing workspace CR.** Export the workspace CR to a YAML file from the source cluster:

```console
$ kubectl get workspace <NAME> -o yaml > backup.<NAME>.workspace.yaml
```

- **Remove metadata fields.** You need to delete specific fields like `metadata.resourceVersion` and `metadata.uid` to avoid conflicts in the target cluster. These fields are specific to the Kubernetes resource lifecycle in a given cluster:

```console
$ yq eval 'del(.metadata.resourceVersion)' -i backup.<NAME>.workspace.yaml
$ yq eval 'del(.metadata.uid)' -i backup.<NAME>.workspace.yaml
```

- **Apply the backup file to the target cluster.** Ensure that the operator is up and running on the target cluster. Apply the backed-up Workspace YAML to the target cluster:

```console
$ kubectl apply -f backup.<NAME>.workspace.yaml
```

*At this point, you might observe a `Name has already been taken` error message in the logs. You can safely ignore this message.*

- **Patch the workspace CR's status in the target cluster.** Patch moved workspace CR status:

```console
$ kubectl patch workspace <NAME> --subresource='status' --type='merge' --patch-file=backup.<NAME>.workspace.yaml
```

*As a result, you will not see the `Name has already been taken` error in the logs because the operator will recognize the newly created resource as unique within the context of the new cluster.*

0 comments on commit 1253f22

Please sign in to comment.