Skip to content

Commit 6b27b06

Browse files
authored
feat: add terramate.path/name metadata (#87)
1 parent 2767ec1 commit 6b27b06

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed

cmd/terramate/cli/cli.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type cliSpec struct {
6868
Generate struct {
6969
Basedir string `short:"b" optional:"true" help:"Generate code for stacks inside basedir."`
7070
} `cmd:"" help:"Generate terraform code for stacks."`
71+
72+
Metadata struct {
73+
} `cmd:"" help:"shows metadata available on the project"`
7174
}
7275

7376
// Run will run terramate with the provided flags defined on args from the
@@ -220,6 +223,8 @@ func (c *cli) run() error {
220223
return c.runOnStacks(basedir)
221224
case "generate":
222225
return terramate.Generate(c.wd)
226+
case "metadata":
227+
return c.printMetadata()
223228
default:
224229
return fmt.Errorf("unexpected command sequence: %s", c.ctx.Command())
225230
}
@@ -291,6 +296,23 @@ func (c *cli) printStacks(basedir string) error {
291296
return nil
292297
}
293298

299+
func (c *cli) printMetadata() error {
300+
metadata, err := terramate.LoadMetadata(c.wd)
301+
if err != nil {
302+
return err
303+
}
304+
305+
c.log("Available metadata:")
306+
307+
for _, stack := range metadata.Stacks {
308+
c.log("\nstack %q:", stack.Path)
309+
c.log("\tterraform.name=%q", stack.Name)
310+
c.log("\tterraform.path=%q", stack.Path)
311+
}
312+
313+
return nil
314+
}
315+
294316
func (c *cli) runOnStacks(basedir string) error {
295317
var nErrors int
296318

docs/metadata.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ accessed through the variable namespace **terramate**.
66
This can be referenced from any terramate code to reference
77
information like the path of the stack that is being evaluated.
88

9+
To see all metadata available on your project run:
10+
11+
```
12+
terramate metadata
13+
```
914

1015
## terramate.path (string)
1116

metadata.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2021 Mineiros GmbH
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package terramate
16+
17+
import (
18+
"path/filepath"
19+
"strings"
20+
)
21+
22+
// StackMetadata has all metadata loaded per stack
23+
type StackMetadata struct {
24+
Name string
25+
Path string
26+
}
27+
28+
// Metadata has all metadata loader per project
29+
type Metadata struct {
30+
// Stacks is a lexycographicaly sorted (by stack path) list of stack metadata
31+
Stacks []StackMetadata
32+
}
33+
34+
// LoadMetadata loads the project metadata given the project basedir.
35+
func LoadMetadata(basedir string) (Metadata, error) {
36+
stacks, err := ListStacks(basedir)
37+
if err != nil {
38+
return Metadata{}, err
39+
}
40+
41+
stacksMetadata := make([]StackMetadata, len(stacks))
42+
for i, stack := range stacks {
43+
stacksMetadata[i] = StackMetadata{
44+
Name: filepath.Base(stack.Dir),
45+
Path: strings.TrimPrefix(stack.Dir, basedir),
46+
}
47+
}
48+
49+
return Metadata{
50+
Stacks: stacksMetadata,
51+
}, nil
52+
}

metadata_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright 2021 Mineiros GmbH
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package terramate_test
16+
17+
import (
18+
"fmt"
19+
"testing"
20+
21+
"github.com/google/go-cmp/cmp"
22+
"github.com/madlambda/spells/assert"
23+
"github.com/mineiros-io/terramate"
24+
"github.com/mineiros-io/terramate/test/sandbox"
25+
)
26+
27+
func TestLoadMetadata(t *testing.T) {
28+
29+
type testcase struct {
30+
name string
31+
layout []string
32+
want terramate.Metadata
33+
wantErr error
34+
}
35+
36+
tcases := []testcase{
37+
{
38+
name: "no stacks",
39+
layout: []string{},
40+
want: terramate.Metadata{
41+
Stacks: []terramate.StackMetadata{},
42+
},
43+
},
44+
{
45+
name: "single stacks",
46+
layout: []string{"s:stack"},
47+
want: terramate.Metadata{
48+
Stacks: []terramate.StackMetadata{
49+
{
50+
Name: "stack",
51+
Path: "/stack",
52+
},
53+
},
54+
},
55+
},
56+
{
57+
name: "two stacks",
58+
layout: []string{
59+
"s:stack-1",
60+
"s:stack-2",
61+
},
62+
want: terramate.Metadata{
63+
Stacks: []terramate.StackMetadata{
64+
{
65+
Name: "stack-1",
66+
Path: "/stack-1",
67+
},
68+
{
69+
Name: "stack-2",
70+
Path: "/stack-2",
71+
},
72+
},
73+
},
74+
},
75+
{
76+
name: "stack and some non-stack dirs",
77+
layout: []string{
78+
"s:stack",
79+
"d:non-stack",
80+
"d:non-stack-2",
81+
},
82+
want: terramate.Metadata{
83+
Stacks: []terramate.StackMetadata{
84+
{
85+
Name: "stack",
86+
Path: "/stack",
87+
},
88+
},
89+
},
90+
},
91+
{
92+
name: "stacks nested",
93+
layout: []string{
94+
"s:envs/prod/stack-1",
95+
"s:envs/staging/stack-1",
96+
},
97+
want: terramate.Metadata{
98+
Stacks: []terramate.StackMetadata{
99+
{
100+
Name: "stack-1",
101+
Path: "/envs/prod/stack-1",
102+
},
103+
{
104+
Name: "stack-1",
105+
Path: "/envs/staging/stack-1",
106+
},
107+
},
108+
},
109+
},
110+
{
111+
// TODO(katcipis): update to give error when we merge strict stack list logic
112+
name: "single invalid stack",
113+
layout: []string{
114+
fmt.Sprintf("f:invalid-stack/%s:data=notvalidhcl", terramate.ConfigFilename),
115+
},
116+
want: terramate.Metadata{
117+
Stacks: []terramate.StackMetadata{
118+
{
119+
Name: "invalid-stack",
120+
Path: "/invalid-stack",
121+
},
122+
},
123+
},
124+
//wantErr: hcl.ErrMalformedTerramateBlock,
125+
},
126+
{
127+
// TODO(katcipis): update to give error when we merge strict stack list logic
128+
name: "valid stack with invalid stack",
129+
layout: []string{
130+
"s:stack-valid-1",
131+
fmt.Sprintf("f:invalid-stack/%s:data=notvalidhcl", terramate.ConfigFilename),
132+
},
133+
want: terramate.Metadata{
134+
Stacks: []terramate.StackMetadata{
135+
{
136+
Name: "invalid-stack",
137+
Path: "/invalid-stack",
138+
},
139+
{
140+
Name: "stack-valid-1",
141+
Path: "/stack-valid-1",
142+
},
143+
},
144+
},
145+
//wantErr: hcl.ErrMalformedTerramateBlock,
146+
},
147+
}
148+
149+
for _, tcase := range tcases {
150+
t.Run(tcase.name, func(t *testing.T) {
151+
s := sandbox.New(t)
152+
s.BuildTree(tcase.layout)
153+
154+
metadata, err := terramate.LoadMetadata(s.BaseDir())
155+
156+
if tcase.wantErr != nil {
157+
assert.IsError(t, err, tcase.wantErr)
158+
return
159+
}
160+
161+
if diff := cmp.Diff(tcase.want, metadata); diff != "" {
162+
t.Fatalf("want %v != got %v.\ndiff:\n%s", tcase.want, metadata, diff)
163+
}
164+
})
165+
}
166+
}

0 commit comments

Comments
 (0)