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

feat(build): support installing terraform or providers in airgapped env #125

Merged
merged 2 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ porter mixin install terraform
## Build from source

Following commands build the terraform mixin.

```bash
git clone https://github.com/getporter/terraform-mixin.git
cd terraform-mixin
Expand All @@ -36,21 +37,38 @@ mixins:
clientVersion: 1.0.3
workingDir: myinfra
initFile: providers.tf
installHost: install.example.com
providerHost: providers.example.com
```

### clientVersion

The Terraform client version can be specified via the `clientVersion` configuration when declaring this mixin.

### workingDir

The `workingDir` configuration setting is the relative path to your terraform files. Defaults to "terraform".

### initFile
Terraform providers are installed into the bundle during porter build.

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.
> 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.

### installHost

Optional host that mirrors the official terraform installation at
`https://releases.hashicorp.com/*` in order to install in an air-gapped
environment.

### providerHost

Optional host to use as a network mirror when installing terraform providers.
This needs to conform to the [Terraform registry
protocol](https://www.terraform.io/docs/internals/provider-registry-protocol.html).

### User Agent Opt Out

When you declare the mixin, you can disable the mixin from customizing the azure user agent string
Expand Down Expand Up @@ -116,10 +134,9 @@ The specified path inside the installer (`/cnab/app/terraform/terraform.tfstate`

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.


## Terraform variables file

By default the mixin will create a default
By default the mixin will create a default
[`terraform.tfvars.json`](https://www.terraform.io/docs/language/values/variables.html#variable-definitions-tfvars-files)
file from the `vars` block during during the install step.

Expand Down
29 changes: 25 additions & 4 deletions pkg/terraform/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ ENV PORTER_TERRAFORM_MIXIN_USER_AGENT_OPT_OUT="{{ .UserAgentOptOut}}"
ENV AZURE_HTTP_USER_AGENT="{{ .AzureUserAgent }}"
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
apt-get update && apt-get install -y wget unzip && \
wget https://releases.hashicorp.com/terraform/{{.ClientVersion}}/terraform_{{.ClientVersion}}_linux_amd64.zip --progress=dot:giga && \
wget {{or .InstallHost "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
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
{{if .ProviderHost }}
tee <<EOF > provider_mirror.tfrc && terraform init -backend=false && mkdir -p /usr/local/share/terraform/plugins/ && cp --recursive .terraform/providers /usr/local/share/terraform/plugins/
provider_installation {
direct {
exclude = ["registry.terraform.io/*/*"]
}
network_mirror {
url = "{{ .ProviderHost }}"
}
}
EOF
{{ else }}
terraform init -backend=false && \
rm -fr .terraform/providers && \
terraform providers mirror /usr/local/share/terraform/plugins
{{ end }}
`

// BuildInput represents stdin passed to the mixin for the build command.
Expand All @@ -42,6 +55,14 @@ type MixinConfig struct {

InitFile string `yaml:"initFile,omitempty"`
WorkingDir string `yaml:"workingDir,omitempty"`

// Host from which to install `terraform`.
InstallHost string `yaml:"installHost,omitempty"`

// Host from which to download providers, i.e. a provider registry. See
// terraform provider registry documentation:
// https://developer.hashicorp.com/terraform/internals/provider-registry-protocol
ProviderHost string `yaml:"providerHost,omitempty"`
}

type buildConfig struct {
Expand Down
35 changes: 29 additions & 6 deletions pkg/terraform/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,29 @@ 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
expectedProviderHost 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=\"\"",
},
{
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 in airgrapped environment",
inputFile: "testdata/build-input-in-airgapped-env.yaml",
expectedVersion: "https://install.example.com/terraform/1.2.3/terraform_1.2.3_linux_amd64.zip",
expectedProviderHost: "https://providers.example.com",
},
}

for _, tc := range testcases {
Expand All @@ -43,6 +59,13 @@ func TestMixin_Build(t *testing.T) {
gotOutput := m.TestContext.GetOutput()
assert.Contains(t, gotOutput, tc.expectedVersion)
assert.Contains(t, gotOutput, tc.expectedUserAgent)

if tc.expectedProviderHost != "" {
assert.Contains(t, gotOutput, tc.expectedProviderHost)
} else {
assert.NotContains(t, gotOutput, "network_mirror")
}

assert.NotContains(t, "{{.", gotOutput, "Not all of the template values were consumed")
})
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/terraform/testdata/build-input-in-airgapped-env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
config:
clientVersion: 1.2.3
installHost: https://install.example.com
providerHost: https://providers.example.com/
Loading