From 7dd6547fd93d9de282012aa98a996900a3a59583 Mon Sep 17 00:00:00 2001 From: Sebastian Florek Date: Thu, 30 May 2024 17:49:18 -0500 Subject: [PATCH] feat(harness): add support for tf plural managed state (#203) * fix tf state struct * simplify resource links fn * add initial support for managed plural tf state * fix err msg * cleanup unused code * add ability to auto-wire creds and also hardcode actor to since the username is ignored * add matrix build for terraform versions --------- Co-authored-by: michaeljguarino --- .github/workflows/publish-harness.yaml | 31 ++++++++++++++++--- cmd/harness/main.go | 1 + go.mod | 2 +- go.sum | 6 ++-- pkg/harness/controller/controller.go | 4 +-- pkg/harness/controller/controller_hooks.go | 8 +++++ pkg/harness/controller/controller_options.go | 6 ++++ pkg/harness/controller/controller_types.go | 3 ++ pkg/harness/stackrun/v1/types.go | 20 +++++++++--- pkg/harness/tool/ansible/ansible.go | 5 +++ .../terraform/templates/_override.tf.gotmpl | 11 +++++++ pkg/harness/tool/terraform/terraform.go | 21 +++++++++++++ .../tool/terraform/terraform_templates.go | 31 +++++++++++++++++++ pkg/harness/tool/terraform/terraform_types.go | 1 + pkg/harness/tool/v1/types.go | 2 ++ 15 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 pkg/harness/tool/terraform/templates/_override.tf.gotmpl create mode 100644 pkg/harness/tool/terraform/terraform_templates.go diff --git a/.github/workflows/publish-harness.yaml b/.github/workflows/publish-harness.yaml index 508677e8..e57ceb0d 100644 --- a/.github/workflows/publish-harness.yaml +++ b/.github/workflows/publish-harness.yaml @@ -97,6 +97,29 @@ jobs: needs: [publish-harness-base] env: TERRAFORM_VERSION: 1.8.2 + strategy: + matrix: + versions: + - full: 1.8.2 + tag: 1.8.2 + - full: 1.8.2 + tag: "1.8" + - full: 1.7.5 + tag: '1.7' + - full: 1.6.6 + tag: '1.6' + - full: 1.5.7 + tag: '1.5' + - full: 1.4.7 + tag: '1.4' + - full: 1.3.10 + tag: '1.3' + - full: 1.2.9 + tag: '1.2' + - full: 1.1.9 + tag: '1.1' + - full: 1.0.11 + tag: '1.0' permissions: contents: write discussions: write @@ -116,9 +139,9 @@ jobs: ghcr.io/pluralsh/harness docker.io/pluralsh/harness tags: | - type=semver,pattern={{version}},suffix=-terraform-${{ env.TERRAFORM_VERSION }},priority=1000 - type=sha,suffix=-terraform-${{ env.TERRAFORM_VERSION }},priority=800 - type=ref,event=pr,suffix=-terraform-${{ env.TERRAFORM_VERSION }},priority=600 + type=semver,pattern={{version}},suffix=-terraform-${{ matrix.versions.tag }},priority=1000 + type=sha,suffix=-terraform-${{ matrix.versions.tag }},priority=800 + type=ref,event=pr,suffix=-terraform-${{ matrix.versions.tag }},priority=600 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -146,7 +169,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max build-args: | - TERRAFORM_IMAGE_TAG=${{ env.TERRAFORM_VERSION }} + TERRAFORM_IMAGE_TAG=${{ matrix.versions.full }} HARNESS_BASE_IMAGE_REPO=ghcr.io/pluralsh/stackrun-harness-base HARNESS_BASE_IMAGE_TAG=${{ needs.publish-harness-base.outputs.version }} diff --git a/cmd/harness/main.go b/cmd/harness/main.go index 3434dacf..3abcbf32 100644 --- a/cmd/harness/main.go +++ b/cmd/harness/main.go @@ -30,6 +30,7 @@ func main() { controller.WithStackRun(args.StackRunID()), controller.WithStackRunStepTimeout(args.Timeout()), controller.WithConsoleClient(consoleClient), + controller.WithConsoleToken(args.ConsoleToken()), controller.WithFetchClient(fetchClient), controller.WithWorkingDir(args.WorkingDir()), controller.WithSinkOptions( diff --git a/go.mod b/go.mod index ba8eaae9..432cd6bf 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/open-policy-agent/gatekeeper/v3 v3.15.1 github.com/orcaman/concurrent-map/v2 v2.0.1 github.com/pkg/errors v0.9.1 - github.com/pluralsh/console-client-go v0.5.10 + github.com/pluralsh/console-client-go v0.5.13 github.com/pluralsh/controller-reconcile-helper v0.0.4 github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34 github.com/pluralsh/polly v0.1.10 diff --git a/go.sum b/go.sum index 3c2fe096..b51db4ed 100644 --- a/go.sum +++ b/go.sum @@ -526,8 +526,10 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pluralsh/console-client-go v0.5.10 h1:2BnzzJ6w8bkDX/k55GaRykPTVOy66k/vojfhBAof5G8= -github.com/pluralsh/console-client-go v0.5.10/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= +github.com/pluralsh/console-client-go v0.5.12 h1:JuTxEXK1B60sE5yUMfgfn8/aus5i459MFMZs8FnX/LA= +github.com/pluralsh/console-client-go v0.5.12/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= +github.com/pluralsh/console-client-go v0.5.13 h1:HOmkho5aaU42f6PkSb+BUFjhCJKnL5jceLZiT16HMBE= +github.com/pluralsh/console-client-go v0.5.13/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= github.com/pluralsh/controller-reconcile-helper v0.0.4 h1:1o+7qYSyoeqKFjx+WgQTxDz4Q2VMpzprJIIKShxqG0E= github.com/pluralsh/controller-reconcile-helper v0.0.4/go.mod h1:AfY0gtteD6veBjmB6jiRx/aR4yevEf6K0M13/pGan/s= github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34 h1:ab2PN+6if/Aq3/sJM0AVdy1SYuMAnq4g20VaKhTm/Bw= diff --git a/pkg/harness/controller/controller.go b/pkg/harness/controller/controller.go index 6762c4d2..49355af4 100644 --- a/pkg/harness/controller/controller.go +++ b/pkg/harness/controller/controller.go @@ -38,12 +38,12 @@ func (in *stackRunController) Start(ctx context.Context) (retErr error) { } }() - in.preStart() - if err := in.prepare(); err != nil { return err } + in.preStart() + // Add executables to executor for _, e := range in.executables(ctx) { if err := in.executor.Add(e); err != nil { diff --git a/pkg/harness/controller/controller_hooks.go b/pkg/harness/controller/controller_hooks.go index 63a7044e..45afb8d1 100644 --- a/pkg/harness/controller/controller_hooks.go +++ b/pkg/harness/controller/controller_hooks.go @@ -29,6 +29,14 @@ func (in *stackRunController) preStart() { if err := stackrun.StartStackRun(in.consoleClient, in.stackRunID); err != nil { klog.ErrorS(err, "could not update stack run status") } + + if in.stackRun.ManageState { + err := in.tool.ConfigureStateBackend("harness", in.consoleToken, in.stackRun.StateUrls) + if err != nil { + // TODO: Should this be a fatal error? + klog.Fatalf("could not configure state backend: %v", err) + } + } } // postStart function is executed after all stack run steps. diff --git a/pkg/harness/controller/controller_options.go b/pkg/harness/controller/controller_options.go index ac5efc05..9e628418 100644 --- a/pkg/harness/controller/controller_options.go +++ b/pkg/harness/controller/controller_options.go @@ -43,3 +43,9 @@ func WithStackRunStepTimeout(timeout time.Duration) Option { s.stackRunStepTimeout = timeout } } + +func WithConsoleToken(token string) Option { + return func(s *stackRunController) { + s.consoleToken = token + } +} diff --git a/pkg/harness/controller/controller_types.go b/pkg/harness/controller/controller_types.go index e571221c..f97e08c5 100644 --- a/pkg/harness/controller/controller_types.go +++ b/pkg/harness/controller/controller_types.go @@ -41,6 +41,9 @@ type stackRunController struct { // consoleClient consoleClient console.Client + // consoleToken + consoleToken string + // fetchClient fetchClient helpers.FetchClient diff --git a/pkg/harness/stackrun/v1/types.go b/pkg/harness/stackrun/v1/types.go index a546dd5e..fbde49d9 100644 --- a/pkg/harness/stackrun/v1/types.go +++ b/pkg/harness/stackrun/v1/types.go @@ -4,6 +4,7 @@ import ( "fmt" gqlclient "github.com/pluralsh/console-client-go" + "github.com/samber/lo" ) type StackRun struct { @@ -17,6 +18,9 @@ type StackRun struct { ExecWorkDir *string Approval bool ApprovedAt *string + ManageState bool + Creds *gqlclient.StackRunBaseFragment_PluralCreds + StateUrls *gqlclient.StackRunBaseFragment_StateUrls } func (in *StackRun) FromStackRunBaseFragment(fragment *gqlclient.StackRunBaseFragment) *StackRun { @@ -31,19 +35,27 @@ func (in *StackRun) FromStackRunBaseFragment(fragment *gqlclient.StackRunBaseFra Approval: fragment.Approval != nil && *fragment.Approval, ApprovedAt: fragment.ApprovedAt, ExecWorkDir: fragment.Workdir, + ManageState: fragment.ManageState != nil && *fragment.ManageState, + Creds: fragment.PluralCreds, + StateUrls: fragment.StateUrls, } } // Env parses the StackRun.Environment as a list of strings. -// Each entry is of the form "key=value". +// Each entry is of the form "key=value". Automatically adds Plural env vars if creds were configured. func (in *StackRun) Env() []string { - result := make([]string, len(in.Environment)) + env := make([]string, len(in.Environment)) for i, e := range in.Environment { - result[i] = fmt.Sprintf("%s=%s", e.Name, e.Value) + env[i] = fmt.Sprintf("%s=%s", e.Name, e.Value) } - return result + if in.Creds != nil { + env = append(env, fmt.Sprintf("PLURAL_CONSOLE_TOKEN=%s", lo.FromPtr(in.Creds.Token))) + env = append(env, fmt.Sprintf("PLURAL_CONSOLE_URL=%s", lo.FromPtr(in.Creds.URL))) + } + + return env } type Lifecycle string diff --git a/pkg/harness/tool/ansible/ansible.go b/pkg/harness/tool/ansible/ansible.go index c5d89898..58513e7a 100644 --- a/pkg/harness/tool/ansible/ansible.go +++ b/pkg/harness/tool/ansible/ansible.go @@ -26,6 +26,11 @@ func (in *Ansible) Modifier(stage console.StepStage) v1.Modifier { panic("implement me") } +func (in *Ansible) ConfigureStateBackend(actor, deployToken string, urls *console.StackRunBaseFragment_StateUrls) error { + //TODO implement me + panic("implement me") +} + func New(dir string) *Ansible { return &Ansible{dir: dir} } diff --git a/pkg/harness/tool/terraform/templates/_override.tf.gotmpl b/pkg/harness/tool/terraform/templates/_override.tf.gotmpl new file mode 100644 index 00000000..9a3b840c --- /dev/null +++ b/pkg/harness/tool/terraform/templates/_override.tf.gotmpl @@ -0,0 +1,11 @@ +terraform { + backend "http" { + address = "{{ .Address }}" + lock_address = "{{ .LockAddress }}" + lock_method = "POST" + unlock_address = "{{ .UnlockAddress }}" + unlock_method = "POST" + username = "{{ .Actor }}" + password = "{{ .DeployToken }}" + } +} diff --git a/pkg/harness/tool/terraform/terraform.go b/pkg/harness/tool/terraform/terraform.go index 2f6d373a..ce5b2842 100644 --- a/pkg/harness/tool/terraform/terraform.go +++ b/pkg/harness/tool/terraform/terraform.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "path" console "github.com/pluralsh/console-client-go" "github.com/pluralsh/polly/algorithms" @@ -78,6 +79,26 @@ func (in *Terraform) Modifier(stage console.StepStage) v1.Modifier { return v1.NewProxyModifier() } +func (in *Terraform) ConfigureStateBackend(actor, deployToken string, urls *console.StackRunBaseFragment_StateUrls) error { + input := &OverrideTemplateInput{ + Address: lo.FromPtr(urls.Terraform.Address), + LockAddress: lo.FromPtr(urls.Terraform.Lock), + UnlockAddress: lo.FromPtr(urls.Terraform.Unlock), + Actor: actor, + DeployToken: deployToken, + } + fileName, content, err := overrideTemplate(input) + if err != nil { + return err + } + + if err = helpers.File().Create(path.Join(in.dir, fileName), content); err != nil { + return fmt.Errorf("failed configuring state backend file %q: %w", fileName, err) + } + + return nil +} + func (in *Terraform) resource(r v4.Resource) *console.StackStateResourceAttributes { return &console.StackStateResourceAttributes{ Identifier: r.Address, diff --git a/pkg/harness/tool/terraform/terraform_templates.go b/pkg/harness/tool/terraform/terraform_templates.go new file mode 100644 index 00000000..1c24ed68 --- /dev/null +++ b/pkg/harness/tool/terraform/terraform_templates.go @@ -0,0 +1,31 @@ +package terraform + +import ( + _ "embed" + "strings" + "text/template" +) + +//go:embed templates/_override.tf.gotmpl +var overrideTemplateText string + +type OverrideTemplateInput struct { + Address string + LockAddress string + UnlockAddress string + Actor string + DeployToken string +} + +func overrideTemplate(input *OverrideTemplateInput) (fileName, content string, err error) { + tplName := "_override.tf" + tmpl, err := template.New(tplName).Parse(overrideTemplateText) + if err != nil { + return "", "", err + } + + out := new(strings.Builder) + err = tmpl.Execute(out, input) + + return tplName, out.String(), err +} diff --git a/pkg/harness/tool/terraform/terraform_types.go b/pkg/harness/tool/terraform/terraform_types.go index 16456ac5..d95d2c42 100644 --- a/pkg/harness/tool/terraform/terraform_types.go +++ b/pkg/harness/tool/terraform/terraform_types.go @@ -9,3 +9,4 @@ type Terraform struct { // Default: terraform.tfplan planFileName string } + diff --git a/pkg/harness/tool/v1/types.go b/pkg/harness/tool/v1/types.go index 1253cff7..08eb5643 100644 --- a/pkg/harness/tool/v1/types.go +++ b/pkg/harness/tool/v1/types.go @@ -21,6 +21,8 @@ type Tool interface { // created by specific tool after all steps are finished running. It then // transforms this information into gqlclient.StackOutputAttributes. Output() ([]*console.StackOutputAttributes, error) + // ConfigureStateBackend ... + ConfigureStateBackend(actor, deployToken string, urls *console.StackRunBaseFragment_StateUrls) error // Modifier returns specific modifier implementation based on the // current step stage. Modifiers can for example alter arguments of the // executable step command.