Skip to content

Commit a43f141

Browse files
authored
tools/terraform-bundle: refactor to use new provider installer and provider directory layouts (#24629)
* tools/terraform-bundle: refactor to use new provider installer and provider directory layouts terraform-bundle now supports a "source" attribute for providers, uses the new provider installer, and the archive it creates preserves the new (required) directory hierarchy for providers, under a "plugins" directory. This is a breaking change in many ways: source is required for any non-HashiCorp provider, locally-installed providers must be given a source (can be arbitrary, see docs) and placed in the expected directory hierarchy, and the unzipped archive is no longer flat; there is a new "plugins" directory created with providers in the new directory layout. This PR also extends the existing test to check the contents of the zip file. TODO: Re-enable e2e tests (currently suppressed with a t.Skip) This commit includes an update to our travis configuration, so the terraform-bundle e2e tests run. It also turns off the e2e tests, which will fail until we have a terraform 0.13.* release under releases.hashicorp.com. We decided it was better to merge this now instead of waiting when we started seeing issues opened from users who built terraform-bundle from 0.13 and found it didn't work with 0.12 - better that they get an immediate error message from the binary directing them to build from the appropriate release.
1 parent 1750994 commit a43f141

File tree

13 files changed

+502
-221
lines changed

13 files changed

+502
-221
lines changed

.circleci/config.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ jobs:
8181
- run:
8282
name: Run Go E2E Tests
8383
command: |
84-
PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname)
85-
echo "Running $(echo $PACKAGE_NAMES | wc -w) packages"
86-
echo $PACKAGE_NAMES
87-
gotestsum --format=short-verbose --junitfile $TEST_RESULTS_DIR/gotestsum-report.xml -- -p 2 -cover -coverprofile=cov_e2e.part ./command/e2etest
84+
gotestsum --format=short-verbose --junitfile $TEST_RESULTS_DIR/gotestsum-report.xml -- -p 2 -cover -coverprofile=cov_e2e.part ./command/e2etest ./tools/terraform-bundle/e2etest
8885
8986
# save coverage report parts
9087
- persist_to_workspace:

e2e/e2e.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,8 @@ func GoBuild(pkgPath, tmpPrefix string) string {
270270

271271
return tmpFilename
272272
}
273+
274+
// WorkDir() returns the binary workdir
275+
func (b *binary) WorkDir() string {
276+
return b.workDir
277+
}

internal/providercache/dir.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ func NewDir(baseDir string) *Dir {
5555
}
5656
}
5757

