GithubHelp home page GithubHelp logo

getporter / terraform-mixin Goto Github PK

View Code? Open in Web Editor NEW
16.0 7.0 19.0 2.08 MB

A Terraform Mixin for Porter

Home Page: https://getporter.org/mixins/terraform

License: Apache License 2.0

Go 92.99% Shell 7.01%
porter terraform mixin

terraform-mixin's Introduction

Terraform Mixin for Porter

This is a Terraform mixin for Porter.

Build Status

Install via Porter

This will install the latest mixin release via the Porter CLI.

porter mixin install terraform

Build from source

Following commands build the terraform mixin.

git clone https://github.com/getporter/terraform-mixin.git
cd terraform-mixin
# Learn about Mage in our CONTRIBUTING.md
go run mage.go EnsureMage
mage build

Then, to install the resulting mixin into PORTER_HOME, execute mage install

Mixin Configuration

mixins:
- terraform:
    clientVersion: 1.0.3
    workingDir: myinfra
    initFile: providers.tf

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

User Agent Opt Out

When you declare the mixin, you can disable the mixin from customizing the azure user agent string

mixins:
- terraform:
    userAgentOptOut: true

By default, the terraform mixin adds the porter and mixin version to the user agent string used by the azure provider. We use this to understand which version of porter and the mixin are being used by a bundle, and assist with troubleshooting. Below is an example of what the user agent string looks like:

AZURE_HTTP_USER_AGENT="getporter/porter/v1.0.0 getporter/terraform/v1.2.3"

You can add your own custom strings to the user agent string by editing your template Dockerfile and setting the AZURE_HTTP_USER_AGENT environment variable.

Terraform state

Let Porter do the heavy lifting

The simplest way to use this mixin with Porter is to let Porter track the Terraform state 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:

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:

state:
  - name: tfstate
    path: terraform/terraform.tfstate
  - name: tfvars
    path: terraform/terraform.tfvars.json

The TabbyCats Tracker bundle is a good example of how to use the terraform mixin with the Porter v1 prerelease.

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.

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

Terraform variables file

By default the mixin will create a default terraform.tfvars.json file from the vars block during during the install step.

To use this file, a tfvars file parameter and output must be added to persist it for subsequent steps.

This can be disabled by setting disableVarFile to true during install.

Here is an example setup using the tfvar file:

parameters:
  - name: tfvars
    type: file
    # This designates the path within the installer to place the parameter value
    path: /cnab/app/terraform/terraform.tfvars.json
    # Here we tell Porter that the value for this parameter should come from the 'tfvars' output
    source:
      output: tfvars
  - name: foo
    type: string
    applyTo:
      - install 
  - name: baz
    type: string
    default: blaz
    applyTo:
      - install 

outputs:
  - name: tfvars
    type: file
    # This designates the path within the installer to read the output from
    path: /cnab/app/terraform/terraform.tfvars.json
    
install:
  - terraform:
      description: "Install Azure Key Vault"
      vars:
        foo: bar
        baz: biz
      outputs:
      - name: vault_uri
upgrade: # No var block required
  - terraform:
      description: "Install Azure Key Vault"
      outputs:
      - name: vault_uri
uninstall: # No var block required
  - terraform:
      description: "Install Azure Key Vault"
      outputs:
      - name: vault_uri

and with var file disabled

parameters:
  - name: foo
    type: string
    applyTo:
      - install 
  - name: baz
    type: string
    default: blaz
    applyTo:
      - install 

install:
  - terraform:
      description: "Install Azure Key Vault"
      disableVarFile: true
      vars:
        foo: bar
        baz: biz
      outputs:
      - name: vault_uri
uninstall: # Var block required
  - terraform:
      description: "Install Azure Key Vault"
      vars:
        foo: bar
        baz: biz

Examples

Install

install:
  - terraform:
      description: "Install Azure Key Vault"
      backendConfig:
        key: "mybundle.tfstate"
        storage_account_name: "mystorageacct"
        container_name: "mycontainer"
        access_key: "myaccesskey"
      outputs:
      - name: vault_uri

Upgrade

upgrade:
  - terraform:
      description: "Upgrade Azure Key Vault"
      backendConfig:
        key: "mybundle.tfstate"
        storage_account_name: "mystorageacct"
        container_name: "mycontainer"
        access_key: "myaccesskey"
      outputs:
      - name: vault_uri

