Skip to content

Commit 6baf1f0

Browse files
authored
Add support for injecting meta-arguments (#20)
Signed-off-by: Yoriyasu Yano <[email protected]>
1 parent 53d89f1 commit 6baf1f0

File tree

8 files changed

+284
-6
lines changed

8 files changed

+284
-6
lines changed

src/_custom/helpers.libsonnet

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,31 @@ local objItemsAll(obj) =
5454
];
5555

5656

57+
// isStringArray returns true if the given value is an array with all elements as string.
58+
//
59+
// Args:
60+
// v (any): The value being evaluated.
61+
//
62+
// Returns:
63+
// A boolean indicating whether the given arg is a string array.
64+
local isStringArray(v) =
65+
std.isArray(v)
66+
&& (
67+
// We temporarily avoid using std.all since the linter does not support it.
68+
std.foldl(
69+
function(x, y) (x && y),
70+
[
71+
std.isString(i)
72+
for i in v
73+
],
74+
true,
75+
)
76+
);
77+
78+
5779
{
5880
mergeAll:: mergeAll,
5981
objItems:: objItems,
6082
objItemsAll:: objItemsAll,
83+
isStringArray:: isStringArray,
6184
}

src/_custom/main.libsonnet

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
(import './root.libsonnet')
2+
+ (import './meta.libsonnet')
23
+ (import './helpers.libsonnet')