58-
// newDirWithPlatform is a variant of NewDir that allows selecting a specific
58+
// NewDirWithPlatform is a variant of NewDir that allows selecting a specific
5959
// target platform, rather than taking the current one where this code is
6060
// running.
6161
//
6262
// This is primarily intended for portable unit testing and not particularly
63-
// useful in "real" callers.
64-
func newDirWithPlatform(baseDir string, platform getproviders.Platform) *Dir {
63+
// useful in "real" callers, with the exception of terraform-bundle.
64+
func NewDirWithPlatform(baseDir string, platform getproviders.Platform) *Dir {
6565
return &Dir{
6666
baseDir: baseDir,
6767
targetPlatform: platform,

internal/providercache/dir_modify_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ func TestLinkFromOtherCache(t *testing.T) {
8484
addrs.DefaultRegistryHost, "hashicorp", "null",
8585
)
8686

87-
srcDir := newDirWithPlatform(srcDirPath, windowsPlatform)
88-
tmpDir := newDirWithPlatform(tmpDirPath, windowsPlatform)
87+
srcDir := NewDirWithPlatform(srcDirPath, windowsPlatform)
88+
tmpDir := NewDirWithPlatform(tmpDirPath, windowsPlatform)
8989

9090
// First we'll check our preconditions: srcDir should have only the
9191
// null provider version 2.0.0 in it, because we're faking that we're on

internal/providercache/dir_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestDirReading(t *testing.T) {
4040

4141
t.Run("ProviderLatestVersion", func(t *testing.T) {
4242
t.Run("exists", func(t *testing.T) {
43-
dir := newDirWithPlatform(testDir, windowsPlatform)
43+
dir := NewDirWithPlatform(testDir, windowsPlatform)
4444

4545
got := dir.ProviderLatestVersion(nullProvider)
4646
want := &CachedProvider{
@@ -59,7 +59,7 @@ func TestDirReading(t *testing.T) {
5959
}
6060
})
6161
t.Run("no package for current platform", func(t *testing.T) {
62-
dir := newDirWithPlatform(testDir, windowsPlatform)
62+
dir := NewDirWithPlatform(testDir, windowsPlatform)
6363

6464
// random provider is only cached for linux_amd64 in our fixtures dir
6565
got := dir.ProviderLatestVersion(randomProvider)
@@ -70,7 +70,7 @@ func TestDirReading(t *testing.T) {
7070
}
7171
})
7272
t.Run("no versions available at all", func(t *testing.T) {
73-
dir := newDirWithPlatform(testDir, windowsPlatform)
73+
dir := NewDirWithPlatform(testDir, windowsPlatform)
7474

7575
// nonexist provider is not present in our fixtures dir at all
7676
got := dir.ProviderLatestVersion(nonExistProvider)
@@ -84,7 +84,7 @@ func TestDirReading(t *testing.T) {
8484

8585
t.Run("ProviderVersion", func(t *testing.T) {
8686
t.Run("exists", func(t *testing.T) {
87-
dir := newDirWithPlatform(testDir, windowsPlatform)
87+
dir := NewDirWithPlatform(testDir, windowsPlatform)
8888

8989
got := dir.ProviderVersion(nullProvider, versions.MustParseVersion("2.0.0"))
9090
want := &CachedProvider{
@@ -100,7 +100,7 @@ func TestDirReading(t *testing.T) {
100100
}
101101
})
102102
t.Run("specified version is not cached", func(t *testing.T) {
103-
dir := newDirWithPlatform(testDir, windowsPlatform)
103+
dir := NewDirWithPlatform(testDir, windowsPlatform)
104104

105105
// there is no v5.0.0 package in our fixtures dir
106106
got := dir.ProviderVersion(nullProvider, versions.MustParseVersion("5.0.0"))
@@ -111,7 +111,7 @@ func TestDirReading(t *testing.T) {
111111
}
112112
})
113113
t.Run("no package for current platform", func(t *testing.T) {
114-
dir := newDirWithPlatform(testDir, windowsPlatform)
114+
dir := NewDirWithPlatform(testDir, windowsPlatform)
115115

116116
// random provider 1.2.0 is only cached for linux_amd64 in our fixtures dir
117117
got := dir.ProviderVersion(randomProvider, versions.MustParseVersion("1.2.0"))
@@ -122,7 +122,7 @@ func TestDirReading(t *testing.T) {
122122
}
123123
})
124124
t.Run("no versions available at all", func(t *testing.T) {
125-
dir := newDirWithPlatform(testDir, windowsPlatform)
125+
dir := NewDirWithPlatform(testDir, windowsPlatform)
126126

127127
// nonexist provider is not present in our fixtures dir at all
128128
got := dir.ProviderVersion(nonExistProvider, versions.MustParseVersion("1.0.0"))
@@ -135,7 +135,7 @@ func TestDirReading(t *testing.T) {
135135
})
136136

137137
t.Run("AllAvailablePackages", func(t *testing.T) {
138-
dir := newDirWithPlatform(testDir, linuxPlatform)
138+
dir := NewDirWithPlatform(testDir, linuxPlatform)
139139

140140
got := dir.AllAvailablePackages()
141141
want := map[addrs.Provider][]CachedProvider{

tools/terraform-bundle/README.md

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,8 @@ the rest of this README to be in `PATH`.
3434

3535
`terraform-bundle` is a repackaging of the module installation functionality
3636
from Terraform itself, so for best results you should build from the tag
37-
relating to the version of Terraform you plan to use. There is some slack in
38-
this requirement due to the fact that the module installation behavior changes
39-
rarely, but please note that in particular bundles for versions of
40-
Terraform before v0.12 must be built from a `terraform-bundle` built against
41-
a Terraform v0.11 tag at the latest, since Terraform v0.12 installs plugins
42-
in a different way that is not compatible.
37+
relating to the version of Terraform you plan to use. For example, use the v0.12
38+
tag to build a version of terraform-bundle compatible with Terraform v0.12*.
4339

4440
## Usage
4541

@@ -59,19 +55,26 @@ terraform {
5955
# Define which provider plugins are to be included
6056
providers {
6157
# Include the newest "aws" provider version in the 1.0 series.
62-
aws = ["~> 1.0"]
58+
aws = {
59+
versions = ["~> 1.0"]
60+
}
6361
6462
# Include both the newest 1.0 and 2.0 versions of the "google" provider.
6563
# Each item in these lists allows a distinct version to be added. If the
6664
# two expressions match different versions then _both_ are included in
6765
# the bundle archive.
68-
google = ["~> 1.0", "~> 2.0"]
66+
google = {
67+
versions = ["~> 1.0", "~> 2.0"]
68+
}
6969
7070
# Include a custom plugin to the bundle. Will search for the plugin in the
71-
# plugins directory, and package it with the bundle archive. Plugin must have
72-
# a name of the form: terraform-provider-*, and must be build with the operating
73-
# system and architecture that terraform enterprise is running, e.g. linux and amd64
74-
customplugin = ["0.1"]
71+
# plugins directory and package it with the bundle archive. Plugin must have
72+
# a name of the form: terraform-provider-*, and must be built with the operating
73+
# system and architecture that terraform enterprise is running, e.g. linux and amd64.
74+
customplugin = {
75+
versions = ["0.1"]
76+
source = "myorg/customplugin"
77+
}
7578
}
7679
7780
```
@@ -80,10 +83,10 @@ The `terraform` block defines which version of Terraform will be included
8083
in the bundle. An exact version is required here.
8184

8285
The `providers` block defines zero or more providers to include in the bundle
83-
along with core Terraform. Each attribute in this block is a provider name,
84-
and its value is a list of version constraints. For each given constraint,
85-
`terraform-bundle` will find the newest available version matching the
86-
constraint and include it in the bundle.
86+
along with core Terraform. Each attribute is a provider name, and its value is a
87+
block with the list of version constraints and (optional) source. For each given
88+
constraint, `terraform-bundle` will find the newest available version matching
89+
the constraint and include it in the bundle.
8790

8891
It is allowed to specify multiple constraints for the same provider, in which
8992
case multiple versions can be included in the resulting bundle. Each constraint
@@ -119,13 +122,44 @@ this composite version number so that bundle archives can be easily
119122
distinguished from official release archives and from each other when multiple
120123
bundles contain the same core Terraform version.
121124

122-
To include custom plugins in the bundle file, create a local directory "./plugins"
123-
and put all the plugins you want to include there. Optionally, you can use the
124-
`-plugin-dir` flag to specify a location where to find the plugins. To be recognized
125-
as a valid plugin, the file must have a name of the form
126-
`terraform-provider-<NAME>_v<VERSION>`. In
127-
addition, ensure that the plugin is built using the same operating system and
128-
architecture used for Terraform Enterprise. Typically this will be `linux` and `amd64`.
125+
## Custom Plugins
126+
To include custom plugins in the bundle file, create a local directory
127+
"./plugins" and put all the plugins you want to include there, under the
128+
required [sub directory](#plugins-directory-layout). Optionally, you can use the
129+
`-plugin-dir` flag to specify a location where to find the plugins. To be
130+
recognized as a valid plugin, the file must have a name of the form
131+
`terraform-provider-<NAME>`. In addition, ensure that the plugin is built using
132+
the same operating system and architecture used for Terraform Enterprise.
133+
Typically this will be `linux` and `amd64`.
134+
135+
### Plugins Directory Layout
136+
To include custom plugins in the bundle file, you must specify a "source"
137+
attribute in the configuration and place the plugin in the appropriate
138+
subdirectory under "./plugins". The directory must have the following layout:
139+
140+
```
141+
./plugins/$SOURCEHOST/$SOURCENAMESPACE/$NAME/$VERSION/$OS_$ARCH/
142+
```
143+
144+
When installing custom plugins, you may choose any arbitrary identifier for the
145+
$SOURCEHOST and $SOURCENAMESPACE subdirectories.
146+
147+
For example, given the following configuration and a plugin built for Terraform Enterprise:
148+
149+
```
150+
providers {
151+
customplugin = {
152+
versions = ["0.1"]
153+
source = "example.com/myorg/customplugin"
154+
}
155+
}
156+
```
157+
158+
The binary must be placed in the following directory:
159+
160+
```
161+
./plugins/example.com/myorg/customplugin/0.1/linux_amd64/
162+
```
129163

130164
## Provider Resolution Behavior
131165

tools/terraform-bundle/config.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@ import (
55
"io/ioutil"
66

77
"github.com/hashicorp/hcl"
8+
"github.com/hashicorp/terraform/addrs"
9+
"github.com/hashicorp/terraform/internal/getproviders"
810
"github.com/hashicorp/terraform/plugin/discovery"
911
)
1012

11-
var zeroTwelve = discovery.ConstraintStr(">= 0.12.0").MustParse()
13+
var zeroThirteen = discovery.ConstraintStr(">= 0.13.0").MustParse()
1214

1315
type Config struct {
14-
Terraform TerraformConfig `hcl:"terraform"`
15-
Providers map[string][]discovery.ConstraintStr `hcl:"providers"`
16+
Terraform TerraformConfig `hcl:"terraform"`
17+
Providers map[string]ProviderConfig `hcl:"providers"`
1618
}
1719

1820
type TerraformConfig struct {
1921
Version discovery.VersionStr `hcl:"version"`
2022
}
2123

24+
type ProviderConfig struct {
25+
Versions []string `hcl:"versions"`
26+
Source string `hcl:"source"`
27+
}
28+
2229
func LoadConfig(src []byte, filename string) (*Config, error) {
2330
config := &Config{}
2431
err := hcl.Decode(config, string(src))
@@ -49,17 +56,23 @@ func (c *Config) validate() error {
4956
if v, err = c.Terraform.Version.Parse(); err != nil {
5057
return fmt.Errorf("terraform.version: %s", err)
5158
}
52-
if !zeroTwelve.Allows(v) {
53-
return fmt.Errorf("this version of terraform-bundle can only build bundles for Terraform v0.12 and later; build terraform-bundle from the v0.11 branch or a v0.11.* tag to construct bundles for earlier versions")
59+
if !zeroThirteen.Allows(v) {
60+
return fmt.Errorf("this version of terraform-bundle can only build bundles for Terraform v0.13 and later; build terraform-bundle from a release tag (such as v0.12.*) to construct bundles for earlier versions")
5461
}
5562

5663
if c.Providers == nil {
57-
c.Providers = map[string][]discovery.ConstraintStr{}
64+
c.Providers = map[string]ProviderConfig{}
5865
}
5966

6067
for k, cs := range c.Providers {
61-
for _, c := range cs {
62-
if _, err := c.Parse(); err != nil {
68+
if cs.Source != "" {
69+
_, diags := addrs.ParseProviderSourceString(cs.Source)
70+
if diags.HasErrors() {
71+
return fmt.Errorf("providers.%s: %s", k, diags.Err().Error())
72+
}
73+
}
74+
for _, c := range cs.Versions {
75+
if _, err := getproviders.ParseVersionConstraints(c); err != nil {
6376
return fmt.Errorf("providers.%s: %s", k, err)
6477
}
6578
}

0 commit comments

Comments
 (0)