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
- How can I pre-download all providers and plugins
- Tell the CLI use those predownloaded plugins AND
- 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
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 theinit
command. You could also set a filesystem mirror in eachterraform
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 thefilesystem_mirror
path
conflicts with theplugin_cache_dir
. You would want to resolve that conflict, or perhaps remove the mirror block entirely.Since the setting is captured in the CLI configuration file within the
Dockerfile
, this would be automatically used in future commands.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 thejenkins
user configuration to be more aligned with the standard.TL;DR:
required_providers
blockterraform 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 therequired_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:
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 therequired_providers
.Previously overengineered stuff below:
The only hurdle left was that
terraform providers mirror
downloads the providers in the packed layout:while Terraform expects the plugin cache directory to use the unpacked layout:
So I converted the packed layout to the unpacked layout with the help of find and parallel: