Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

🚀 Add run applied status #353

Merged
merged 7 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-353-20240229-143355.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: '''`Workspace`: The `status` now includes the current configuration version
in `status.run.configurationVersion`.'''
time: 2024-02-29T14:33:55.620532+01:00
custom:
PR: "353"
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-353-20240229-143743.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: '''`Workspace`: The controller will reconcile the workspace more frequently
during incomplete runs to synchronize outputs faster.'''
time: 2024-02-29T14:37:43.54109+01:00
custom:
PR: "353"
2 changes: 1 addition & 1 deletion api/v1alpha2/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ type WorkspaceStatus struct {
// Workspace Runs status.
//
//+optional
Run RunStatus `json:"runStatus,omitempty"`
Run *RunStatus `json:"runStatus,omitempty"`
// Workspace Terraform version.
//
//+kubebuilder:validation:Pattern:="^\\d{1}\\.\\d{1,2}\\.\\d{1,2}$"
Expand Down
8 changes: 6 additions & 2 deletions api/v1alpha2/zz_generated.deepcopy.go

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

24 changes: 15 additions & 9 deletions controllers/workspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
w.log.Info("Workspace Controller", "msg", "successfully reconcilied workspace")
r.Recorder.Eventf(&w.instance, corev1.EventTypeNormal, "ReconcileWorkspace", "Successfully reconcilied workspace ID %s", w.instance.Status.WorkspaceID)

if w.instance.Status.Run != nil && !w.instance.Status.Run.RunCompleted() {
w.log.Info("Workspace Controller", "msg", fmt.Sprintf("current run %s status %s is not completed need to requeue", w.instance.Status.Run.ID, w.instance.Status.Run.Status))
return requeueAfter(requeueRunStatusInterval)
}

return doNotRequeue()
}

Expand Down Expand Up @@ -264,15 +269,6 @@ func (r *WorkspaceReconciler) updateStatus(ctx context.Context, w *workspaceInst
w.instance.Status.WorkspaceID = workspace.ID
w.instance.Status.TerraformVersion = workspace.TerraformVersion

if workspace.CurrentRun != nil {
w.instance.Status.Run.ID = workspace.CurrentRun.ID
run, err := w.tfClient.Client.Runs.Read(ctx, workspace.CurrentRun.ID)
if err != nil {
return err
}
w.instance.Status.Run.Status = string(run.Status)
}

return r.Status().Update(ctx, &w.instance)
}

