From 00b0a4a1955e237e836311b0ceac1ddc962974c8 Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Wed, 20 Mar 2024 22:45:16 -0700 Subject: [PATCH 1/6] feat: Added working dirs as a mixin config at mixin and step levels Signed-off-by: Steven Gettys --- examples/multiple-mixin-configs/README.md | 13 +++ .../multiple-mixin-configs/infra1/main.tf | 4 + .../multiple-mixin-configs/infra1/outputs.tf | 0 .../infra1/variables.tf | 1 + .../multiple-mixin-configs/infra2/main.tf | 4 + .../multiple-mixin-configs/infra2/outputs.tf | 0 .../infra2/variables.tf | 0 examples/multiple-mixin-configs/porter.yaml | 92 +++++++++++++++++++ pkg/terraform/action.go | 4 + pkg/terraform/build.go | 19 +++- pkg/terraform/schema/schema.json | 46 ++++++++-- pkg/terraform/terraform.go | 22 ++++- 12 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 examples/multiple-mixin-configs/README.md create mode 100644 examples/multiple-mixin-configs/infra1/main.tf create mode 100644 examples/multiple-mixin-configs/infra1/outputs.tf create mode 100644 examples/multiple-mixin-configs/infra1/variables.tf create mode 100644 examples/multiple-mixin-configs/infra2/main.tf create mode 100644 examples/multiple-mixin-configs/infra2/outputs.tf create mode 100644 examples/multiple-mixin-configs/infra2/variables.tf create mode 100644 examples/multiple-mixin-configs/porter.yaml diff --git a/examples/multiple-mixin-configs/README.md b/examples/multiple-mixin-configs/README.md new file mode 100644 index 0000000..64b5bc6 --- /dev/null +++ b/examples/multiple-mixin-configs/README.md @@ -0,0 +1,13 @@ +# Basic Terraform Example Bundle + +This example demonstrates how to define and use variables and outputs of different data types in a bundle. + +## Try it out + +``` +cd examples/basic-tf-example +porter build +porter install +porter upgrade +porter uninstall +``` diff --git a/examples/multiple-mixin-configs/infra1/main.tf b/examples/multiple-mixin-configs/infra1/main.tf new file mode 100644 index 0000000..fc0181a --- /dev/null +++ b/examples/multiple-mixin-configs/infra1/main.tf @@ -0,0 +1,4 @@ +resource "local_file" "foo" { + content = "foo" + filename = "${path.module}/foo" +} diff --git a/examples/multiple-mixin-configs/infra1/outputs.tf b/examples/multiple-mixin-configs/infra1/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/multiple-mixin-configs/infra1/variables.tf b/examples/multiple-mixin-configs/infra1/variables.tf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/multiple-mixin-configs/infra1/variables.tf @@ -0,0 +1 @@ + diff --git a/examples/multiple-mixin-configs/infra2/main.tf b/examples/multiple-mixin-configs/infra2/main.tf new file mode 100644 index 0000000..df46343 --- /dev/null +++ b/examples/multiple-mixin-configs/infra2/main.tf @@ -0,0 +1,4 @@ +resource "local_file" "foo" { + content = "bar" + filename = "${path.module}/foo" +} diff --git a/examples/multiple-mixin-configs/infra2/outputs.tf b/examples/multiple-mixin-configs/infra2/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/multiple-mixin-configs/infra2/variables.tf b/examples/multiple-mixin-configs/infra2/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/multiple-mixin-configs/porter.yaml b/examples/multiple-mixin-configs/porter.yaml new file mode 100644 index 0000000..19e4cf5 --- /dev/null +++ b/examples/multiple-mixin-configs/porter.yaml @@ -0,0 +1,92 @@ +schemaVersion: 1.0.0 +name: mulitple-mixin-configs +version: 0.1.0 +registry: ghcr.io/getporter + +mixins: + - terraform: + workingDirs: + - infra1 + - infra2 + +install: + - terraform: + description: 'infra 1' + - terraform: + description: 'infra 2' + +upgrade: + - terraform: + description: 'Upgrade Terraform assets' + vars: + file_contents: ${bundle.parameters.file_contents} + map_var: ${bundle.parameters.map_var} + array_var: ${bundle.parameters.array_var} + boolean_var: ${bundle.parameters.boolean_var} + number_var: ${bundle.parameters.number_var} + json_encoded_html_string_var: ${bundle.parameters.json_encoded_html_string_var} + complex_object_var: ${bundle.parameters.complex_object_var} + outputs: + - name: file_contents + - name: map_var + - name: array_var + - name: boolean_var + - name: number_var + - name: json_encoded_html_string_var + - name: complex_object_var + +show: + - terraform: + description: "Invoke 'terraform show'" + +plan: + - terraform: + description: "Invoke 'terraform plan'" +# Note: this can't be 'version:' as this would conflict with top-level field +# Hence the need for the 'arguments:' override +printVersion: + - terraform: + description: "Invoke 'terraform version'" + arguments: + - 'version' + +uninstall: + - terraform: + description: 'Uninstall Terraform assets' + +outputs: + - name: file_contents + type: string + applyTo: + - install + - upgrade + - name: map_var + type: object + applyTo: + - install + - upgrade + - name: array_var + type: array + applyTo: + - install + - upgrade + - name: boolean_var + type: boolean + applyTo: + - install + - upgrade + - name: number_var + type: number + applyTo: + - install + - upgrade + - name: json_encoded_html_string_var + type: string + applyTo: + - install + - upgrade + - name: complex_object_var + type: object + applyTo: + - install + - upgrade diff --git a/pkg/terraform/action.go b/pkg/terraform/action.go index 3a59603..e187ca7 100644 --- a/pkg/terraform/action.go +++ b/pkg/terraform/action.go @@ -75,6 +75,9 @@ func (s Step) GetCommand() string { } func (s Step) GetWorkingDir() string { + if s.WorkingDir != "" { + return s.WorkingDir + } return "." } @@ -143,6 +146,7 @@ type TerraformFields struct { DisableVarFile bool `yaml:"disableVarFile,omitempty"` LogLevel string `yaml:"logLevel,omitempty"` BackendConfig map[string]interface{} `yaml:"backendConfig,omitempty"` + WorkingDir string `yaml:"workingDir,omitempty"` } type Output struct { diff --git a/pkg/terraform/build.go b/pkg/terraform/build.go index 88b7387..4cee4a5 100644 --- a/pkg/terraform/build.go +++ b/pkg/terraform/build.go @@ -17,11 +17,20 @@ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/ wget https://releases.hashicorp.com/terraform/{{.ClientVersion}}/terraform_{{.ClientVersion}}_linux_amd64.zip --progress=dot:giga && \ unzip terraform_{{.ClientVersion}}_linux_amd64.zip -d /usr/bin && \ rm terraform_{{.ClientVersion}}_linux_amd64.zip +{{if .WorkingDir}} COPY {{.WorkingDir}}/{{.InitFile}} $BUNDLE_DIR/{{.WorkingDir}}/ RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ terraform init -backend=false && \ rm -fr .terraform/providers && \ terraform providers mirror /usr/local/share/terraform/plugins +{{else if .WorkingDirs}} +{{range .WorkingDirs}} +RUN cd $BUNDLE_DIR/{{.}} && \ + terraform init -backend=false && \ + rm -fr .terraform/providers && \ + terraform providers mirror /usr/local/share/terraform/plugins +{{end}} +{{end}} ` // BuildInput represents stdin passed to the mixin for the build command. @@ -40,8 +49,9 @@ type MixinConfig struct { // UserAgentOptOut allows a bundle author to opt out from adding porter and the mixin's version to the terraform user agent string. UserAgentOptOut bool `yaml:"userAgentOptOut,omitempty"` - InitFile string `yaml:"initFile,omitempty"` - WorkingDir string `yaml:"workingDir,omitempty"` + InitFile string `yaml:"initFile,omitempty"` + WorkingDir string `yaml:"workingDir,omitempty"` + WorkingDirs []string `yaml:"workingDirs,omitempty"` } type buildConfig struct { @@ -63,6 +73,11 @@ func (m *Mixin) Build(ctx context.Context) error { return err } + // Check for mutual exclusivity of workingDir and workingDirs + if input.Config.WorkingDir != "" && len(input.Config.WorkingDirs) > 0 { + return errors.New("cannot specify both workingDir and workingDirs") + } + tmpl, err := template.New("Dockerfile").Parse(dockerfileLines) if err != nil { return errors.Wrapf(err, "error parsing terraform mixin Dockerfile template") diff --git a/pkg/terraform/schema/schema.json b/pkg/terraform/schema/schema.json index be8bf32..8eab37a 100644 --- a/pkg/terraform/schema/schema.json +++ b/pkg/terraform/schema/schema.json @@ -6,9 +6,13 @@ { "description": "Declare the terraform mixin without configuration", "type": "string", - "enum": ["terraform"] + "enum": [ + "terraform" + ] }, - {"$ref": "#/definitions/config"} + { + "$ref": "#/definitions/config" + } ] }, "config": { @@ -30,13 +34,38 @@ "workingDir": { "description": "Relative path to your terraform files, defaults to 'terraform'", "type": "string" + }, + "workingDirs": { + "description": "List of relative paths to different terraform working directories. Is mutually exclusive with 'workingDir'", + "type": "array", + "items": { + "type": "string" + } } }, - "additionalProperties": false + "additionalProperties": false, + "dependencies": { + "workingDir": { + "not": { + "required": [ + "workingDirs" + ] + } + }, + "workingDirs": { + "not": { + "required": [ + "workingDir" + ] + } + } + } } }, "additionalProperties": false, - "required": ["terraform"] + "required": [ + "terraform" + ] }, "installStep": { "type": "object", @@ -74,6 +103,9 @@ "backendConfig": { "type": "object" }, + "workingDir": { + "type": "string" + }, "description": { "$ref": "#/definitions/stepDescription" }, @@ -229,9 +261,11 @@ "$ref": "#/definitions/uninstallStep" } }, - "mixins": { + "mixins": { "type": "array", - "items": { "$ref": "#/definitions/declaration" } + "items": { + "$ref": "#/definitions/declaration" + } } }, "additionalProperties": { diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index d21aa34..d4f1479 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -143,8 +143,26 @@ func (m *Mixin) commandPreRun(ctx context.Context, step *Step) error { os.Setenv("TF_LOG", step.LogLevel) } - // First, change to specified working dir - m.Chdir(m.config.WorkingDir) + // Determine the working directory for this step. + // If the config has WorkingDirs set then validate that the step has WorkingDir set to one of the values + // in the configs "WorkingDirs" + if m.config.WorkingDirs != nil { + // Validate that the step working dir is one of the values in the mixin config + validDir := false + stepDir := step.GetWorkingDir() + for _, dir := range m.config.WorkingDirs { + if dir == stepDir { + validDir = true + break + } + } + if !validDir { + return fmt.Errorf("requested working dir %s not found in configs working dirs", stepDir) + } + m.Chdir(stepDir) + } else { + m.Chdir(m.config.WorkingDir) + } if m.DebugMode { fmt.Fprintln(m.Err, "Terraform working directory is", m.Getwd()) } From 6427a91c79670a7e737895ce90f5aa0c898237b0 Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Wed, 20 Mar 2024 23:23:07 -0700 Subject: [PATCH 2/6] test: Added unit tests Signed-off-by: Steven Gettys --- pkg/terraform/action_test.go | 27 ++++++++++++++++ pkg/terraform/build.go | 8 ++--- pkg/terraform/build_test.go | 32 +++++++++++++++---- ...uild-input-with-multiple-working-dirs.yaml | 9 ++++++ pkg/terraform/testdata/step-input.yaml | 1 + 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 pkg/terraform/testdata/build-input-with-multiple-working-dirs.yaml diff --git a/pkg/terraform/action_test.go b/pkg/terraform/action_test.go index 93ebcff..8ecd16d 100644 --- a/pkg/terraform/action_test.go +++ b/pkg/terraform/action_test.go @@ -34,6 +34,7 @@ func TestMixin_UnmarshalStep(t *testing.T) { assert.Equal(t, builder.NewFlag("backendConfig", "key=my.tfstate"), step.Flags[0]) assert.Equal(t, builder.NewFlag("logLevel", "TRACE"), step.Flags[1]) assert.Equal(t, builder.NewFlag("vars", "myvar=foo"), step.Flags[2]) + assert.Equal(t, "testDir", step.WorkingDir) } func TestApplyVarsToStepFlags(t *testing.T) { @@ -75,3 +76,29 @@ func TestApplyVarsToStepFlags(t *testing.T) { assert.Empty(t, gotFlags) }) } + +func TestStepGetWorkingDir_ReturnsValidDirectory(t *testing.T) { + tests := []struct { + name string + workingDir string + exp string + }{ + { + name: "Returns . if WorkingDir is empty", + workingDir: "", + exp: ".", + }, + { + name: "Returns value set in WorkingDir", + workingDir: "testDir", + exp: "testDir", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := Step{} + s.WorkingDir = test.workingDir + assert.Equal(t, test.exp, s.GetWorkingDir()) + }) + } +} diff --git a/pkg/terraform/build.go b/pkg/terraform/build.go index 4cee4a5..8bba467 100644 --- a/pkg/terraform/build.go +++ b/pkg/terraform/build.go @@ -25,6 +25,7 @@ RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ terraform providers mirror /usr/local/share/terraform/plugins {{else if .WorkingDirs}} {{range .WorkingDirs}} +COPY {{.}}/ $BUNDLE_DIR/{{.}}/ RUN cd $BUNDLE_DIR/{{.}} && \ terraform init -backend=false && \ rm -fr .terraform/providers && \ @@ -72,10 +73,9 @@ func (m *Mixin) Build(ctx context.Context) error { if err != nil { return err } - - // Check for mutual exclusivity of workingDir and workingDirs - if input.Config.WorkingDir != "" && len(input.Config.WorkingDirs) > 0 { - return errors.New("cannot specify both workingDir and workingDirs") + // If the WorkingDirs array is specified then clear the configs workingdir value and use that instead for the template + if len(input.Config.WorkingDirs) > 0 { + input.Config.WorkingDir = "" } tmpl, err := template.New("Dockerfile").Parse(dockerfileLines) diff --git a/pkg/terraform/build_test.go b/pkg/terraform/build_test.go index 3e9a31d..96d90c0 100644 --- a/pkg/terraform/build_test.go +++ b/pkg/terraform/build_test.go @@ -13,13 +13,32 @@ import ( func TestMixin_Build(t *testing.T) { testcases := []struct { - name string - inputFile string - expectedVersion string - expectedUserAgent string + name string + inputFile string + expectedVersion string + expectedUserAgent string + expectedDockerfileLines string }{ - {name: "build with custom config", inputFile: "testdata/build-input-with-config.yaml", expectedVersion: "https://releases.hashicorp.com/terraform/0.13.0-rc1/terraform_0.13.0-rc1_linux_amd64.zip", expectedUserAgent: "ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT=\"true\"\nENV AZURE_HTTP_USER_AGENT=\"\""}, - {name: "build with the default Terraform config", expectedVersion: "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_linux_amd64.zip", expectedUserAgent: "ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT=\"false\"\nENV AZURE_HTTP_USER_AGENT=\"getporter/porter getporter/terraform/v1.2.3"}, + { + name: "build with custom config", + inputFile: "testdata/build-input-with-config.yaml", + expectedVersion: "https://releases.hashicorp.com/terraform/0.13.0-rc1/terraform_0.13.0-rc1_linux_amd64.zip", + expectedUserAgent: "ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT=\"true\"\nENV AZURE_HTTP_USER_AGENT=\"\"", + expectedDockerfileLines: "COPY terraform/ $BUNDLE_DIR/terraform/\nRUN cd $BUNDLE_DIR/terraform && \\\n terraform init -backend=false && \\\n rm -fr .terraform/providers && \\\n terraform providers mirror /usr/local/share/terraform/plugins\n\n", + }, + { + name: "build with the default Terraform config", + expectedVersion: "https://releases.hashicorp.com/terraform/1.2.9/terraform_1.2.9_linux_amd64.zip", + expectedUserAgent: "ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT=\"false\"\nENV AZURE_HTTP_USER_AGENT=\"getporter/porter getporter/terraform/v1.2.3", + expectedDockerfileLines: "COPY terraform/ $BUNDLE_DIR/terraform/\nRUN cd $BUNDLE_DIR/terraform && \\\n terraform init -backend=false && \\\n rm -fr .terraform/providers && \\\n terraform providers mirror /usr/local/share/terraform/plugins\n\n", + }, + { + name: "build with mulitple terraform working dirs", + inputFile: "testdata/build-input-with-multiple-working-dirs.yaml", + expectedVersion: "https://releases.hashicorp.com/terraform/1.5.0/terraform_1.5.0_linux_amd64.zip", + expectedUserAgent: "ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT=\"true\"\nENV AZURE_HTTP_USER_AGENT=\"\"", + expectedDockerfileLines: "COPY testDir1/ $BUNDLE_DIR/testDir1/\nRUN cd $BUNDLE_DIR/testDir1 && \\\n terraform init -backend=false && \\\n rm -fr .terraform/providers && \\\n terraform providers mirror /usr/local/share/terraform/plugins\n\nCOPY testDir2/ $BUNDLE_DIR/testDir2/\nRUN cd $BUNDLE_DIR/testDir2 && \\\n terraform init -backend=false && \\\n rm -fr .terraform/providers && \\\n terraform providers mirror /usr/local/share/terraform/plugins\n\n\n", + }, } for _, tc := range testcases { @@ -44,6 +63,7 @@ func TestMixin_Build(t *testing.T) { assert.Contains(t, gotOutput, tc.expectedVersion) assert.Contains(t, gotOutput, tc.expectedUserAgent) assert.NotContains(t, "{{.", gotOutput, "Not all of the template values were consumed") + assert.Contains(t, gotOutput, tc.expectedDockerfileLines) }) } } diff --git a/pkg/terraform/testdata/build-input-with-multiple-working-dirs.yaml b/pkg/terraform/testdata/build-input-with-multiple-working-dirs.yaml new file mode 100644 index 0000000..0a80b11 --- /dev/null +++ b/pkg/terraform/testdata/build-input-with-multiple-working-dirs.yaml @@ -0,0 +1,9 @@ +config: + clientVersion: 1.5.0 + userAgentOptOut: true + workingDirs: + - 'testDir1' + - 'testDir2' + install: + - terraform: + description: 'noop' diff --git a/pkg/terraform/testdata/step-input.yaml b/pkg/terraform/testdata/step-input.yaml index 98ac0fd..c2f8d89 100644 --- a/pkg/terraform/testdata/step-input.yaml +++ b/pkg/terraform/testdata/step-input.yaml @@ -1,6 +1,7 @@ custom: - terraform: description: "Custom Action" + workingDir: "testDir" arguments: - "custom" flags: From f4048fb9ba3676206f60464ffd5ec4427913140c Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Sun, 24 Mar 2024 10:07:31 -0700 Subject: [PATCH 3/6] fix: Looping properly in the dockerfile contents for build Signed-off-by: Steven Gettys --- examples/multiple-mixin-configs/porter.yaml | 80 +++------------------ magefile.go | 15 ++-- pkg/terraform/build.go | 20 +++--- 3 files changed, 34 insertions(+), 81 deletions(-) diff --git a/examples/multiple-mixin-configs/porter.yaml b/examples/multiple-mixin-configs/porter.yaml index 19e4cf5..d760882 100644 --- a/examples/multiple-mixin-configs/porter.yaml +++ b/examples/multiple-mixin-configs/porter.yaml @@ -12,81 +12,23 @@ mixins: install: - terraform: description: 'infra 1' + workingDir: 'infra1' - terraform: description: 'infra 2' + workingDir: 'infra2' upgrade: - terraform: - description: 'Upgrade Terraform assets' - vars: - file_contents: ${bundle.parameters.file_contents} - map_var: ${bundle.parameters.map_var} - array_var: ${bundle.parameters.array_var} - boolean_var: ${bundle.parameters.boolean_var} - number_var: ${bundle.parameters.number_var} - json_encoded_html_string_var: ${bundle.parameters.json_encoded_html_string_var} - complex_object_var: ${bundle.parameters.complex_object_var} - outputs: - - name: file_contents - - name: map_var - - name: array_var - - name: boolean_var - - name: number_var - - name: json_encoded_html_string_var - - name: complex_object_var - -show: - - terraform: - description: "Invoke 'terraform show'" - -plan: + description: 'Upgrade infra 1 assets' + workingDir: 'infra1' - terraform: - description: "Invoke 'terraform plan'" -# Note: this can't be 'version:' as this would conflict with top-level field -# Hence the need for the 'arguments:' override -printVersion: - - terraform: - description: "Invoke 'terraform version'" - arguments: - - 'version' + description: 'Upgrade infra 2 assets' + workingDir: 'infra2' uninstall: - terraform: - description: 'Uninstall Terraform assets' - -outputs: - - name: file_contents - type: string - applyTo: - - install - - upgrade - - name: map_var - type: object - applyTo: - - install - - upgrade - - name: array_var - type: array - applyTo: - - install - - upgrade - - name: boolean_var - type: boolean - applyTo: - - install - - upgrade - - name: number_var - type: number - applyTo: - - install - - upgrade - - name: json_encoded_html_string_var - type: string - applyTo: - - install - - upgrade - - name: complex_object_var - type: object - applyTo: - - install - - upgrade + description: 'Uninstall infra 1 assets' + workingDir: 'infra1' + - terraform: + description: 'Uninstall infra 2 assets' + workingDir: 'infra2' diff --git a/magefile.go b/magefile.go index 790b646..624deed 100644 --- a/magefile.go +++ b/magefile.go @@ -3,15 +3,18 @@ package main import ( + "os" + "get.porter.sh/magefiles/mixins" "get.porter.sh/magefiles/porter" "github.com/carolynvs/magex/shx" ) const ( - mixinName = "terraform" - mixinPackage = "get.porter.sh/mixin/terraform" - mixinBin = "bin/mixins/" + mixinName + mixinName = "terraform" + mixinPackage = "get.porter.sh/mixin/terraform" + mixinBin = "bin/mixins/" + mixinName + DefaultPorterVersion = "v1.0.16" ) var ( @@ -68,7 +71,11 @@ func Clean() { // Install porter locally func EnsureLocalPorter() { porter.UseBinForPorterHome() - porter.EnsurePorter() + version := DefaultPorterVersion + if os.Getenv("PORTER_VERSION") != "" { + version = os.Getenv("PORTER_VERSION") + } + porter.EnsurePorterAt(version) } func TestIntegration() { diff --git a/pkg/terraform/build.go b/pkg/terraform/build.go index 8bba467..ab664cc 100644 --- a/pkg/terraform/build.go +++ b/pkg/terraform/build.go @@ -2,6 +2,7 @@ package terraform import ( "context" + "fmt" "text/template" "get.porter.sh/porter/pkg/exec/builder" @@ -17,16 +18,17 @@ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/ wget https://releases.hashicorp.com/terraform/{{.ClientVersion}}/terraform_{{.ClientVersion}}_linux_amd64.zip --progress=dot:giga && \ unzip terraform_{{.ClientVersion}}_linux_amd64.zip -d /usr/bin && \ rm terraform_{{.ClientVersion}}_linux_amd64.zip -{{if .WorkingDir}} -COPY {{.WorkingDir}}/{{.InitFile}} $BUNDLE_DIR/{{.WorkingDir}}/ -RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ +{{if .WorkingDirs}} +{{ $InitFile := .InitFile }} +{{range .WorkingDirs}} +COPY {{.}}/{{$InitFile}} $BUNDLE_DIR/{{.}}/ +RUN cd $BUNDLE_DIR/{{.}} && \ terraform init -backend=false && \ rm -fr .terraform/providers && \ terraform providers mirror /usr/local/share/terraform/plugins -{{else if .WorkingDirs}} -{{range .WorkingDirs}} -COPY {{.}}/ $BUNDLE_DIR/{{.}}/ -RUN cd $BUNDLE_DIR/{{.}} && \ +{{else}} +COPY {{.WorkingDir}}/{{.InitFile}} $BUNDLE_DIR/{{.WorkingDir}}/ +RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ terraform init -backend=false && \ rm -fr .terraform/providers && \ terraform providers mirror /usr/local/share/terraform/plugins @@ -75,7 +77,9 @@ func (m *Mixin) Build(ctx context.Context) error { } // If the WorkingDirs array is specified then clear the configs workingdir value and use that instead for the template if len(input.Config.WorkingDirs) > 0 { - input.Config.WorkingDir = "" + if m.DebugMode { + fmt.Fprintf(m.Err, "DEBUG: List of working dirs was provided, using :\n%v\n", input.Config.WorkingDirs) + } } tmpl, err := template.New("Dockerfile").Parse(dockerfileLines) From 0d6894c3108554e0f306e71f21be3b9b4acebb7a Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Sun, 24 Mar 2024 15:06:16 -0700 Subject: [PATCH 4/6] chore: Checking in working code with debugging Signed-off-by: Steven Gettys --- .../multiple-mixin-configs/infra1/main.tf | 4 +- .../multiple-mixin-configs/infra1/outputs.tf | 3 ++ .../infra1/variables.tf | 5 +- .../multiple-mixin-configs/infra2/main.tf | 4 +- .../multiple-mixin-configs/infra2/outputs.tf | 3 ++ .../infra2/variables.tf | 4 ++ examples/multiple-mixin-configs/porter.yaml | 50 ++++++++++++++++++- pkg/terraform/install.go | 2 + pkg/terraform/terraform.go | 20 +++----- 9 files changed, 75 insertions(+), 20 deletions(-) diff --git a/examples/multiple-mixin-configs/infra1/main.tf b/examples/multiple-mixin-configs/infra1/main.tf index fc0181a..d787415 100644 --- a/examples/multiple-mixin-configs/infra1/main.tf +++ b/examples/multiple-mixin-configs/infra1/main.tf @@ -1,4 +1,4 @@ resource "local_file" "foo" { - content = "foo" - filename = "${path.module}/foo" + content = var.infra1_var + filename = "${path.module}/infra1" } diff --git a/examples/multiple-mixin-configs/infra1/outputs.tf b/examples/multiple-mixin-configs/infra1/outputs.tf index e69de29..113b213 100644 --- a/examples/multiple-mixin-configs/infra1/outputs.tf +++ b/examples/multiple-mixin-configs/infra1/outputs.tf @@ -0,0 +1,3 @@ +output "infra1_output" { + value = var.infra1_var +} \ No newline at end of file diff --git a/examples/multiple-mixin-configs/infra1/variables.tf b/examples/multiple-mixin-configs/infra1/variables.tf index 8b13789..d8ff9fd 100644 --- a/examples/multiple-mixin-configs/infra1/variables.tf +++ b/examples/multiple-mixin-configs/infra1/variables.tf @@ -1 +1,4 @@ - +variable "infra1_var" { + type = string + description = "Variable for infra 1 working dir" +} diff --git a/examples/multiple-mixin-configs/infra2/main.tf b/examples/multiple-mixin-configs/infra2/main.tf index df46343..acb92a5 100644 --- a/examples/multiple-mixin-configs/infra2/main.tf +++ b/examples/multiple-mixin-configs/infra2/main.tf @@ -1,4 +1,4 @@ resource "local_file" "foo" { - content = "bar" - filename = "${path.module}/foo" + content = var.infra2_var + filename = "${path.module}/infra1" } diff --git a/examples/multiple-mixin-configs/infra2/outputs.tf b/examples/multiple-mixin-configs/infra2/outputs.tf index e69de29..d508e0a 100644 --- a/examples/multiple-mixin-configs/infra2/outputs.tf +++ b/examples/multiple-mixin-configs/infra2/outputs.tf @@ -0,0 +1,3 @@ +output "infra2_output" { + value = var.infra2_var +} \ No newline at end of file diff --git a/examples/multiple-mixin-configs/infra2/variables.tf b/examples/multiple-mixin-configs/infra2/variables.tf index e69de29..9f74fa9 100644 --- a/examples/multiple-mixin-configs/infra2/variables.tf +++ b/examples/multiple-mixin-configs/infra2/variables.tf @@ -0,0 +1,4 @@ +variable "infra2_var" { + type = string + description = "Variable for infra 2 working dir" +} diff --git a/examples/multiple-mixin-configs/porter.yaml b/examples/multiple-mixin-configs/porter.yaml index d760882..045176d 100644 --- a/examples/multiple-mixin-configs/porter.yaml +++ b/examples/multiple-mixin-configs/porter.yaml @@ -3,6 +3,21 @@ name: mulitple-mixin-configs version: 0.1.0 registry: ghcr.io/getporter +parameters: + - name: infra1_var + type: string + default: 'infra1' + applyTo: + - 'install' + - 'upgrade' + - 'uninstall' + - name: infra2_var + type: string + default: 'infra2' + applyTo: + - 'install' + - 'upgrade' + - 'uninstall' mixins: - terraform: workingDirs: @@ -13,22 +28,53 @@ install: - terraform: description: 'infra 1' workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} + outputs: + - name: infra1_output - terraform: description: 'infra 2' workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} + outputs: + - name: infra2_output upgrade: - terraform: description: 'Upgrade infra 1 assets' workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} + outputs: + - name: infra1_output - terraform: - description: 'Upgrade infra 2 assets' + description: 'infra 2' workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} + outputs: + - name: infra2_output uninstall: - terraform: description: 'Uninstall infra 1 assets' workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} - terraform: - description: 'Uninstall infra 2 assets' + description: 'infra 2' workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} +outputs: + - name: infra1_output + type: string + applyTo: + - 'install' + - 'upgrade' + - name: infra2_output + type: string + applyTo: + - 'install' + - 'upgrade' diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index e0da305..d4aa277 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -13,6 +13,8 @@ const defaultTerraformVarsFilename = "terraform.tfvars.json" // Install runs a terraform apply func (m *Mixin) Install(ctx context.Context) error { + fmt.Fprintf(m.Err, "\n\nIN INSTALL\n\n") + fmt.Printf("\n\nIN INSTALL\n\n") action, err := m.loadAction(ctx) if err != nil { return err diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index d4f1479..003e8af 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -142,25 +142,18 @@ func (m *Mixin) commandPreRun(ctx context.Context, step *Step) error { if step.LogLevel != "" { os.Setenv("TF_LOG", step.LogLevel) } - + fmt.Fprintf(m.Err, "\n\nSTEP:\n\n%+v\n\n", step) + fmt.Fprintf(m.Err, "\n\nCONFIG:\n\n%+v\n\n", m.config) // Determine the working directory for this step. // If the config has WorkingDirs set then validate that the step has WorkingDir set to one of the values // in the configs "WorkingDirs" - if m.config.WorkingDirs != nil { - // Validate that the step working dir is one of the values in the mixin config - validDir := false + if step.WorkingDir != "" { stepDir := step.GetWorkingDir() - for _, dir := range m.config.WorkingDirs { - if dir == stepDir { - validDir = true - break - } - } - if !validDir { - return fmt.Errorf("requested working dir %s not found in configs working dirs", stepDir) - } + fmt.Fprintf(m.Err, "\n\nCDing STEP WORKINGDIR to %v\n\n", stepDir) + // Validate that the step working dir is one of the values in the mixin config m.Chdir(stepDir) } else { + fmt.Fprintf(m.Err, "\n\nCDing CONFIG to %v\n\n", m.config.WorkingDir) m.Chdir(m.config.WorkingDir) } if m.DebugMode { @@ -169,6 +162,7 @@ func (m *Mixin) commandPreRun(ctx context.Context, step *Step) error { // Initialize Terraform fmt.Println("Initializing Terraform...") + fmt.Println("HELLO") err := m.Init(ctx, step.BackendConfig) if err != nil { return fmt.Errorf("could not init terraform, %s", err) From 66fade1bbedaf0482a5d439b6ccff68ac9323624 Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Mon, 25 Mar 2024 22:29:28 -0700 Subject: [PATCH 5/6] test: Added docs and integration test Signed-off-by: Steven Gettys --- README.md | 66 ++++++++++++------- .../multiple-mixin-configs/infra1/main.tf | 2 +- .../multiple-mixin-configs/infra2/main.tf | 2 +- examples/multiple-mixin-configs/porter.yaml | 14 ++++ pkg/terraform/build.go | 3 +- pkg/terraform/install.go | 2 - pkg/terraform/terraform.go | 9 +-- scripts/test/test-cli.sh | 27 ++++++++ 8 files changed, 88 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 901b372..d6ae87f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,16 @@ mixins: workingDir: myinfra initFile: providers.tf ``` +Or +```yaml +mixins: +- terraform: + clientVersion: 1.0.3 + workingDirs: + - infra1 + - infra2 + initFile: providers.tf +``` ### clientVersion The Terraform client version can be specified via the `clientVersion` configuration when declaring this mixin. @@ -44,11 +54,15 @@ The Terraform client version can be specified via the `clientVersion` configurat ### workingDir The `workingDir` configuration setting is the relative path to your terraform files. Defaults to "terraform". +### workingDirs +The `workingDirs` configuraiton setting is used when multiple terraform plans are part of a single bundle. When the `workingDirs` setting is specified then the `workingDir` setting is ignored. + ### initFile Terraform providers are installed into the bundle during porter build. We recommend that you put your provider declarations into a single file, e.g. "terraform/providers.tf". Then use `initFile` to specify the relative path to this file within workingDir. This will dramatically improve Docker image layer caching and performance when building, publishing and installing the bundle. +If `workingDirs` is specified instead of `workingDir` then the `initFile` must be the same in all of the terraform plans for the bundle. > Note: this approach isn't suitable when using terraform modules as those need to be "initilized" as well but aren't specified in the `initFile`. You shouldn't specifiy an `initFile` in this situation. ### User Agent Opt Out @@ -77,28 +91,7 @@ You can add your own custom strings to the user agent string by editing your [te ### Let Porter do the heavy lifting -The simplest way to use this mixin with Porter is to let Porter track the Terraform [state](https://www.terraform.io/docs/state/index.html) as actions are executed. This can be done via a parameter of type `file` that has a source of a corresponding output (of the same `file` type). Each time the bundle is executed, the output will capture the updated state file and inject it into the next action via its parameter correlate. - -Here is an example setup that works with Porter v0.38: - -```yaml -parameters: - - name: tfstate - type: file - # This designates the path within the installer to place the parameter value - path: /cnab/app/terraform/terraform.tfstate - # Here we tell Porter that the value for this parameter should come from the 'tfstate' output - source: - output: tfstate - -outputs: - - name: tfstate - type: file - # This designates the path within the installer to read the output from - path: /cnab/app/terraform/terraform.tfstate -``` - -If you are working with the Porter v1 prerelease, use the new state section: +The simplest way to use this mixin with Porter is to let Porter track the Terraform [state](https://www.terraform.io/docs/state/index.html) as actions are executed. This can be done via the state section: ```yaml state: @@ -108,10 +101,30 @@ state: path: terraform/terraform.tfvars.json ``` -The [TabbyCats Tracker bundle](https://github.com/carolynvs/tabbycat-demo) is a good example of how to use the terraform mixin with the Porter v1 prerelease. +The [TabbyCats Tracker bundle](https://github.com/carolynvs/tabbycat-demo) is a good example of how to use the terraform mixin with Porter v1. The specified path inside the installer (`/cnab/app/terraform/terraform.tfstate`) should be where Terraform will be looking to read/write its state. For a full example bundle using this approach, see the [basic-tf-example](examples/basic-tf-example). +Any arbitrary file can be added to the state including any files created by terraform during install or upgrade. + +When working with multiple different terraform plans in the same bundle make sure to specify the path to the corresponding plans state: + +```yaml +state: + - name: infra1-tfstate + path: infra1/terraform.tfstate + - name: infra1-tfvars + path: infra1/terraform.tfvars.json + - name: infra1-file + path: infra1/infra1-file + - name: infra2-tfstate + path: infra2/terraform.tfstate + - name: infra2-tfvars + path: infra2/terraform.tfvars.json + - name: infra2-file + path: infra2/infra2-file +``` + ### Remote Backends Alternatively, state can be managed by a remote backend. When doing so, each action step needs to supply the remote backend config via `backendConfig`. In the step examples below, the configuration has key/value pairs according to the [Azurerm](https://www.terraform.io/docs/backends/types/azurerm.html) backend. @@ -303,3 +316,10 @@ install: See the Porter [Outputs documentation](https://porter.sh/wiring/#outputs) on how to wire up outputs for use in a bundle. + + +## Multiple Terraform Plans In A Single Bundle + +Multiple terraform plans can be specified for a single bundle. When using the mixin with this configuration then every step **MUST** include a `workingDir` configuration setting so that porter can resolve the corresponding plan for that step at runtime. + +The `workingDir` and `workingDirs` configuration settings are mutally exclusive. If the `workingDirs` configuration setting is provided then anything set for `workingDir` will be ignored at bundle build time. \ No newline at end of file diff --git a/examples/multiple-mixin-configs/infra1/main.tf b/examples/multiple-mixin-configs/infra1/main.tf index d787415..6f06b7c 100644 --- a/examples/multiple-mixin-configs/infra1/main.tf +++ b/examples/multiple-mixin-configs/infra1/main.tf @@ -1,4 +1,4 @@ resource "local_file" "foo" { content = var.infra1_var - filename = "${path.module}/infra1" + filename = "${path.module}/infra1-file" } diff --git a/examples/multiple-mixin-configs/infra2/main.tf b/examples/multiple-mixin-configs/infra2/main.tf index acb92a5..fa25494 100644 --- a/examples/multiple-mixin-configs/infra2/main.tf +++ b/examples/multiple-mixin-configs/infra2/main.tf @@ -1,4 +1,4 @@ resource "local_file" "foo" { content = var.infra2_var - filename = "${path.module}/infra1" + filename = "${path.module}/infra2-file" } diff --git a/examples/multiple-mixin-configs/porter.yaml b/examples/multiple-mixin-configs/porter.yaml index 045176d..c85d337 100644 --- a/examples/multiple-mixin-configs/porter.yaml +++ b/examples/multiple-mixin-configs/porter.yaml @@ -78,3 +78,17 @@ outputs: applyTo: - 'install' - 'upgrade' + +state: + - name: infra1-tfstate + path: infra1/terraform.tfstate + - name: infra1-tfvars + path: infra1/terraform.tfvars.json + - name: infra1-file + path: infra1/infra1-file + - name: infra2-tfstate + path: infra2/terraform.tfstate + - name: infra2-tfvars + path: infra2/terraform.tfvars.json + - name: infra2-file + path: infra2/infra2-file diff --git a/pkg/terraform/build.go b/pkg/terraform/build.go index ab664cc..2b7f809 100644 --- a/pkg/terraform/build.go +++ b/pkg/terraform/build.go @@ -26,6 +26,7 @@ RUN cd $BUNDLE_DIR/{{.}} && \ terraform init -backend=false && \ rm -fr .terraform/providers && \ terraform providers mirror /usr/local/share/terraform/plugins +{{end}} {{else}} COPY {{.WorkingDir}}/{{.InitFile}} $BUNDLE_DIR/{{.WorkingDir}}/ RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ @@ -33,7 +34,6 @@ RUN cd $BUNDLE_DIR/{{.WorkingDir}} && \ rm -fr .terraform/providers && \ terraform providers mirror /usr/local/share/terraform/plugins {{end}} -{{end}} ` // BuildInput represents stdin passed to the mixin for the build command. @@ -81,7 +81,6 @@ func (m *Mixin) Build(ctx context.Context) error { fmt.Fprintf(m.Err, "DEBUG: List of working dirs was provided, using :\n%v\n", input.Config.WorkingDirs) } } - tmpl, err := template.New("Dockerfile").Parse(dockerfileLines) if err != nil { return errors.Wrapf(err, "error parsing terraform mixin Dockerfile template") diff --git a/pkg/terraform/install.go b/pkg/terraform/install.go index d4aa277..e0da305 100644 --- a/pkg/terraform/install.go +++ b/pkg/terraform/install.go @@ -13,8 +13,6 @@ const defaultTerraformVarsFilename = "terraform.tfvars.json" // Install runs a terraform apply func (m *Mixin) Install(ctx context.Context) error { - fmt.Fprintf(m.Err, "\n\nIN INSTALL\n\n") - fmt.Printf("\n\nIN INSTALL\n\n") action, err := m.loadAction(ctx) if err != nil { return err diff --git a/pkg/terraform/terraform.go b/pkg/terraform/terraform.go index 003e8af..31e18d3 100644 --- a/pkg/terraform/terraform.go +++ b/pkg/terraform/terraform.go @@ -142,18 +142,12 @@ func (m *Mixin) commandPreRun(ctx context.Context, step *Step) error { if step.LogLevel != "" { os.Setenv("TF_LOG", step.LogLevel) } - fmt.Fprintf(m.Err, "\n\nSTEP:\n\n%+v\n\n", step) - fmt.Fprintf(m.Err, "\n\nCONFIG:\n\n%+v\n\n", m.config) // Determine the working directory for this step. - // If the config has WorkingDirs set then validate that the step has WorkingDir set to one of the values - // in the configs "WorkingDirs" + // TODO: (gettys) this should validate against the mixin config configuration settings if step.WorkingDir != "" { stepDir := step.GetWorkingDir() - fmt.Fprintf(m.Err, "\n\nCDing STEP WORKINGDIR to %v\n\n", stepDir) - // Validate that the step working dir is one of the values in the mixin config m.Chdir(stepDir) } else { - fmt.Fprintf(m.Err, "\n\nCDing CONFIG to %v\n\n", m.config.WorkingDir) m.Chdir(m.config.WorkingDir) } if m.DebugMode { @@ -162,7 +156,6 @@ func (m *Mixin) commandPreRun(ctx context.Context, step *Step) error { // Initialize Terraform fmt.Println("Initializing Terraform...") - fmt.Println("HELLO") err := m.Init(ctx, step.BackendConfig) if err != nil { return fmt.Errorf("could not init terraform, %s", err) diff --git a/scripts/test/test-cli.sh b/scripts/test/test-cli.sh index e0fa2df..0c85a79 100755 --- a/scripts/test/test-cli.sh +++ b/scripts/test/test-cli.sh @@ -26,6 +26,7 @@ function verify-output() { } +##### Basic Example Test ##### # Copy terraform assets cp -r ${REPO_DIR}/examples/basic-tf-example/terraform . @@ -73,3 +74,29 @@ verify-output "json_encoded_html_string_var" '?new#conn&string$characters~!' verify-output "complex_object_var" '{"nested_object":{"internal_value":"https://new.connection.com?test&test=$hello"},"top_value":"https://my.updated.service?test=$id<>"}' ${PORTER_HOME}/porter uninstall --debug + +rm -rf * + +##### Multiple Working Dirs Test ##### +# Copy terraform assets +cp -r ${REPO_DIR}/examples/multiple-mixin-configs/ . + +${PORTER_HOME}/porter build +${PORTER_HOME}/porter install --verbosity=debug \ + --param infra1_var="foo" \ + --param infra2_var="bar" + +verify-output "infra1_output" "foo" +verify-output "infra2_output" "bar" + +${PORTER_HOME}/porter upgrade --verbosity=debug \ + --param infra1_var="upgradeFoo" \ + --param infra2_var="upgradeBar" + +verify-output "infra1_output" "upgradeFoo" +verify-output "infra2_output" "upgradeBar" + +${PORTER_HOME}/porter uninstall --verbosity=debug + + + From c0cd442d927ef714331bb0d17777d4c84b8e3c9b Mon Sep 17 00:00:00 2001 From: Steven Gettys Date: Mon, 25 Mar 2024 22:41:35 -0700 Subject: [PATCH 6/6] docs: Added example docs Signed-off-by: Steven Gettys --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6ae87f..9429d8e 100644 --- a/README.md +++ b/README.md @@ -318,8 +318,106 @@ See the Porter [Outputs documentation](https://porter.sh/wiring/#outputs) on how outputs for use in a bundle. -## Multiple Terraform Plans In A Single Bundle +### Multiple Terraform Plans In A Single Bundle Multiple terraform plans can be specified for a single bundle. When using the mixin with this configuration then every step **MUST** include a `workingDir` configuration setting so that porter can resolve the corresponding plan for that step at runtime. -The `workingDir` and `workingDirs` configuration settings are mutally exclusive. If the `workingDirs` configuration setting is provided then anything set for `workingDir` will be ignored at bundle build time. \ No newline at end of file +The `workingDir` and `workingDirs` configuration settings are mutally exclusive. If the `workingDirs` configuration setting is provided then anything set for `workingDir` will be ignored at bundle build time. + +```yaml +schemaVersion: 1.0.0 +name: mulitple-mixin-configs +version: 0.1.0 +registry: ghcr.io/getporter + +parameters: + - name: infra1_var + type: string + default: 'infra1' + applyTo: + - 'install' + - 'upgrade' + - 'uninstall' + - name: infra2_var + type: string + default: 'infra2' + applyTo: + - 'install' + - 'upgrade' + - 'uninstall' +mixins: + - terraform: + workingDirs: + - infra1 + - infra2 + +install: + - terraform: + description: 'infra 1' + workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} + outputs: + - name: infra1_output + - terraform: + description: 'infra 2' + workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} + outputs: + - name: infra2_output + +upgrade: + - terraform: + description: 'Upgrade infra 1 assets' + workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} + outputs: + - name: infra1_output + - terraform: + description: 'infra 2' + workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} + outputs: + - name: infra2_output + +uninstall: + - terraform: + description: 'Uninstall infra 1 assets' + workingDir: 'infra1' + vars: + infra1_var: ${bundle.parameters.infra1_var} + - terraform: + description: 'infra 2' + workingDir: 'infra2' + vars: + infra2_var: ${bundle.parameters.infra2_var} +outputs: + - name: infra1_output + type: string + applyTo: + - 'install' + - 'upgrade' + - name: infra2_output + type: string + applyTo: + - 'install' + - 'upgrade' + +state: + - name: infra1-tfstate + path: infra1/terraform.tfstate + - name: infra1-tfvars + path: infra1/terraform.tfvars.json + - name: infra1-file + path: infra1/infra1-file + - name: infra2-tfstate + path: infra2/terraform.tfstate + - name: infra2-tfvars + path: infra2/terraform.tfvars.json + - name: infra2-file + path: infra2/infra2-file + +``` \ No newline at end of file