nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{
2 stdenv,
3 lib,
4 buildGoModule,
5 fetchFromGitHub,
6 makeWrapper,
7 coreutils,
8 runCommand,
9 runtimeShell,
10 writeText,
11 terraform-providers,
12 installShellFiles,
13}:
14
15let
16 generic =
17 {
18 version,
19 hash,
20 vendorHash ? null,
21 ...
22 }@attrs:
23 let
24 attrs' = builtins.removeAttrs attrs [
25 "version"
26 "hash"
27 "vendorHash"
28 ];
29 in
30 buildGoModule (
31 {
32 pname = "terraform";
33 inherit version vendorHash;
34
35 src = fetchFromGitHub {
36 owner = "hashicorp";
37 repo = "terraform";
38 rev = "v${version}";
39 inherit hash;
40 };
41
42 ldflags = [
43 "-s"
44 "-w"
45 "-X 'github.com/hashicorp/terraform/version.dev=no'"
46 ];
47
48 postPatch = ''
49 # Between go 1.23 and 1.24 the following GODEBUG setting was removed, and a new
50 # similar one was added.
51 # https://github.com/golang/go/issues/72111
52 # The setting is configured upstream due to the following timeouts caused by
53 # the TLS handshake using post-quantum crypto with servers that don't support it
54 # https://tldr.fail/
55 substituteInPlace go.mod \
56 --replace-quiet 'godebug tlskyber=0' 'godebug tlsmlkem=0'
57 '';
58 postConfigure = ''
59 # speakeasy hardcodes /bin/stty https://github.com/bgentry/speakeasy/issues/22
60 substituteInPlace vendor/github.com/bgentry/speakeasy/speakeasy_unix.go \
61 --replace-fail "/bin/stty" "${coreutils}/bin/stty"
62 '';
63
64 nativeBuildInputs = [ installShellFiles ];
65
66 postInstall = ''
67 # https://github.com/posener/complete/blob/9a4745ac49b29530e07dc2581745a218b646b7a3/cmd/install/bash.go#L8
68 installShellCompletion --bash --name terraform <(echo complete -C terraform terraform)
69 '';
70
71 preCheck = ''
72 export HOME=$TMPDIR
73 export TF_SKIP_REMOTE_TESTS=1
74 '';
75
76 subPackages = [ "." ];
77
78 meta = {
79 description = "Tool for building, changing, and versioning infrastructure";
80 homepage = "https://www.terraform.io/";
81 changelog = "https://github.com/hashicorp/terraform/blob/v${version}/CHANGELOG.md";
82 license = lib.licenses.bsl11;
83 maintainers = with lib.maintainers; [
84 Chili-Man
85 kalbasit
86 timstott
87 zimbatm
88 zowoq
89 techknowlogick
90 qjoly
91 ];
92 mainProgram = "terraform";
93 };
94 }
95 // attrs'
96 );
97
98 pluggable =
99 terraform:
100 let
101 withPlugins =
102 plugins:
103 let
104 actualPlugins = plugins terraform.plugins;
105
106 # Wrap PATH of plugins propagatedBuildInputs, plugins may have runtime dependencies on external binaries
107 wrapperInputs = lib.unique (
108 lib.flatten (lib.catAttrs "propagatedBuildInputs" (builtins.filter (x: x != null) actualPlugins))
109 );
110
111 passthru = {
112 withPlugins = newplugins: withPlugins (x: newplugins x ++ actualPlugins);
113 full = withPlugins (p: lib.filter lib.isDerivation (lib.attrValues p.actualProviders));
114
115 # Expose wrappers around the override* functions of the terraform
116 # derivation.
117 #
118 # Note that this does not behave as anyone would expect if plugins
119 # are specified. The overrides are not on the user-visible wrapper
120 # derivation but instead on the function application that eventually
121 # generates the wrapper. This means:
122 #
123 # 1. When using overrideAttrs, only `passthru` attributes will
124 # become visible on the wrapper derivation. Other overrides that
125 # modify the derivation *may* still have an effect, but it can be
126 # difficult to follow.
127 #
128 # 2. Other overrides may work if they modify the terraform
129 # derivation, or they may have no effect, depending on what
130 # exactly is being changed.
131 #
132 # 3. Specifying overrides on the wrapper is unsupported.
133 #
134 # See nixpkgs#158620 for details.
135 overrideDerivation = f: (pluggable (terraform.overrideDerivation f)).withPlugins plugins;
136 overrideAttrs = f: (pluggable (terraform.overrideAttrs f)).withPlugins plugins;
137 override = x: (pluggable (terraform.override x)).withPlugins plugins;
138 };
139 in
140 # Don't bother wrapping unless we actually have plugins, since the wrapper will stop automatic downloading
141 # of plugins, which might be counterintuitive if someone just wants a vanilla Terraform.
142 if actualPlugins == [ ] then
143 terraform.overrideAttrs (orig: {
144 passthru = orig.passthru // passthru;
145 })
146 else
147 lib.appendToName "with-plugins" (
148 stdenv.mkDerivation {
149 inherit (terraform) meta pname version;
150 nativeBuildInputs = [ makeWrapper ];
151
152 # Expose the passthru set with the override functions
153 # defined above, as well as any passthru values already
154 # set on `terraform` at this point (relevant in case a
155 # user overrides attributes).
156 passthru = terraform.passthru // passthru;
157
158 buildCommand = ''
159 # Create wrappers for terraform plugins because Terraform only
160 # walks inside of a tree of files.
161 for providerDir in ${toString actualPlugins}
162 do
163 for file in $(find $providerDir/libexec/terraform-providers -type f)
164 do
165 relFile=''${file#$providerDir/}
166 mkdir -p $out/$(dirname $relFile)
167 cat <<WRAPPER > $out/$relFile
168 #!${runtimeShell}
169 exec "$file" "$@"
170 WRAPPER
171 chmod +x $out/$relFile
172 done
173 done
174
175 # Create a wrapper for terraform to point it to the plugins dir.
176 mkdir -p $out/bin/
177 makeWrapper "${terraform}/bin/terraform" "$out/bin/terraform" \
178 --set NIX_TERRAFORM_PLUGIN_DIR $out/libexec/terraform-providers \
179 --prefix PATH : "${lib.makeBinPath wrapperInputs}"
180 '';
181 }
182 );
183 in
184 withPlugins (_: [ ]);
185
186 plugins = removeAttrs terraform-providers [
187 "override"
188 "overrideDerivation"
189 "recurseForDerivations"
190 ];
191in
192rec {
193 # Constructor for other terraform versions
194 mkTerraform = attrs: pluggable (generic attrs);
195
196 terraform_1 = mkTerraform {
197 version = "1.12.2";
198 hash = "sha256-ilQ1rscGD66OT6lHsBgWELayC24B2D7l6iH6vtvqzFI=";
199 vendorHash = "sha256-zWNLIurNP5e/AWr84kQCb2+gZIn6EAsuvr0ZnfSq7Zw=";
200 patches = [ ./provider-path-0_15.patch ];
201 passthru = {
202 inherit plugins;
203 tests = { inherit terraform_plugins_test; };
204 };
205 };
206
207 # Tests that the plugins are being used. Terraform looks at the specific
208 # file pattern and if the plugin is not found it will try to download it
209 # from the Internet. With sandboxing enable this test will fail if that is
210 # the case.
211 terraform_plugins_test =
212 let
213 mainTf = writeText "main.tf" ''
214 resource "random_id" "test" {}
215 '';
216 terraform = terraform_1.withPlugins (p: [ p.random ]);
217 test = runCommand "terraform-plugin-test" { buildInputs = [ terraform ]; } ''
218 set -e
219 # make it fail outside of sandbox
220 export HTTP_PROXY=http://127.0.0.1:0 HTTPS_PROXY=https://127.0.0.1:0
221 cp ${mainTf} main.tf
222 terraform init
223 touch $out
224 '';
225 in
226 test;
227
228}