Expand Down Expand Up @@ -645,5 +641,15 @@ func (r *WorkspaceReconciler) reconcileWorkspace(ctx context.Context, w *workspa
w.log.Info("Reconcile Notifications", "msg", "successfully reconcilied notifications")
r.Recorder.Eventf(&w.instance, corev1.EventTypeNormal, "ReconcileNotifications", "Reconcilied notifications in workspace ID %s", w.instance.Status.WorkspaceID)

// Reconsile Runs (Status)
err = r.reconcileRuns(ctx, w, workspace)
if err != nil {
w.log.Error(err, "Reconcile Runs", "msg", "failed to reconcile runs")
r.Recorder.Eventf(&w.instance, corev1.EventTypeWarning, "ReconcileRuns", "Failed to reconcile runs in workspace ID %s", w.instance.Status.WorkspaceID)
return err
}
w.log.Info("Reconcile Runs", "msg", "successfully reconcilied runs")
r.Recorder.Eventf(&w.instance, corev1.EventTypeNormal, "ReconcileRuns", "Successfully reconcilied runs in workspace ID %s", w.instance.Status.WorkspaceID)

return r.updateStatus(ctx, w, workspace)
}
2 changes: 1 addition & 1 deletion controllers/workspace_controller_outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (r *WorkspaceReconciler) reconcileOutputs(ctx context.Context, w *workspace
}
if runApplied {
w.log.Info("Reconcile Outputs", "mgs", "run successfully applied")
if w.instance.Status.Run.OutputRunID != workspace.CurrentRun.ID {
if w.instance.Status.Run != nil && w.instance.Status.Run.OutputRunID != workspace.CurrentRun.ID {
w.log.Info("Reconcile Outputs", "mgs", "creating or updating outputs")
err = r.setOutputs(ctx, w)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions controllers/workspace_controller_outputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ var _ = Describe("Workspace controller", Ordered, func() {
By("Validating configuration version and workspace run")
Eventually(func() bool {
Expect(k8sClient.Get(ctx, namespacedName, instance)).Should(Succeed())
if instance.Status.Run == nil {
return false
}

runs, err := tfClient.Runs.List(ctx, instance.Status.WorkspaceID, &tfc.RunListOptions{})
Expect(err).Should(Succeed())
Expand Down
42 changes: 42 additions & 0 deletions controllers/workspace_controller_runs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package controllers

import (
"context"
"fmt"

tfc "github.com/hashicorp/go-tfe"
appv1alpha2 "github.com/hashicorp/terraform-cloud-operator/api/v1alpha2"
)

func (r *WorkspaceReconciler) reconcileRuns(ctx context.Context, w *workspaceInstance, workspace *tfc.Workspace) error {
w.log.Info("Reconcile Runs", "msg", "new reconciliation event")

if workspace.CurrentRun == nil {
w.log.Info("Reconcile Runs", "msg", "there is no current run")
return nil
}

if w.instance.Status.Run == nil {
w.instance.Status.Run = &appv1alpha2.RunStatus{}
}

// Update current run status
if workspace.CurrentRun.ID != w.instance.Status.Run.ID || !w.instance.Status.Run.RunCompleted() {
w.log.Info("Reconcile Runs", "msg", "get the current run status")
run, err := w.tfClient.Client.Runs.Read(ctx, workspace.CurrentRun.ID)
if err != nil {
w.log.Error(err, "Reconcile Runs", "msg", "failed to get the current run status")
return err
}
w.log.Info("Reconcile Runs", "msg", fmt.Sprintf("successfully got the current run status %s", run.Status))
// Update status
w.instance.Status.Run.ID = run.ID
w.instance.Status.Run.Status = string(run.Status)
w.instance.Status.Run.ConfigurationVersion = run.ConfigurationVersion.ID
}

return nil
}
100 changes: 100 additions & 0 deletions controllers/workspace_controller_runs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package controllers

import (
"fmt"
"time"

tfc "github.com/hashicorp/go-tfe"
appv1alpha2 "github.com/hashicorp/terraform-cloud-operator/api/v1alpha2"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("Workspace controller", Ordered, func() {
var (
instance *appv1alpha2.Workspace
namespacedName = newNamespacedName()
workspace = fmt.Sprintf("kubernetes-operator-%v", randomNumber())
)

BeforeAll(func() {
// Set default Eventually timers
SetDefaultEventuallyTimeout(syncPeriod * 4)
SetDefaultEventuallyPollingInterval(2 * time.Second)
})

BeforeEach(func() {
if cloudEndpoint != tfcDefaultAddress {
Skip("Does not run against TFC, skip this test")
}
// Create a new workspace object for each test
instance = &appv1alpha2.Workspace{
TypeMeta: metav1.TypeMeta{
APIVersion: "app.terraform.io/v1alpha2",
Kind: "Workspace",
},
ObjectMeta: metav1.ObjectMeta{
Name: namespacedName.Name,
Namespace: namespacedName.Namespace,
DeletionTimestamp: nil,
Finalizers: []string{},
},
Spec: appv1alpha2.WorkspaceSpec{
Organization: organization,
Token: appv1alpha2.Token{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretNamespacedName.Name,
},
Key: secretKey,
},
},
Name: workspace,
ApplyMethod: "auto",
},
Status: appv1alpha2.WorkspaceStatus{},
}
})

AfterEach(func() {
// Delete the Kubernetes workspace object and wait until the controller finishes the reconciliation after deletion of the object
deleteWorkspace(instance)
})

Context("Workspace controller", func() {
It("can handle runs", func() {
namespacedName := getNamespacedName(instance)
// Create a new Kubernetes workspace object and wait until the controller finishes the reconciliation
createWorkspace(instance)

outputValue := "hoi"
cv := createAndUploadConfigurationVersion(instance, outputValue)

By("Validating configuration version and workspace run")
Eventually(func() bool {
Expect(k8sClient.Get(ctx, namespacedName, instance)).Should(Succeed())
if instance.Status.Run == nil {
return false
}

runs, err := tfClient.Runs.List(ctx, instance.Status.WorkspaceID, &tfc.RunListOptions{})
Expect(err).Should(Succeed())
Expect(runs).ShouldNot(BeNil())

for _, r := range runs.Items {
if r.ConfigurationVersion.ID == cv.ID {
if r.ID == instance.Status.Run.ID && instance.Status.Run.RunCompleted() {
return true
}
}
}
return false
}).Should(BeTrue())
})
})
})
Loading