Invoke

An invoke step is used for any custom action (not one of install, upgrade or uninstall).

By default, the command given to terraform will be the step name. Here it is show, resulting in terraform show with the provided configuration.

show:
  - terraform:
      description: "Invoke 'terraform show'"
      backendConfig:
        key: "mybundle.tfstate"
        storage_account_name: "mystorageacct"
        container_name: "mycontainer"
        access_key: "myaccesskey"

Or, if the step name does not match the intended terraform command, the command can be supplied via the arguments: section, like so:

printVersion:
  - terraform:
      description: "Invoke 'terraform version'"
      arguments:
        - version

Uninstall

uninstall:
  - terraform:
      description: "Uninstall Azure Key Vault"
      backendConfig:
        key: "mybundle.tfstate"
        storage_account_name: "mystorageacct"
        container_name: "mycontainer"
        access_key: "myaccesskey"

See further examples in the Examples directory

Step Outputs

As seen above, outputs can be declared for a step. All that is needed is the name of the output.

For each output listed, terraform output <output name> is invoked to fetch the output value from the state file for use by Porter. Outputs can be saved to the filesystem so that subsequent steps can use the file by specifying the destinationFile field. This is particularly useful when your terraform module creates a Kubernetes cluster. In the example below, the module creates a cluster, and then writes the kubeconfig to /root/.kube/config so that the rest of the bundle can immediately use the cluster.

install:
  - terraform:
      description: "Create a Kubernetes cluster"
      outputs:
      - name: kubeconfig
        destinationFile: /root/.kube/config

See the Porter Outputs documentation on how to wire up outputs for use in a bundle.

terraform-mixin's People

Contributors

bdegeeter avatar carolynvs avatar carolynvs-msft avatar chris-crone avatar dependabot[bot] avatar jdolitsky avatar jeremyrickard avatar jsevedge avatar mchorfa avatar sestegra avatar tamirkamara avatar thorstenhans avatar vdice avatar vinozzz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

terraform-mixin's Issues

Allow saving output immediately as a file

In some cases I need to use an output immediately as a file, for example:

  1. run terraform to create a kubernetes cluster
  2. run helm to install a release on that cluster.

To do this without fussing with the exec mixin, the terraform outputs should be able to be sent to a file like so:

install:
  - terraform:
      description: "Create infrastructure"
      vars:
        installation: "{{ installation.name}}"
        location: "{{ bundle.parameters.location }}"
      outputs:
        - name: connstr
        - name: kubeconfig
          destinationFile: /root/.kube/config

Support specifying the configuration directory

Right now, the mixin has the configuration directory hard coded. I think that we should allow users to specify this for a few reasons:

1.) The JSON schema include that, so tooling can prompt users, so there is less "magic" to know
2.) We can enable people to use the mixin more than once in a single bundle (for complex scenarios)
3.) In general it just gives people more flexibility.

Ampersands in Terraform Outputs are being Encoded

Hi there,

In PR #91 there was a change to the way that this mixin grabs output from Terraform. Specifically, the mixin now requests output values as json rather than raw format. Here is the change in question:

https://github.com/getporter/terraform-mixin/pull/91/files#diff-282757e263fe02ec96e35fd1120b9263db1ca315d75cea25d3ccf68239bd7472L56

Unfortunately, due to some strange formatting rules within Terraform, this breaks any URI that includes an ampersand (i.e. URIs or URLs that have querystring parameters) and this is a common case for connection strings or service endpoints.

My investigation into an issue has been written up here, and includes a small repro sample that demonstrates the issue:
microsoft/AzureTRE#2549 (comment)

There is a closed Terraform issue describes this behaviour and it was closed by simply adding additional documentation to explain the behaviour (and a comment that they will never change this behaviour):
hashicorp/terraform#26110

The fix was to better document it:
hashicorp/terraform#26122

Can the terraform-mixin either switch back to using the -raw flag when accessing TF outputs? I'm not sure what the alternative is (I'm not sure how safe it would be to patch up/replace these encoded characters)

Bump Terraform Version

Currently, the mixin is currently using a dated Terraform version. Terraform CLI is currently available in version 1.0.4.

The terraform-mixin should use the most recent version

Using a remote backend should be a user decision, not a bundle author decision