src/_custom/meta.libsonnet

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Meta-argument constructor functions. Refer to the meta-arguments tab on
2+
// https://developer.hashicorp.com/terraform/language for more information.
3+
//
4+
// This can be used as arguments to the `_meta` parameter for any resource
5+
// or data source constructor generated by libgenerator.
6+
7+
local h = import './helpers.libsonnet';
8+
9+
// newMeta will generate an object that can be mixed into any resource or data
10+
// source to set the Terraform meta arguments.
11+
local newMeta(count=null, depends_on=null, for_each=null, provider=null, lifecycle=null) =
12+
local maybeCount =
13+
if count != null then
14+
{ count: count }
15+
else
16+
{};
17+
18+
local maybeDependsOn =
19+
if depends_on != null then
20+
{ depends_on: depends_on }
21+
else
22+
{};
23+
24+
local maybeForEach =
25+
if for_each != null then
26+
{ for_each: for_each }
27+
else
28+
{};
29+
30+
local maybeProvider =
31+
if provider != null then
32+
{ provider: provider }
33+
else
34+
{};
35+
36+
local maybeLifecycle =
37+
if lifecycle != null then
38+
{ lifecycle: lifecycle }
39+
else
40+
{};
41+
42+
maybeCount
43+
+ maybeDependsOn
44+
+ maybeForEach
45+
+ maybeProvider
46+
+ maybeLifecycle;
47+
48+
49+
// newModuleMeta will generate an object that can be mixed into any module call to set the Terraform meta arguments.
50+
local newModuleMeta(count=null, depends_on=null, for_each=null, providers=null) =
51+
local maybeCount =
52+
if count != null then
53+
{ count: count }
54+
else
55+
{};
56+
57+
local maybeDependsOn =
58+
if depends_on != null then
59+
{ depends_on: depends_on }
60+
else
61+
{};
62+
63+
local maybeForEach =
64+
if for_each != null then
65+
{ for_each: for_each }
66+
else
67+
{};
68+
69+
local maybeProviders =
70+
if providers != null then
71+
if std.isObject(providers) then
72+
{ providers: providers }
73+
else
74+
error 'providers meta argument must be a map'
75+
else
76+
{};
77+
78+
maybeCount
79+
+ maybeDependsOn
80+
+ maybeForEach
81+
+ maybeProviders;
82+
83+
84+
// newLifecycle will generate a new lifecycle block. Note that unlike the other functions, this includes type checking
85+
// due to the Terraform requirement that the lifecycle block only supports literal values only. As such, it is easier to
86+
// do a type check on the args since there is no possibility to use complex Terraform expressions (which will reduce to
87+
// a string type in jsonnet).
88+
local newLifecycle(
89+
create_before_destroy=null,
90+
prevent_destroy=null,
91+
ignore_changes=null,
92+
replace_triggered_by=null,
93+
precondition=null,
94+
postcondition=null,
95+
) =
96+
local maybeCreateBeforeDestroy =
97+
if create_before_destroy != null then
98+
if std.isBoolean(create_before_destroy) then
99+
{ create_before_destroy: create_before_destroy }
100+
else
101+
error 'lifecycle meta argument attr create_before_destroy must be a boolean'
102+
else
103+
{};
104+
105+
local maybePreventDestroy =
106+
if prevent_destroy != null then
107+
if std.isBoolean(prevent_destroy) then
108+
{ prevent_destroy: prevent_destroy }
109+
else
110+
error 'lifecycle meta argument attr prevent_destroy must be a boolean'
111+
else
112+
{};
113+
114+
local maybeIgnoreChanges =
115+
if ignore_changes != null then
116+
if h.isStringArray(ignore_changes) then
117+
{ ignore_changes: ignore_changes }
118+
else
119+
error 'lifecycle meta argument attr ignore_changes must be a string array'
120+
else
121+
{};
122+
123+
local maybeReplaceTriggeredBy =
124+
if replace_triggered_by != null then
125+
if h.isStringArray(replace_triggered_by) then
126+
{ replace_triggered_by: replace_triggered_by }
127+
else
128+
error 'lifecycle meta argument attr replace_triggered_by must be a string array'
129+
else
130+
{};
131+
132+
local maybePrecondition =
133+
if precondition != null then
134+
if std.isArray(precondition) then
135+
{ precondition: precondition }
136+
else
137+
error 'lifecycle meta argument attr precondition must be an array of condition blocks'
138+
else
139+
{};
140+
141+
local maybePostcondition =
142+
if postcondition != null then
143+
if std.isArray(postcondition) then
144+
{ postcondition: postcondition }
145+
else
146+
error 'lifecycle meta argument attr postcondition must be an array of condition blocks'
147+
else
148+
{};
149+
150+
maybeCreateBeforeDestroy
151+
+ maybePreventDestroy
152+
+ maybeIgnoreChanges
153+
+ maybeReplaceTriggeredBy
154+
+ maybePrecondition
155+
+ maybePostcondition;
156+
157+
158+
// newCondition will generate a new condition block that can be used as part of precondition or postcondition in the
159+
// lifecycle block.
160+
local newCondition(condition, error_message) =
161+
{
162+
condition: condition,
163+
error_message: error_message,
164+
};
165+
166+
167+
// root object
168+
{
169+
meta:: {
170+
new:: newMeta,
171+
newForModule:: newModuleMeta,
172+
lifecycle:: {
173+
new:: newLifecycle,
174+
condition:: {
175+
new:: newCondition,
176+
},
177+
},
178+
},
179+
}

