skip to Main Content

I am trying to build a docker image that contains all of the necessary plugins/providers that several source repos need, so that when an automated terraform validate runs, it doesn’t have to download gigs of redundant data.

However, I recognize that this provides for a maintenance problem in that someone may update a plugin version, and that would needed to be downloaded, since the docker image would not contain it.

The question

  1. How can I pre-download all providers and plugins
  2. Tell the CLI use those predownloaded plugins AND
  3. also tell it that, if it doesn’t find what it needs locally, then it can go to the network

Below are the relevant file:
.terraformrc

plugin_cache_dir   = "$HOME/.terraform.d/plugin-cache"
disable_checkpoint = true
provider_installation {
  filesystem_mirror {
    path    = "$HOME/.terraform/providers"
  }
  direct {
  }
}

tflint (not relevant to this question, but it shows up in the below Dockerfile)

plugin "aws" {
  enabled = true
  version = "0.21.1"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}
plugin "azurerm" {
  enabled = true
  version = "0.20.0"
  source  = "github.com/terraform-linters/tflint-ruleset-azurerm"
}

Dockerfile

FROM ghcr.io/terraform-linters/tflint-bundle AS  base
LABEL name=tflint
RUN adduser -h /home/jenkins -s /bin/sh -u 1000 -D jenkins

RUN apk fix && apk --no-cache --update add git terraform openssh
ADD .terraformrc /home/jenkins/.terraformrc
RUN mkdir -p  /home/jenkins/.terraform.d/plugin-cache/registry.terraform.io

ADD .tflint.hcl /home/jenkins/.tflint.hcl
WORKDIR /home/jenkins
RUN tflint --init

FROM base AS build

ARG SSH_PRIVATE_KEY

RUN mkdir /root/.ssh && 
    echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_ed25519 &&  
    chmod 400 /root/.ssh/id_ed25519 &&  
    touch /root/.ssh/known_hosts &&  
    ssh-keyscan mygitrepo >> /root/.ssh/known_hosts


RUN git clone git@mygitrepo:wrai/tools/g.git

RUN git clone git@mygitrepo:myproject/a.git && 
    git clone git@mygitrepo:myproject/b.git && 
    git clone git@mygitrepo:myproject/c.git && 
    git clone git@mygitrepo:myproject/d.git && 
    git clone git@mygitrepo:myproject/e.git && 
    git clone git@mygitrepo:myproject/f.git

RUN ls -1d */ | xargs -I {} find {} -name '*.tf' | xargs -n 1 dirname | sort -u |  
    xargs -I {} -n 1 -P 20 terraform -chdir={} providers mirror /home/jenkins/.terraform.d

RUN chown -R jenkins:jenkins /home/jenkins
USER jenkins

FROM base AS a
COPY --from=build /home/jenkins/a/ /home/jenkins/a
RUN cd /home/jenkins/a && terraform init

FROM base AS b
COPY --from=build /home/jenkins/b/ /home/jenkins/b
RUN cd /home/jenkins/b && terraform init

FROM base AS c
COPY --from=build /home/jenkins/c/ /home/jenkins/c
RUN cd /home/jenkins/c && terraform init

FROM base AS azure_infrastructure
COPY --from=build /home/jenkins/d/ /home/jenkins/d
RUN cd /home/jenkins/d && terraform init

FROM base AS aws_infrastructure
COPY --from=build /home/jenkins/e/ /home/jenkins/e
RUN cd /home/jenkins/e && terraform init

2

Answers


    1. Staging plugins:

    This is most easily accomplished with the plugin cache dir setting in the CLI. This supersedes the old usage with the -plugin-dir=PATH argument for the init command. You could also set a filesystem mirror in each terraform block within the root module config, but this would be cumbersome for your use case. In your situation, you are already configuring this in your .terraformrc, but the filesystem_mirror path conflicts with the plugin_cache_dir. You would want to resolve that conflict, or perhaps remove the mirror block entirely.

    1. Use staged plugins:

    Since the setting is captured in the CLI configuration file within the Dockerfile, this would be automatically used in future commands.

    1. Download additional plugins if necessary:

    This is default behavior of the init command, and therefore requires no further actions on your part.

    Side note:

    The jenkins user typically is /sbin/nologin for shell and /var/lib/jenkins for home directory. If the purpose of this Docker image is for a Jenkins build agent, then you may want the jenkins user configuration to be more aligned with the standard.

    Login or Signup to reply.
  1. TL;DR:

    1. Configure the terraform plugin cache directory
    2. Create directory with a single TF file containing required_providers block
    3. Run terraform init from there

    I’ve stumbled over this question as I tried to figure out the same thing.

    I first tried leveraging an implied filesystem_mirror by running terraform providers mirror /usr/local/share/terraform/plugins in a directory containing only one terraform file containing the required_providers block. This works fine as long as you only use the versions of the providers you mirrored.
    However, it’s not possible to use a different version of a provider than the one you mirrored, because:

    Terraform will scan all of the filesystem mirror directories to see which providers are placed there and automatically exclude all of those providers from the implied direct block.

    I’ve found it to be a better solution to use a plugin cache directory instead. EDIT: You can prefetch the plugins by setting TF_PLUGIN_CACHE_DIR to some directory and then running terraform init in a directory that only declares the required_providers.

    Previously overengineered stuff below:
    The only hurdle left was that terraform providers mirror downloads the providers in the packed layout:

    Packed layout: HOSTNAME/NAMESPACE/TYPE/terraform-provider-TYPE_VERSION_TARGET.zip is the distribution zip file obtained from the provider’s origin registry.

    while Terraform expects the plugin cache directory to use the unpacked layout:

    Unpacked layout: HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET is a directory containing the result of extracting the provider’s distribution zip file.

    So I converted the packed layout to the unpacked layout with the help of find and parallel:

    find path/to/plugin-dir -name index.json -exec rm {} +`  
    
    find path/to/plugin-dir -name '*.json' | parallel --will-cite 'mkdir -p {//}/{/.}/linux_amd64; unzip {//}/*.zip -d {//}/{/.}/linux_amd64; rm {}; rm {//}/*.zip'
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search