When the author writes the bundle, they don't know if the end user will have existing terraform accounts/infra. Instead of having the bundle decide if it uses a remote backend or not, it should be optional configuration that the end user may supply when running the bundle. If it's not specified, then bundle state is used instead.

I'm not sure yet what this should look like, more design work is needed, but I wanted to get this out there that we are putting this configuration in the wrong spot.

Custom action: Add support for arbitrary arguments/flags

This mixin currently has rudimentary custom action support for use in a porter.yaml manifest, e.g.

customActions:
  plan:
    description: "Invoke 'terraform plan'"
    modifies: false
    stateless: true
...
plan:
  - terraform:
      description: "Invoke 'terraform plan'"
      # by default, the mixin will call terraform with a command
      # with the same name as the custom action; however, we can
      # override with:
      command: "plan"

However, a custom action might require an arbitrary set of args/options to send along to terraform. Therefore, we have a need to add an additional args block to support this... perhaps something like:

plan:
  - terraform:
      description: "Invoke 'terraform plan'"
      command: "plan"
      args:
        - "-var foo=bar"
        - "-refresh=true"
...

Or, similar to how the exec mixin does things, a flags block:

plan:
  - terraform:
      description: "Invoke 'terraform plan'"
      command: "plan"
      flags:
        var: foo=bar
        refresh: true
...

Convert script-based cli tests to integration tests

The terraform bundle cli tests were ported over from the main Porter repo in #39.

It would be preferable to convert this into an integration test (see the tests dir, currenly holding the one schema integration test). Makefile logic/cruft dealing with setup for the script (and the script itself) can then be removed.

Add user agent string for AWS provider

#105 adds support for setting the user agent string used by the terraform azure provider to include info about porter. We should do the same for any other interested providers. Since I've seen the aws mixin uses an environment variable to set the user agent string too (getporter/aws-mixin#34), let's start there and populate the aws user agent environment variable like we do with the azure one.

Add ability to specify remote store/backend

Add ability to use a remote backend. This is currently a necessity to fetch and update state between CNAB action runs.

This may include a refactor to allow all lifecycle actions (install, uninstall, upgrade) to run terraform init prior to the lifecycle action, perhaps as a default. (Currently only install runs init and this is explicitly specified by the porter yaml...)

For an example using an Azure Storage backend, see the example bundle in https://github.com/deislabs/example-bundles/tree/master/aks-terraform

Terraform version is bound to the mixin version... how to decouple?

Hello porter-heads ๐Ÿ‘‹

Does the team has any thoughts on controlling the version of terraform within the mixin or the mixin version within porter.yaml? Ideally, I want to keep on top of terraform releases without having to wait for porter-terraform mixin releases (e.g. currently porter-terraform is 3 minor versions behind) but with the current mixin artifact shipping the terraform runtime as is done today (and being new to porter), I'm not sure how that could be achieved. Since the stack is very docker-centric, I wonder if pulling a terraform image at porter build or runtime would be a potential approach. Thoughts? Maybe I've missed how this can be controlled through configuration. If so, please point me to that and I'd gladly help document it.

Thanks for the fine work here!

Make the working directory configurable per command

I recently added working directory as a mixin level config, which lets the user override the default directory with all their terraform files. #61 Sometimes people have multiple terraform modules though, and its not possible to use more than one in the same bundle. We should add working directory to the terraform mixin command so that you can do something like this:

install:
  - terraform:
       workingDir: mymodule1
  - terraform:
       workingDir: mymodule2

Auto-approve should always be set to true and not be a field

Is there ever a case where the auto-approve flag should be set to false? It seems to control if the CLI should be in interactive mode or not, and the mixin always needs to run in unattended mode. In other mixins, we have been setting flags that always need to be set automatically for the user. If that's the case for this flag here, I suggest that we just set it inside the mixin and not expose it.