src/_custom/root.libsonnet

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,20 @@ local withProvider(name, attrs, alias=null, src=null, version=null) =
7171
// type (string): The resource type to create (e.g., aws_instance, null_resource, etc).
7272
// label (string): The label to apply to the instance of the resource.
7373
// attrs (obj): The attributes for the instance of the resource being created.
74+
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
75+
// the meta-arguments on the attrs object, it is recommended to use the `_meta` arg to highlight the
76+
// meta-arguments.
77+
// TODO: add type checking
7478
//
7579
// Returns:
7680
// A mixin object that injects the new resource into the root Terraform configuration.
77-
local withResource(type, label, attrs) = {
81+
local withResource(type, label, attrs, _meta={}) = {
7882
resource+: {
7983
[type]+: {
80-
[label]: attrs,
84+
[label]: (
85+
attrs
86+
+ _meta
87+
),
8188
},
8289
},
8390

@@ -114,13 +121,20 @@ local withResource(type, label, attrs) = {
114121
// type (string): The data source type to create (e.g., aws_instance, local_file, etc).
115122
// label (string): The label to apply to the instance of the data source.
116123
// attrs (obj): The attributes for the instance of the data source to read.
124+
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
125+
// the meta-arguments on the attrs object, it is recommended to use the `_meta` arg to highlight the
126+
// meta-arguments.
127+
// TODO: add type checking
117128
//
118129
// Returns:
119130
// A mixin object that injects the new data source into the root Terraform configuration.
120-
local withData(type, label, attrs) = {
131+
local withData(type, label, attrs, _meta={}) = {
121132
data+: {
122133
[type]+: {
123-
[label]: attrs,
134+
[label]: (
135+
attrs
136+
+ _meta
137+
),
124138
},
125139
},
126140

@@ -161,10 +175,14 @@ local withData(type, label, attrs) = {
161175
// inputs (obj): The input values to pass into the module block.
162176
// version (string): The version of the module source to pull in, if the module source references a registry. When
163177
// null, the version field is omitted from the resulting module block.
178+
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
179+
// the meta-arguments on the inputs object, it is recommended to use the `_meta` arg to highlight the
180+
// meta-arguments.
181+
// TODO: add type checking
164182
//
165183
// Returns:
166184
// A mixin object that injects the new module block into the root Terraform configuration.
167-
local withModule(name, source, inputs, version=null) =
185+
local withModule(name, source, inputs, version=null, _meta={}) =
168186
local maybeVersion =
169187
if version != null then
170188
{ version: version }
@@ -176,7 +194,8 @@ local withModule(name, source, inputs, version=null) =
176194
[name]:
177195
{ source: source }
178196
+ maybeVersion
179-
+ inputs,
197+
+ inputs
198+
+ _meta,
180199
},
181200

182201
_ref+:: {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"arrayNestedString": false,
3+
"arrayNestedStringFlattened": true,
4+
"arrayNumber": false,
5+
"arrayObject": false,
6+
"arrayString": true,
7+
"emptyArray": true,
8+
"number": false,
9+
"object": false,
10+
"string": false
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
local h = import 'src/_custom/helpers.libsonnet';
2+
3+
{
4+
number: h.isStringArray(42),
5+
string: h.isStringArray('hello world'),
6+
object: h.isStringArray({ msg: 'hello world' }),
7+
emptyArray: h.isStringArray([]),
8+
arrayNumber: h.isStringArray([42]),
9+
arrayObject: h.isStringArray([{ msg: 'hello world' }]),
10+
arrayString: h.isStringArray(['hello', 'world']),
11+
arrayNestedString: h.isStringArray([
12+
['hello'],
13+
['world'],
14+
]),
15+
arrayNestedStringFlattened: h.isStringArray(
16+
std.flattenArrays([
17+
['hello'],
18+
['world'],
19+
]),
20+
),
21+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
local tf = import 'main.libsonnet';
2+
3+
tf.withResource(
4+
'null_resource',
5+
'foo',
6+
{},
7+
_meta=tf.meta.new(count=5),
8+
)
9+
+ tf.withOutput('output', { num_created: '${length(null_resource.foo)}' })

test/unit_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ func TestUnitRef(t *testing.T) {
142142
g.Expect(out.NullResourceID).NotTo(Equal(""))
143143
}
144144

145+
func TestUnitMeta(t *testing.T) {
146+
t.Parallel()
147+
g := NewGomegaWithT(t)
148+
149+
var out struct {
150+
NumCreated int `json:"num_created"`
151+
}
152+
153+
metaPath := filepath.Join(unitTestFixtureDir, "meta")
154+
jsonnetFPath := filepath.Join(metaPath, "main.tf.jsonnet")
155+
err := renderAndApplyE(t, jsonnetFPath, nil, &out)
156+
g.Expect(err).NotTo(HaveOccurred())
157+
g.Expect(out.NumCreated).To(Equal(5))
158+
}
159+
145160
func renderAndApplyE(
146161
t *testing.T,
147162
jsonnetFPath string,

0 commit comments

Comments
 (0)