From 6139c2f0751ee9b007fc46df0e1e05172bc05ded Mon Sep 17 00:00:00 2001 From: Rohith Jayawardene Date: Fri, 15 Nov 2024 10:14:08 +0000 Subject: [PATCH] [FEATURE] - Switching to Opentofu (#1550) * [FEATURE] - Switching to Opentofu Currently we are using the hashicorp image and binary, this PR changes the default implementation to opentofu * chore: updating the unit tests * chore: fixing up the reference to the binary * chore: changing the iamge to use in the helm chart --- charts/terranetes-controller/values.yaml | 2 +- cmd/controller/main.go | 3 ++- pkg/assets/job.yaml.tpl | 17 +++++++++-------- pkg/controller/configuration/controller.go | 2 ++ pkg/controller/configuration/delete.go | 3 ++- pkg/controller/configuration/ensure.go | 6 ++++-- pkg/controller/configuration/reconcile_test.go | 16 +++++++++------- pkg/server/server.go | 1 + pkg/server/types.go | 2 ++ pkg/utils/jobs/jobs.go | 9 ++++++--- 10 files changed, 38 insertions(+), 23 deletions(-) diff --git a/charts/terranetes-controller/values.yaml b/charts/terranetes-controller/values.yaml index d6a718b0e..40fa86bfd 100644 --- a/charts/terranetes-controller/values.yaml +++ b/charts/terranetes-controller/values.yaml @@ -35,7 +35,7 @@ controller: # Configuration for the images used by the jobs images: # is the default image to use for terraform operations - terraform: hashicorp/terraform:1.5.7 + terraform: ghcr.io/opentofu/opentofu:1.8.5 # image to use for infracost infracost: infracost/infracost:ci-0.10.35 # policy is image for policy diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 21a88cd4c..9db7b984c 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -78,6 +78,7 @@ func main() { flags.StringSliceVar(&config.ExecutorSecrets, "executor-secret", []string{}, "Name of a secret in controller namespace which should be added to the job") flags.StringSliceVar(&config.JobLabels, "job-label", []string{}, "A collection of key=values to add to all jobs") flags.StringVar(&config.BackendTemplate, "backend-template", "", "Name of secret in the controller namespace containing a template for the terraform state") + flags.StringVar(&config.BinaryPath, "binary-path", "/usr/local/bin/tofu", "The path of the terraform binary to use") flags.StringVar(&config.ExecutorCPULimit, "executor-cpu-limit", "", "The default CPU limit for the executor container (default is no limit)") flags.StringVar(&config.ExecutorCPURequest, "executor-cpu-request", "5m", "The default CPU request for the executor container") flags.StringVar(&config.ExecutorImage, "executor-image", fmt.Sprintf("ghcr.io/appvia/terranetes-executor:%s", version.Version), "The image to use for the executor") @@ -93,7 +94,7 @@ func main() { flags.StringVar(&config.TLSCert, "tls-cert", "tls.pem", "The name of the file containing the TLS certificate") flags.StringVar(&config.TLSDir, "tls-dir", "", "The directory the certificates are held") flags.StringVar(&config.TLSKey, "tls-key", "tls-key.pem", "The name of the file containing the TLS key") - flags.StringVar(&config.TerraformImage, "terraform-image", "hashicorp/terraform:latest", "The image to use for the terraform") + flags.StringVar(&config.TerraformImage, "terraform-image", "ghcr.io/opentofu/opentofu:latest", "The image to use for the terraform") crFlags := flag.NewFlagSet("controller-runtime", flag.ContinueOnError) zapOpts.BindFlags(crFlags) diff --git a/pkg/assets/job.yaml.tpl b/pkg/assets/job.yaml.tpl index a068856c6..e2eec7da9 100644 --- a/pkg/assets/job.yaml.tpl +++ b/pkg/assets/job.yaml.tpl @@ -1,4 +1,5 @@ --- +{{ $binary := .BinaryPath }} apiVersion: batch/v1 kind: Job metadata: @@ -122,10 +123,10 @@ spec: mountPath: /data - name: init - image: {{ .Images.Terraform }} + image: {{ .Images.Image }} workingDir: /data command: - - /bin/terraform + - {{ $binary }} args: - init env: @@ -195,7 +196,7 @@ spec: containers: - name: {{ .TerraformContainerName }} - image: {{ .Images.Terraform }} + image: {{ .Images.Image }} imagePullPolicy: {{ .ImagePullPolicy }} workingDir: /data command: @@ -203,9 +204,9 @@ spec: args: - --comment=Executing Terraform {{- if eq .Stage "plan" }} - - --command=/bin/terraform plan {{ .TerraformArguments }} -out=/run/plan.out -lock=false -no-color -input=false + - --command={{ $binary }} plan {{ .TerraformArguments }} -out=/run/plan.out -lock=false -no-color -input=false # We need to retain a uncompressed version, for checkov and infracosts - - --command=/bin/terraform show -json /run/plan.out > /run/tfplan.json + - --command={{ $binary }} show -json /run/plan.out > /run/tfplan.json - --command=/bin/cp /run/tfplan.json /run/plan.json - --command=/bin/gzip /run/plan.json - --command=/bin/mv /run/plan.json.gz /run/plan.json @@ -214,9 +215,9 @@ spec: - --upload=$(TERRAFORM_PLAN_OUT_NAME)=/run/plan.out {{- end }} {{- if eq .Stage "apply" }} - - --command=/bin/terraform apply {{ .TerraformArguments }} -lock=false -no-color -input=false -auto-approve + - --command={{ $binary }} apply {{ .TerraformArguments }} -lock=false -no-color -input=false -auto-approve {{- if .SaveTerraformState }} - - --command=/bin/terraform state pull > /run/tfstate + - --command={{ $binary }} state pull > /run/tfstate - --command=/bin/gzip /run/tfstate - --command=/bin/mv /run/tfstate.gz /run/tfstate - --namespace=$(KUBE_NAMESPACE) @@ -224,7 +225,7 @@ spec: {{- end }} {{- end }} {{- if eq .Stage "destroy" }} - - --command=/bin/terraform destroy {{ .TerraformArguments }} -auto-approve + - --command={{ $binary }} destroy {{ .TerraformArguments }} -auto-approve {{- end }} - --on-error=/run/steps/terraform.failed - --on-success=/run/steps/terraform.complete diff --git a/pkg/controller/configuration/controller.go b/pkg/controller/configuration/controller.go index da6fb97c5..5f5afa36b 100644 --- a/pkg/controller/configuration/controller.go +++ b/pkg/controller/configuration/controller.go @@ -72,6 +72,8 @@ type Controller struct { // BackoffLimit is the amount of times we are allowing a job to failed before deeming // it a failure BackoffLimit int + // BinaryPath is the name of the binary to use to run the terraform commands + BinaryPath string // EnableContextInjection enables the injection of the context into the terraform configuration // variables. This means we shall inject an number of default variables into the configuration // such as namespace, name and labels diff --git a/pkg/controller/configuration/delete.go b/pkg/controller/configuration/delete.go index faaf4f582..755036fe2 100644 --- a/pkg/controller/configuration/delete.go +++ b/pkg/controller/configuration/delete.go @@ -99,6 +99,7 @@ func (c *Controller) ensureTerraformDestroy(configuration *terraformv1alpha1.Con terraformv1alpha1.RetryAnnotation: configuration.GetAnnotations()[terraformv1alpha1.RetryAnnotation], }), BackoffLimit: c.BackoffLimit, + BinaryPath: c.BinaryPath, EnableInfraCosts: c.EnableInfracosts, ExecutorImage: c.ExecutorImage, ExecutorSecrets: c.ExecutorSecrets, @@ -106,7 +107,7 @@ func (c *Controller) ensureTerraformDestroy(configuration *terraformv1alpha1.Con InfracostsSecret: c.InfracostsSecretName, Namespace: c.ControllerNamespace, Template: state.jobTemplate, - TerraformImage: GetTerraformImage(configuration, c.TerraformImage), + Image: GetTerraformImage(configuration, c.TerraformImage), }) if err != nil { cond.Failed(err, "Failed to create the terraform destroy job") diff --git a/pkg/controller/configuration/ensure.go b/pkg/controller/configuration/ensure.go index 2f44c54cb..6d5ef6289 100644 --- a/pkg/controller/configuration/ensure.go +++ b/pkg/controller/configuration/ensure.go @@ -816,6 +816,7 @@ func (c *Controller) ensureTerraformPlan(configuration *terraformv1alpha1.Config terraformv1alpha1.JobTemplateHashLabel: state.jobTemplateHash, }), BackoffLimit: c.BackoffLimit, + BinaryPath: c.BinaryPath, DefaultExecutorCPULimit: c.DefaultExecutorCPULimit, DefaultExecutorCPURequest: c.DefaultExecutorCPURequest, DefaultExecutorMemoryLimit: c.DefaultExecutorMemoryLimit, @@ -823,6 +824,7 @@ func (c *Controller) ensureTerraformPlan(configuration *terraformv1alpha1.Config EnableInfraCosts: c.EnableInfracosts, ExecutorImage: c.ExecutorImage, ExecutorSecrets: c.ExecutorSecrets, + Image: GetTerraformImage(configuration, c.TerraformImage), InfracostsImage: c.InfracostsImage, InfracostsSecret: c.InfracostsSecretName, Namespace: c.ControllerNamespace, @@ -830,7 +832,6 @@ func (c *Controller) ensureTerraformPlan(configuration *terraformv1alpha1.Config PolicyImage: c.PolicyImage, SaveTerraformState: saveState, Template: state.jobTemplate, - TerraformImage: GetTerraformImage(configuration, c.TerraformImage), } // @step: use the options to generate the job @@ -1255,6 +1256,7 @@ func (c *Controller) ensureTerraformApply(configuration *terraformv1alpha1.Confi }, ), BackoffLimit: c.BackoffLimit, + BinaryPath: c.BinaryPath, DefaultExecutorCPULimit: c.DefaultExecutorCPULimit, DefaultExecutorCPURequest: c.DefaultExecutorCPURequest, DefaultExecutorMemoryLimit: c.DefaultExecutorMemoryLimit, @@ -1267,7 +1269,7 @@ func (c *Controller) ensureTerraformApply(configuration *terraformv1alpha1.Confi Namespace: c.ControllerNamespace, SaveTerraformState: saveState, Template: state.jobTemplate, - TerraformImage: GetTerraformImage(configuration, c.TerraformImage), + Image: GetTerraformImage(configuration, c.TerraformImage), }) if err != nil { cond.Failed(err, "Failed to create the terraform apply job") diff --git a/pkg/controller/configuration/reconcile_test.go b/pkg/controller/configuration/reconcile_test.go index 17d728439..ec9404a16 100644 --- a/pkg/controller/configuration/reconcile_test.go +++ b/pkg/controller/configuration/reconcile_test.go @@ -61,6 +61,7 @@ func makeFakeController(cc client.Client) *Controller { cache: cache.New(5*time.Minute, 10*time.Minute), recorder: recorder, BackoffLimit: 2, + BinaryPath: "/usr/local/bin/tofu", ControllerNamespace: "terraform-system", DefaultExecutorCPULimit: "1", DefaultExecutorCPURequest: "5m", @@ -71,7 +72,7 @@ func makeFakeController(cc client.Client) *Controller { ExecutorImage: "ghcr.io/appvia/terranetes-executor", InfracostsImage: "infracosts/infracost:latest", PolicyImage: "bridgecrew/checkov:2.0.1140", - TerraformImage: "hashicorp/terraform:1.1.9", + TerraformImage: "ghcr.io/opentofu/opentofu:latest", } return ctrl @@ -936,6 +937,7 @@ var _ = Describe("Configuration Controller", func() { kc: kfake.NewSimpleClientset(), cache: cache.New(5*time.Minute, 10*time.Minute), recorder: recorder, + BinaryPath: "/usr/local/bin/tofu", DefaultExecutorCPULimit: "1", DefaultExecutorCPURequest: "5m", DefaultExecutorMemoryLimit: "1Gi", @@ -946,7 +948,7 @@ var _ = Describe("Configuration Controller", func() { InfracostsImage: "infracosts/infracost:latest", ControllerNamespace: "default", PolicyImage: "bridgecrew/checkov:2.0.1140", - TerraformImage: "hashicorp/terraform:1.1.9", + TerraformImage: "ghcr.io/opentofu/opentofu:latest", } ctrl.cache.SetDefault(cfgNamespace, fixtures.NewNamespace(cfgNamespace)) } @@ -1426,7 +1428,7 @@ var _ = Describe("Configuration Controller", func() { Expect(cc.List(context.TODO(), list, client.InNamespace(ctrl.ControllerNamespace))).ToNot(HaveOccurred()) Expect(len(list.Items)).To(Equal(1)) - Expect(list.Items[0].Spec.Template.Spec.Containers[0].Image).To(Equal("hashicorp/terraform:test")) + Expect(list.Items[0].Spec.Template.Spec.Containers[0].Image).To(Equal("ghcr.io/opentofu/opentofu:test")) }) }) @@ -1442,7 +1444,7 @@ var _ = Describe("Configuration Controller", func() { Expect(cc.List(context.TODO(), list, client.InNamespace(ctrl.ControllerNamespace))).ToNot(HaveOccurred()) Expect(len(list.Items)).To(Equal(1)) - Expect(list.Items[0].Spec.Template.Spec.Containers[0].Image).To(Equal("hashicorp/terraform:1.1.9")) + Expect(list.Items[0].Spec.Template.Spec.Containers[0].Image).To(Equal("ghcr.io/opentofu/opentofu:latest")) }) }) }) @@ -2010,8 +2012,8 @@ terraform { expected := []string{ "--comment=Executing Terraform", - "--command=/bin/terraform plan --var-file variables.tfvars.json -out=/run/plan.out -lock=false -no-color -input=false", - "--command=/bin/terraform show -json /run/plan.out > /run/tfplan.json", + "--command=/usr/local/bin/tofu plan --var-file variables.tfvars.json -out=/run/plan.out -lock=false -no-color -input=false", + "--command=/usr/local/bin/tofu show -json /run/plan.out > /run/tfplan.json", "--command=/bin/cp /run/tfplan.json /run/plan.json", "--command=/bin/gzip /run/plan.json", "--command=/bin/mv /run/plan.json.gz /run/plan.json", @@ -3293,7 +3295,7 @@ terraform { expected := []string{ "--comment=Executing Terraform", - "--command=/bin/terraform apply --var-file variables.tfvars.json -lock=false -no-color -input=false -auto-approve", + "--command=/usr/local/bin/tofu apply --var-file variables.tfvars.json -lock=false -no-color -input=false -auto-approve", "--on-error=/run/steps/terraform.failed", "--on-success=/run/steps/terraform.complete", } diff --git a/pkg/server/server.go b/pkg/server/server.go index c40ec2103..eb48d065b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -173,6 +173,7 @@ func New(cfg *rest.Config, config Config) (*Server, error) { if err := (&configuration.Controller{ BackendTemplate: config.BackendTemplate, BackoffLimit: config.BackoffLimit, + BinaryPath: config.BinaryPath, ControllerJobLabels: jobLabels, ControllerNamespace: config.Namespace, DefaultExecutorCPULimit: config.ExecutorCPULimit, diff --git a/pkg/server/types.go b/pkg/server/types.go index 97e5cd0c5..449140e28 100644 --- a/pkg/server/types.go +++ b/pkg/server/types.go @@ -29,6 +29,8 @@ type Config struct { BackendTemplate string // BackoffLimit is the number of times we are willing to allow a job to fail BackoffLimit int + // BinaryPath is the name of the binary to use to run the terraform commands + BinaryPath string // ConfigurationThreshold is the max number of configurations we are willing // to run at the same time ConfigurationThreshold float64 diff --git a/pkg/utils/jobs/jobs.go b/pkg/utils/jobs/jobs.go index 48da9ae73..e96406d31 100644 --- a/pkg/utils/jobs/jobs.go +++ b/pkg/utils/jobs/jobs.go @@ -53,6 +53,8 @@ type Options struct { // BackoffLimit is the number of times we are willing to allow a job to fail // before we give up BackoffLimit int + // BinaryPath is the name of the binary to use to run the terraform commands + BinaryPath string // DefaultExecutorMemoryRequest is the default memory request for the executor DefaultExecutorMemoryRequest string // DefaultExecutorMemoryLimit is the default memory limit for the executor @@ -81,8 +83,8 @@ type Options struct { SaveTerraformState bool // Template is the source for the job template if overridden by the controller Template []byte - // TerraformImage is the image to use for the terraform jobs - TerraformImage string + // Image is the image to use for the terraform jobs + Image string } // Render is responsible for rendering the terraform configuration @@ -214,6 +216,7 @@ func (r *Render) createTerraformFromTemplate(options Options, stage string) (*ba terraformv1alpha1.ConfigurationStageLabel: stage, terraformv1alpha1.ConfigurationUIDLabel: string(r.configuration.GetUID()), }), + "BinaryPath": options.BinaryPath, "DefaultExecutorMemoryRequest": options.DefaultExecutorMemoryRequest, "DefaultExecutorMemoryLimit": options.DefaultExecutorMemoryLimit, "DefaultExecutorCPURequest": options.DefaultExecutorCPURequest, @@ -247,7 +250,7 @@ func (r *Render) createTerraformFromTemplate(options Options, stage string) (*ba "Images": map[string]interface{}{ "Executor": options.ExecutorImage, "Infracosts": options.InfracostsImage, - "Terraform": options.TerraformImage, + "Image": options.Image, "Policy": options.PolicyImage, }, "Secrets": map[string]interface{}{