Update the mixin to compile against porter 1.0.0-rc.2

  • Use go 1.18.6 (go.mod file and the build file)
  • Update go.mod to use porter rc.1
  • Rename pkg/context to pkg/portercontext
  • m.Debug -> m.DebugMode
  • Update main to start a root logger (see pkg/exec/main.go in porter's repo for an example)
  • Embed a RuntimeConfig instead of a portercontext.Context
  • Update the magefile

String outputs have enclosing quotes

Newer versions of terraform (post v0.14.0) have quotes around string outputs, instead of just the string value. The value isn't usable until you do extra work to strip off the quotes.

Upstream terraform issue: hashicorp/terraform#26831

Workarounds involve either using -json and then piping to jq, or using the new -raw flag.

compile failure tf version v0.10.0 with porter v1.0 on beta3

repro:

  1. install porter v1.0-beta3
  2. porter create foo
  3. porter mixin install terraform (receive v0.10.0)
  4. modify the porter.yaml file to merely reference the - terraform mixin
  5. porter build

expected: I am a good person on this horrible planet

received:

squillace@dellsquill:~/work/squillace/pwc$ porter mixins list
---------------------------------------------
  Name       Version        Author
---------------------------------------------
  atlas      v0.0.0         ralph squillace
  az         v0.7.3         Porter Authors
  exec       v1.0.0-beta.3  Porter Authors
  terraform  v0.10.0        Porter Authors
squillace@dellsquill:~/work/squillace/pwc$ porter build
exit status 1
exit status 1
exit status 1
Copying porter runtime ===>
Copying mixins ===>
Copying mixin exec ===>
Copying mixin az ===>
Copying mixin atlas ===>
Copying mixin terraform ===>
Building invocation image
[+] Building 2.5s (19/26)
 => [internal] load build definition from Dockerfile                                                                                                                                             0.0s
 => => transferring dockerfile: 1.90kB                                                                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                                                                0.0s
 => => transferring context: 35B                                                                                                                                                                 0.0s
 => resolve image config for docker.io/docker/dockerfile-upstream:1.4.0                                                                                                                          0.1s
 => CACHED docker-image://docker.io/docker/dockerfile-upstream:1.4.0@sha256:178c4e4a93795b9365dbf6cf10da8fcf517fcb4a17f1943a775c0f548e9fc2ff                                                     0.0s
 => [internal] load .dockerignore                                                                                                                                                                0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                             0.0s
 => [internal] load metadata for docker.io/library/debian:stretch-slim                                                                                                                           0.1s
 => [internal] load build context                                                                                                                                                                1.4s
 => => transferring context: 187.14MB                                                                                                                                                            1.4s
 => [stage-0  1/18] FROM docker.io/library/debian:stretch-slim@sha256:abaa313c7e1dfe16069a1a42fa254014780f165d4fd084844602edbe29915e70                                                           0.0s
 => CACHED [stage-0  2/18] RUN useradd nonroot -m -u 65532 -g 0 -o                                                                                                                               0.0s
 => CACHED [stage-0  3/18] RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache                                0.0s
 => CACHED [stage-0  4/18] RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt     apt-get update && apt-get install -y ca-certificates                          0.0s
 => CANCELED [stage-0  5/18] RUN apt-get update && apt-get -y install gnupg wget && wget -qO - https://pgp.mongodb.com/server-5.0.asc | apt-key add - && echo "deb [ arch=amd64,arm64 ] https:/  1.7s
 => CACHED [stage-0  6/18] RUN apt-get update && apt-get install -y apt-transport-https lsb-release gnupg curl                                                                                   0.0s
 => CACHED [stage-0  7/18] RUN curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/microsoft.asc.gpg                                             0.0s
 => CACHED [stage-0  8/18] RUN echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/azure-cli.list                          0.0s
 => CACHED [stage-0  9/18] RUN apt-get update && apt-get install -y azure-cli                                                                                                                    0.0s
 => CACHED [stage-0 10/18] RUN apt-get update && apt-get install -y wget unzip &&  wget https://releases.hashicorp.com/terraform/1.0.4/terraform_1.0.4_linux_amd64.zip &&  unzip terraform_1.0.  0.0s
 => ERROR [stage-0 11/18] COPY terraform/ /cnab/app/terraform/                                                                                                                                   0.0s
------
 > [stage-0 11/18] COPY terraform/ /cnab/app/terraform/:
------
error building docker image: failed to solve: failed to compute cache key: "/terraform" not found: not found
unable to build CNAB invocation image: error building docker image: failed to solve: failed to compute cache key: "/terraform" not found: not found
unable to build CNAB invocation image: error building docker image: failed to solve: failed to compute cache key: "/terraform" not found: not found

Refresh CI

  • Update the brigade.js script w/ latest best practices/utilities

  • Add support for handling issue comment events (e.g. /brig run)

Should unininstall pass parameters?

I'm getting the following error when I uninstall the porter terraform test CI bundle:

$ ./bin/porter uninstall --insecure --debug --param file_contents='foo!'
uninstalling porter-terraform...
uninstalling bundle porter-terraform (/Users/carolynvs/go/src/github.com/deislabs/porter/bundle.json) as porter-terraform
	params: [file_contents porter-debug]
	creds: []
executing porter uninstall configuration from /cnab/app/porter.yaml
Uninstall Terraform assets
Initializing Terraform...
/usr/bin/terraform terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "local" (1.2.1)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.local: version = "~> 1.2"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
/usr/bin/terraform terraform destroy -auto-approve
var.file_contents
  Contents of the file 'foo'

  Enter a value:
var.file_contents
  Contents of the file 'foo'

  Enter a value:
var.file_contents
  Contents of the file 'foo'

  Enter a value:
var.file_contents
  Contents of the file 'foo'


  Enter a value:
Error: Error asking for user input: missing required value for "file_contents"


Error: exit status 1
err: exit status 1
Error: mixin execution failed: exit status 1
Error: failed to uninstall the bundle: container exit code: 1

I think it's because the mixin isn't passing the param to the terraform cli for uninstall? Just a guess.

Optimize for layer caching during init

As part of mixin's build, we output the following lines, so that the invocation image has the providers installed. The problem with this is that any time that you change anything in the bundle, whether or not it's related to terraform, init is run again and the layer cache is invalidated. When a small bundle clocks in at 1.6GB this is a problem.

COPY . $BUNDLE_DIR
RUN cd %s && terraform init -backend=false

What if we encouraged people to put their provider declarations into a separate file, e.g. providers.tf, and then only copied that file into the bundle before running init? The rest is copied into the bundle later when we copy the bundle contents.

mixins:
  - terraform:
      initFile: providers.tf # relative to the workingDir

Error with latest version of terraform mixin

Mixin version 1.0.0

Porter is adding the following RUN command during build which is causing the following error: #15 2.833 Too many command line arguments. Configuration path expected.
#15 ERROR: executor failed running [/bin/sh -c cd $BUNDLE_DIR/terraform && terraform init -backend=false && rm -fr .terraform/providers && terraform providers mirror /usr/local/share/terraform/plugins]: exit code: 1

RUN cd $BUNDLE_DIR/terraform &&
terraform init -backend=false &&
rm -fr .terraform/providers &&
terraform providers mirror /usr/local/share/terraform/plugins

I am using terraform 0.12.31 which does not support terraform provider mirrors.

Install: tfvars file creation with no vars block

Currently, this mixin's install action will automatically create a tfvars file for any terraform vars provided in a vars block. However, some bundles may inject vars via the env (like the azure-keyvault bundle) and thus provide no vars block on install. The problem here is the tfvars file is then created with just null as its contents and thus terraform errors out.

(This default can be switched off via disableVarFile: true)

Proposed fix is to continue to gen the tfvars file as a default but, when the vars block is empty, have the contents of this file be {}, valid json.

can't cd into /cnab/app/terraform -- is it no longer created?

I'm wondering whether terraform creates this directory now? What changed? any ideas, @vdice or @carolynvs ?

https://github.com/squillace/blowschunks is a bundle; what happens when you build with porter v0.38.1 (3018b91c) is:

Writing Dockerfile =======>
FROM debian:stretch-slim

ARG BUNDLE_DIR

RUN apt-get update && apt-get install -y ca-certificates

ENV TERRAFORM_VERSION=0.12.17
RUN apt-get update && apt-get install -y wget unzip && \
 wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
 unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin
COPY . $BUNDLE_DIR
RUN cd /cnab/app/terraform && terraform init -backend=false

# exec mixin has no buildtime dependencies


COPY . $BUNDLE_DIR
RUN rm $BUNDLE_DIR/porter.yaml
RUN rm -fr $BUNDLE_DIR/.cnab
COPY .cnab /cnab
WORKDIR $BUNDLE_DIR
CMD ["/cnab/app/run"]

Starting Invocation Image Build (getporter/porter-hello-installer:v0.1.0) =======>
Step 1/13 : FROM debian:stretch-slim
 ---> 546475075b6c
Step 2/13 : ARG BUNDLE_DIR
 ---> Using cache
 ---> dbf5a3ec6635
Step 3/13 : RUN apt-get update && apt-get install -y ca-certificates
 ---> Using cache
 ---> f9d60b6f9aaa
Step 4/13 : ENV TERRAFORM_VERSION=0.12.17
 ---> Using cache
 ---> 368b927d60b9
Step 5/13 : RUN apt-get update && apt-get install -y wget unzip &&  wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip &&  unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin
 ---> Using cache
 ---> ec088e537dc8
Step 6/13 : COPY . $BUNDLE_DIR
 ---> Using cache
 ---> e883b594938d
Step 7/13 : RUN cd /cnab/app/terraform && terraform init -backend=false
 ---> Running in d6badbe616b0
/bin/sh: 1: cd: can't cd to /cnab/app/terraform
Error: unable to build CNAB invocation image: failed to stream docker build output: The command '/bin/sh -c cd /cnab/app/terraform && terraform init -backend=false' returned a non-zero code: 2

Fix red squiggles on the var field

I've noticed that the var field shows red squiggles in VS Code when filled out properly. The mixin's schema needs to be corrected to reflect the supported mixin syntax so that it doesn't happen. This is more important than red squiggles because eventually mixin schema will be used to lint and validate the porter.yaml, so this would become a hard error.

Outputs have a trailing \n

Outputs from the terraform mixin have a trailing \n which can cause issues if they're used in later steps. I believe the cause of the \n is that terraform appends one to the value when you call terraform output <output> and this mixin doesn't remove it.

The good news is that terraform output has a nifty --json option which gives the following output:

$ terraform output --json
{
  "namespace": {
    "sensitive": false,
    "type": "string",
    "value": "mynamespace"
  }
}

A possible fix would be to fetch all the outputs from Terraform and then parse them in the porter mixin to get the correct value (and type!) of the output.

Unit test coverage with a local terraform provider?

Look into possibilities around unit tests where this mixin code interacts with the terraform cli.

As mentioned in #50 (comment), it would be handy to be able to write unit tests that interact with terraform cli output, instead of resorting to cli/integration testing.

Only call terraform init at runtime when remote backend is configured

I noticed that we call terraform init at buildtime which is great. I added #79 to make that perform even better.
But since we have that, is there a reason that we call init again when when run the bundle? I'm noticing that it's causing terraform to re-download and install providers that were already installed (or at least that's what the output implies).

Initializing Terraform...
/usr/bin/terraform terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Reusing previous version of hashicorp/random from the dependency lock file
- Installing hashicorp/azurerm v2.72.0...
- Installed hashicorp/azurerm v2.72.0 (signed by HashiCorp)
- Installing hashicorp/random v3.1.0...
- Installed hashicorp/random v3.1.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

Since we only need the terraform init call at runtime when a remote backend is configured (as opposed to the bundle uses porter's state functionality), we should add an if statement around that init call so that we only do it when remote backend configuration is present.

Unable to pass objects into terraform plan

Hi!
I receive the following error when passing an object into terraform plan configured with object variable:

err: could unmarshal input:
upgrade:

  • terraform:
    vars:
    client_settings:
    create_instance: "true"
    prefix: deployment
    : yaml: unmarshal errors:
    line 5: cannot unmarshal !!map into string
    Error: could unmarshal input:
    upgrade:
  • terraform:
    description: Create BIG-IP Next on VIO
    vars:
    client_settings:
    create_instance: "true"
    prefix: deployment
    : yaml: unmarshal errors:
    line 5: cannot unmarshal !!map into string
    1 error occurred:
    * mixin execution failed: exit status 1

1 error occurred:
* container exit code: 1, message:

Here is example porter config:

upgrade:

  • terraform:
    vars:
    prefix: "{{bundle.parameters.prefix}}"
    client_settings: {"client_settings":"true"}

Would be great if terraform mixin supported passing objects into plan.

Use terraform provider mirror at buildtime?

See this discussion for context: getporter/porter#2279 (reply in thread)

In some cases, just calling terraform init isn't sufficient to avoid calls back to download providers at runtime, particularly when a remote backend is being used (since we call init again in that case). Looks like we should also call mirror so that the second call to init at runtime grabs the files from the bundle and not by redownloading the provider files.

Support git modules

The git binary is needed when Terraform references git/github modules. It is only required in the build phase as we mirror all providers/modules.

One way to handle the situation is to use a custom docker template and install git before the PORTER_MIXINS placeholder.

I thought we can add a setting to the mixin to optionally install git much like we install wget and unzip. WDYT?

Error with basic-tf-example

Environment

macos 10.15.6
docker 19.03.13
porter v0.29.1 (2e0f3648)
terraform mixin v0.6.0

Scenario

$ cd examples/basic-tf-example
$ porter build
Copying porter runtime ===>
Copying mixins ===>
Copying mixin terraform ===>

Generating Dockerfile =======>

Writing Dockerfile =======>

Starting Invocation Image Build =======>
$ porter install
porter install
installing basic-tf-example...
executing install action from basic-tf-example (installation: basic-tf-example)
Install Terraform assets
Initializing Terraform...
/usr/bin/terraform terraform init

Initializing the backend...

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.local: version = "~> 1.4"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
stat rocks!: no such file or directory
Error: error running command terraform apply -auto-approve -input=false -var myvar=porter rocks!: exit status 1
err: error running command terraform apply -auto-approve -input=false -var myvar=porter rocks!: exit status 1
Error: mixin execution failed: exit status 1
Error: 2 errors occurred:
	* container exit code: 1, message: <nil>. fetching outputs failed: error copying outputs from container: Error: No such container:path: eefa835f1f0b5e32b94330c7e5bb0817093710c6c172c7a89fe1348bfe1bfdca:/cnab/app/outputs
	* required output myvar is missing and has no default

Results

Terraform variables with spaces are not surrounded by quotes.

terraform apply -auto-approve -input=false -var myvar=porter rocks!

Investigation

In following source code, quotes should be added:

step.Flags = append(step.Flags, builder.NewFlag("var", fmt.Sprintf("%s=%s", k, step.Vars[k])))

Build step assumes that there is a directory named terraform

  1. Run porter create
  2. Add the terraform mixin
    mixins:
    - terraform
    
  3. Run porter build

This results in the following error

Error: unable to build CNAB invocation image: failed to stream docker build output: The command '/bin/sh -c cd /cnab/app/terraform && terraform init -backend=false' returned a non-zero code: 2

After making a directory named terraform at the root of the bundle, the build passes. Seems like we have a hard coded assumption about where the terraform files are kept that should more gracefully handle when it's not there, or return an error during lint.

Support non-string variables

The latest schema for porter.yaml supports a new template format, ${bundle.parameters.NAME}, that allows Porter to inject non-string values into a mixin field, such as a number or boolean.

The terraform mixin is assuming that all variables were defined as strings, which causes the wrong value to be printed for vars when translated to a CLI call

terraform destroy -auto-approve -input=false -var address_space=10.80.28.0/24 -var create_aad_groups=%!s(bool=false) -var shared_storage_quota=%!s(int=50) -var tre_id=tk20a

In the example above, shared_storage_quota was specified as an integer, and create_aad_groups a bool, so we are seeing a Go string replacement error printed out

The error seems to be originating from this line, though we should look for more go format strings with %s used.

step.Flags = append(step.Flags, builder.NewFlag("var", fmt.Sprintf("'%s=%s'", k, step.Vars[k])))

Ability to customise version of Terraform installed

Playing with this mixin on Kubernetes, I found one area of friction is storing the Terraform state. Either one would need to copy the state file between CNAB actions or use some remote backend.

With Terraform 0.13, currently there's an RC out, a Kubernetes secret backend will be added which makes this a lot less painful.

As the version of Terraform is currently hardcoded in the mixin, it's not easy to use this feature as one would have to rebuild the mixin. I think that wanting to specify versions of tools will actually be quite common, would it make sense to add a mixin config that allows this?

Add real-world example

Add an example porter bundle using this mixin, using a real-world/cloud provider resource.

Wire up mixin outputs

Remote backend functionality was added to this mixin in #9. In addition, an example bundle was added.

However, this mixin does not yet handle outputs. We need to add functionality such that this mixin supplies terraform outputs to bundle authors.

Remove input flag

As I understand it, the input flag toggles interactively prompting for required variables. Since bundles don't support interactive tty, this setting can never be flipped to true. Am I missing something or can we remove it?

tfvars file name?

I noticed that we use two different default naming standards for the state files generated by the terraform mixin:

  • terraform.tfstate
  • terraform.tfvars.json

It seems that .tfvars is the preferred extension and would be more consistent with our other file generated by the mixin. Can we change the default to terraform.tfvars?

Whitespace on outputs?

Not sure if this is a Porter issue or a porter-terraform issue, but my outputs have newlines at the end:

porter instance show HELLO -o json
{
  "name": "HELLO",
  "revision": "01DKFNVYXP9RVM2GT71BC0MZ2V",
  "created": "2019-08-29T15:47:24.981725-06:00",
  "modified": "2019-08-29T15:52:54.198351-06:00",
  "bundle": {
    "schemaVersion": "v1.0.0-WD",
    "name": "HELLO",
    "version": "0.1.0",
    "description": "An example Porter configuration",
    "invocationImages": [
      {
        "imageType": "docker",
        "image": "jeremyrickard/porter-do:v0.1.0"
      }
    ],
    "images": {
      "spring-music": {
        "imageType": "docker",
        "image": "jeremyrickard/spring-music@sha256:3bf5403b1fdaa0569ffbf32e046241d8f8adef1d586d85fe7f6d5175c48f8e83",
        "contentDigest": "sha256:3bf5403b1fdaa0569ffbf32e046241d8f8adef1d586d85fe7f6d5175c48f8e83",
        "description": "Spring Music Example"
      }
    },
    "parameters": {
      "database_name": {
        "definition": "database_name",
        "destination": {
          "env": "DATABASE_NAME"
        }
      },
      "porter-debug": {
        "definition": "porter-debug",
        "description": "Print debug information from Porter when executing the bundle",
        "destination": {
          "env": "PORTER_DEBUG"
        }
      },
      "region": {
        "definition": "region",
        "destination": {
          "env": "REGION"
        }
      },
      "space_name": {
        "definition": "space_name",
        "destination": {
          "env": "SPACE_NAME"
        }
      }
    },
    "credentials": {
      "do_access_token": {
        "env": "DO_ACCESS_TOKEN"
      },
      "do_spaces_key": {
        "env": "DO_SPACES_KEY"
      },
      "do_spaces_secret": {
        "env": "DO_SPACES_SECRET"
      },
      "kubeconfig": {
        "path": "/root/.kube/config"
      }
    },
    "outputs": {
      "db_password": {
        "definition": "db_password",
        "path": "/cnab/app/outputs/db_password"
      },
      "db_user": {
        "definition": "db_user",
        "path": "/cnab/app/outputs/db_user"
      }
    },
    "definitions": {
      "database_name": {
        "default": "jrrportertest",
        "type": "string"
      },
      "db_password": {
        "type": "string"
      },
      "db_user": {
        "type": "string"
      },
      "porter-debug": {
        "default": false,
        "description": "Print debug information from Porter when executing the bundle",
        "type": "boolean"
      },
      "region": {
        "default": "nyc3",
        "type": "string"
      },
      "space_name": {
        "default": "jrrportertest",
        "type": "string"
      }
    },
    "custom": {
      "io.cnab.dependencies": null,
      "sh.porter": {
        "manifestDigest": "4b3800a5218ad487ba0205709e22cb5794fa59bc6857aff246cd8babc5eb597c"
      }
    }
  },
  "result": {
    "message": "",
    "action": "install",
    "status": "success"
  },
  "parameters": {
    "database_name": "porternetes",
    "porter-debug": true,
    "region": "nyc3",
    "space_name": "porternetes"
  },
  "outputs": {
    "db_password": "h69279993kyf78oc\n",
    "db_user": "doadmin\n"
  }
}

This ends up breaking when I try to use them inside a single string, for example here is a bit of rendered YAML before calling the helm mixin:

helm:
  chart: charts/spring-music
  description: Deploy Spring Music with Helm
  name: spring-music-helm
  replace: true
  set:
    deploy.datasource: jdbc:postgresql://porternetes-do-user-6482440-0.db.ondigitalocean.com
:25060
/defaultdb
?sslmode=require&user=doadmin
&password=ccpto35006fxn4uy

    deploy.image: jeremyrickard/spring-music@sha256:3bf5403b1fdaa0569ffbf32e046241d8f8adef1d586d85fe7f6d5175c48f8e83
    deploy.profile: postgresql

Split up and passed to an app separately the app gets a string like this (notice the spaces after each):

jdbc:postgresql://porternetes-do-user-6482440-0.db.ondigitalocean.com :25060 /defaultdb ?sslmode=require&user=doadmin &password=h69279993kyf78oc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.