Skip to content

Commit 87b92fe

Browse files
anton-vylushchakAnthony Vylushchak
andauthored
Terrafile Community Format Support (#6)
* Replace CircleCI with Buildkite * go mod tidy && go mod vendor * Add community Terrafile format support * Update README * Fallback to community format only in case of yaml errors * Remove Buildkite integration as this repo is public Co-authored-by: Anthony Vylushchak <[email protected]>
1 parent 01a1647 commit 87b92fe

File tree

14 files changed

+229
-2177
lines changed

14 files changed

+229
-2177
lines changed

.circleci/config.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.idea
2+
.vscode
3+
14
# Binaries for programs and plugins
25
*.exe
36
*.exe~

README.md

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,79 @@ Download your preferred flavor from the [releases](https://github.com/coretech/t
1919

2020
Terrafile expects a file named `Terrafile` which will contain your terraform module dependencies in a yaml like format.
2121

22-
An example Terrafile:
22+
Terrafile config file in custom directory
2323

24+
```sh
25+
$ terrafile -f config/Terrafile
2426
```
25-
[email protected]:segmentio/my-modules:
26-
- chamber_v2.0.0
27-
- constants_v1.0.1
28-
- iam_v1.0.0
29-
- service_v1.0.4
30-
- rds_v0.0.5
31-
- worker_v1.1.0
32-
- master
27+
28+
Terraform modules exported to custom directory
29+
30+
```sh
31+
$ terrafile -p /path/to/custom_directory
3332
```
3433

35-
Terrafile config file in current directory and modules exported to .terrafile/<user>/<repo>/<ref>
34+
## Segment Terrafile Format
35+
36+
Segment Terrafile is an internal format that specifies sources with a list of its versions. Each version is vendored under `.terrafile/<user>/<repo>/<ref>`
37+
38+
> :warning: the major downside of using this format is that vendored modules has version in their path. So once you want to upgrade module version, you will need to modify both `Terrafile` and all module usages. Check out [Community Terrafile Format](#community-terrafile-format) which doesn't have such downside
39+
40+
An example Terrafile:
41+
42+
```yaml
43+
[email protected]:segmentio/terracode-modules:
44+
- chamber_v2.0.0
45+
- iam_v1.0.0
46+
- master
47+
```
3648
3749
```sh
3850
$ terrafile
39-
[*] Cloning [email protected]:segmentio/my-modules
51+
[*] Cloning [email protected]:segmentio/terracode-modules
4052
[*] Vendoring ref chamber_v2.0.0
41-
[*] Vendoring ref constants_v1.0.1
4253
[*] Vendoring ref iam_v1.0.0
43-
[*] Vendoring ref service_v1.0.4
44-
[*] Vendoring ref rds_v0.0.5
45-
[*] Vendoring ref task-role_v1.0.5
4654
[*] Vendoring ref master
4755
```
4856

49-
Terrafile config file in custom directory
50-
5157
```sh
52-
$ terrafile -f config/Terrafile
58+
$ ls -A1 .terrafile/segmentio/terracode-modules
59+
chamber_v2.0.0
60+
iam_v1.0.0
61+
master
5362
```
5463

55-
Terraform modules exported to custom directory
64+
## Community Terrafile Format
65+
66+
Community Terrafile is a community supported format implemented in various languages (e.g. [github: coretech/terrafile](https://github.com/coretech/terrafile), [npm: terrafile](https://www.npmjs.com/package/terrafile)). Each version is vendored under `.terrafile/<alias>`
67+
68+
In comparison with [Segment Terrafile Format](#segment-terrafile-format), module versions are not included in the vendored path. So once you want to upgrade module version, only `Terrafile` needs to be updated and all module usages will be left untouched.
69+
70+
An example Terrafile:
71+
72+
```yaml
73+
terracode-modules-chamber:
74+
source: "[email protected]:segmentio/terracode-modules"
75+
version: "chamber_v2.0.0"
76+
terracode-modules-iam:
77+
source: "[email protected]:segmentio/terracode-modules"
78+
version: "iam_v1.0.0"
79+
terracode-modules:
80+
source: "[email protected]:segmentio/terracode-modules"
81+
version: "master"
82+
```
5683
5784
```sh
58-
$ terrafile -p /path/to/custom_directory
85+
$ terrafile
86+
[*] Cloning [email protected]:segmentio/terracode-modules
87+
[*] Vendoring ref chamber_v2.0.0
88+
[*] Vendoring ref iam_v1.0.0
89+
[*] Vendoring ref master
90+
```
91+
92+
```sh
93+
$ ls -A1 .terrafile
94+
terracode-modules-chamber
95+
terracode-modules-iam
96+
terracode-modules
5997
```

main.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import (
55
"io/ioutil"
66
"os"
77
"os/exec"
8-
"path/filepath"
98
"strings"
109

1110
"github.com/jessevdk/go-flags"
1211
"github.com/nritholtz/stdemuxerhook"
1312
log "github.com/sirupsen/logrus"
14-
"gopkg.in/yaml.v2"
1513
)
1614

1715
var opts struct {
@@ -84,7 +82,7 @@ func gitCheckoutRef(repositoryPath string, ref string, destinationDir string) {
8482
}
8583

8684
func main() {
87-
//fmt.Printf("Terrafile: version %v, commit %v, built at %v \n", version, commit, date)
85+
// fmt.Printf("Terrafile: version %v, commit %v, built at %v \n", version, commit, date)
8886
_, err := flags.Parse(&opts)
8987

9088
// Invalid choice
@@ -98,27 +96,32 @@ func main() {
9896
panic(err)
9997
}
10098

101-
// Parse File
102-
var config map[string][]string
103-
if err := yaml.Unmarshal(yamlFile, &config); err != nil {
99+
// Parse Terrafile
100+
sourceDependenciesMap, err := parseTerrafile(yamlFile)
101+
if err != nil {
102+
panic(err)
103+
}
104+
105+
// Cleanup module path
106+
if err := os.RemoveAll(opts.ModulePath); err != nil {
104107
panic(err)
105108
}
106109

107-
// Clone modules
108-
os.RemoveAll(opts.ModulePath)
109-
os.MkdirAll(opts.ModulePath, os.ModePerm)
110-
for source, refs := range config {
110+
if err := os.MkdirAll(opts.ModulePath, os.ModePerm); err != nil {
111+
panic(err)
112+
}
113+
114+
for source, dependencies := range sourceDependenciesMap {
111115
fmt.Printf("[*] Cloning %s\n", source)
112116
repo := gitClone(source)
113-
for _, ref := range refs {
114-
pathParts := strings.Split(source, ":")
115-
repositoryName := pathParts[1]
116-
fmt.Printf("[*] Vendoring ref %s\n", ref)
117-
targetPath, err := filepath.Abs(fmt.Sprintf("%s/%s/%s", opts.ModulePath, repositoryName, ref))
117+
for _, dependency := range dependencies {
118+
fmt.Printf("[*] Vendoring ref %s\n", dependency.Version)
119+
targetPath, err := dependency.GetTargetPath(opts.ModulePath)
118120
if err != nil {
119121
panic(err)
120122
}
121-
gitCheckoutRef(repo, ref, targetPath)
123+
124+
gitCheckoutRef(repo, dependency.Version, targetPath)
122125
}
123126
}
124127
}

main_test.go

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,61 @@ func init() {
2222
}
2323
terrafileBinaryPath = workingDirectory + "/terrafile"
2424
}
25-
func TestTerraformWithTerrafilePath(t *testing.T) {
26-
folder, back := setup(t)
27-
defer back()
2825

29-
testcli.Run(terrafileBinaryPath, "-f", fmt.Sprint(folder, "/Terrafile"))
30-
31-
if !testcli.Success() {
32-
t.Fatalf("Expected to succeed, but failed: %q with message: %q", testcli.Error(), testcli.Stderr())
33-
}
34-
// Assert output
35-
for _, output := range []string{
36-
"Cloning [email protected]:terraform-aws-modules/terraform-aws-vpc",
37-
"Vendoring ref master",
38-
"Vendoring ref v1.46.0",
39-
} {
40-
assert.Contains(t, testcli.Stdout(), output)
26+
func TestTerrafile(t *testing.T) {
27+
tests := []*struct {
28+
Description string
29+
TerrafileCreator terrafileCreator
30+
ExpectedModules []string
31+
}{
32+
{
33+
Description: "Segment Terrafile Format",
34+
TerrafileCreator: createSegmentTerrafile,
35+
ExpectedModules: []string{
36+
"terraform-aws-modules/terraform-aws-vpc/master",
37+
"terraform-aws-modules/terraform-aws-vpc/v1.46.0",
38+
},
39+
},
40+
{
41+
Description: "Community Terrafile Format",
42+
TerrafileCreator: createCommunityTerrafile,
43+
ExpectedModules: []string{
44+
"terraform-aws-vpc",
45+
"terraform-aws-vpc-1.46.0",
46+
},
47+
},
4148
}
42-
// Assert files exist
43-
for _, moduleName := range []string{
44-
"terraform-aws-modules/terraform-aws-vpc/master",
45-
"terraform-aws-modules/terraform-aws-vpc/v1.46.0",
46-
} {
47-
assert.DirExists(t, path.Join(workingDirectory, "./.terrafile", moduleName))
49+
50+
for _, test := range tests {
51+
t.Run(test.Description, func(t *testing.T) {
52+
folder, back := setup(t, test.TerrafileCreator)
53+
defer back()
54+
defer func() {
55+
assert.NoError(t, os.RemoveAll(path.Join(workingDirectory, "./.terrafile")))
56+
}()
57+
58+
testcli.Run(terrafileBinaryPath, "-d", "-f", fmt.Sprint(folder, "/Terrafile"))
59+
60+
if !testcli.Success() {
61+
t.Fatalf("Expected to succeed, but failed: %q \nStdout: %q \nStderr: %q", testcli.Error(), testcli.Stdout(), testcli.Stderr())
62+
}
63+
// Assert output
64+
for _, output := range []string{
65+
"Cloning [email protected]:terraform-aws-modules/terraform-aws-vpc",
66+
"Vendoring ref master",
67+
"Vendoring ref v1.46.0",
68+
} {
69+
assert.Contains(t, testcli.Stdout(), output)
70+
}
71+
// Assert files exist
72+
for _, moduleName := range test.ExpectedModules {
73+
assert.DirExists(t, path.Join(workingDirectory, "./.terrafile", moduleName))
74+
}
75+
})
4876
}
4977
}
5078

51-
func setup(t *testing.T) (current string, back func()) {
79+
func setup(t *testing.T, createTerrafile terrafileCreator) (current string, back func()) {
5280
folder, err := ioutil.TempDir("", "")
5381
assert.NoError(t, err)
5482
createTerrafile(t, folder)
@@ -61,10 +89,23 @@ func createFile(t *testing.T, filename string, contents string) {
6189
assert.NoError(t, ioutil.WriteFile(filename, []byte(contents), 0644))
6290
}
6391

64-
func createTerrafile(t *testing.T, folder string) {
92+
type terrafileCreator func(t *testing.T, folder string)
93+
94+
func createSegmentTerrafile(t *testing.T, folder string) {
6595
var yaml = `[email protected]:terraform-aws-modules/terraform-aws-vpc:
6696
- v1.46.0
6797
- master
6898
`
6999
createFile(t, path.Join(folder, "Terrafile"), yaml)
70100
}
101+
102+
func createCommunityTerrafile(t *testing.T, folder string) {
103+
var yaml = `terraform-aws-vpc:
104+
source: "[email protected]:terraform-aws-modules/terraform-aws-vpc"
105+
version: master
106+
terraform-aws-vpc-1.46.0:
107+
source: "[email protected]:terraform-aws-modules/terraform-aws-vpc"
108+
version: v1.46.0
109+
`
110+
createFile(t, path.Join(folder, "Terrafile"), yaml)
111+
}

0 commit comments

Comments
 (0)