Skip to content

Commit

Permalink
Add support for injecting meta-arguments (#20)
Browse files Browse the repository at this point in the history
Signed-off-by: Yoriyasu Yano <[email protected]>
  • Loading branch information
yorinasub17 authored Dec 16, 2022
1 parent 53d89f1 commit 6baf1f0
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 6 deletions.
23 changes: 23 additions & 0 deletions src/_custom/helpers.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,31 @@ local objItemsAll(obj) =
];


// isStringArray returns true if the given value is an array with all elements as string.
//
// Args:
// v (any): The value being evaluated.
//
// Returns:
// A boolean indicating whether the given arg is a string array.
local isStringArray(v) =
std.isArray(v)
&& (
// We temporarily avoid using std.all since the linter does not support it.
std.foldl(
function(x, y) (x && y),
[
std.isString(i)
for i in v
],
true,
)
);


{
mergeAll:: mergeAll,
objItems:: objItems,
objItemsAll:: objItemsAll,
isStringArray:: isStringArray,
}
1 change: 1 addition & 0 deletions src/_custom/main.libsonnet
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
(import './root.libsonnet')
+ (import './meta.libsonnet')
+ (import './helpers.libsonnet')
179 changes: 179 additions & 0 deletions src/_custom/meta.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Meta-argument constructor functions. Refer to the meta-arguments tab on
// https://developer.hashicorp.com/terraform/language for more information.
//
// This can be used as arguments to the `_meta` parameter for any resource
// or data source constructor generated by libgenerator.

local h = import './helpers.libsonnet';

// newMeta will generate an object that can be mixed into any resource or data
// source to set the Terraform meta arguments.
local newMeta(count=null, depends_on=null, for_each=null, provider=null, lifecycle=null) =
local maybeCount =
if count != null then
{ count: count }
else
{};

local maybeDependsOn =
if depends_on != null then
{ depends_on: depends_on }
else
{};

local maybeForEach =
if for_each != null then
{ for_each: for_each }
else
{};

local maybeProvider =
if provider != null then
{ provider: provider }
else
{};

local maybeLifecycle =
if lifecycle != null then
{ lifecycle: lifecycle }
else
{};

maybeCount
+ maybeDependsOn
+ maybeForEach
+ maybeProvider
+ maybeLifecycle;


// newModuleMeta will generate an object that can be mixed into any module call to set the Terraform meta arguments.
local newModuleMeta(count=null, depends_on=null, for_each=null, providers=null) =
local maybeCount =
if count != null then
{ count: count }
else
{};

local maybeDependsOn =
if depends_on != null then
{ depends_on: depends_on }
else
{};

local maybeForEach =
if for_each != null then
{ for_each: for_each }
else
{};

local maybeProviders =
if providers != null then
if std.isObject(providers) then
{ providers: providers }
else
error 'providers meta argument must be a map'
else
{};

maybeCount
+ maybeDependsOn
+ maybeForEach
+ maybeProviders;


// newLifecycle will generate a new lifecycle block. Note that unlike the other functions, this includes type checking
// due to the Terraform requirement that the lifecycle block only supports literal values only. As such, it is easier to
// do a type check on the args since there is no possibility to use complex Terraform expressions (which will reduce to
// a string type in jsonnet).
local newLifecycle(
create_before_destroy=null,
prevent_destroy=null,
ignore_changes=null,
replace_triggered_by=null,
precondition=null,
postcondition=null,
) =
local maybeCreateBeforeDestroy =
if create_before_destroy != null then
if std.isBoolean(create_before_destroy) then
{ create_before_destroy: create_before_destroy }
else
error 'lifecycle meta argument attr create_before_destroy must be a boolean'
else
{};

local maybePreventDestroy =
if prevent_destroy != null then
if std.isBoolean(prevent_destroy) then
{ prevent_destroy: prevent_destroy }
else
error 'lifecycle meta argument attr prevent_destroy must be a boolean'
else
{};

local maybeIgnoreChanges =
if ignore_changes != null then
if h.isStringArray(ignore_changes) then
{ ignore_changes: ignore_changes }
else
error 'lifecycle meta argument attr ignore_changes must be a string array'
else
{};

local maybeReplaceTriggeredBy =
if replace_triggered_by != null then
if h.isStringArray(replace_triggered_by) then
{ replace_triggered_by: replace_triggered_by }
else
error 'lifecycle meta argument attr replace_triggered_by must be a string array'
else
{};

local maybePrecondition =
if precondition != null then
if std.isArray(precondition) then
{ precondition: precondition }
else
error 'lifecycle meta argument attr precondition must be an array of condition blocks'
else
{};

local maybePostcondition =
if postcondition != null then
if std.isArray(postcondition) then
{ postcondition: postcondition }
else
error 'lifecycle meta argument attr postcondition must be an array of condition blocks'
else
{};

maybeCreateBeforeDestroy
+ maybePreventDestroy
+ maybeIgnoreChanges
+ maybeReplaceTriggeredBy
+ maybePrecondition
+ maybePostcondition;


// newCondition will generate a new condition block that can be used as part of precondition or postcondition in the
// lifecycle block.
local newCondition(condition, error_message) =
{
condition: condition,
error_message: error_message,
};


// root object
{
meta:: {
new:: newMeta,
newForModule:: newModuleMeta,
lifecycle:: {
new:: newLifecycle,
condition:: {
new:: newCondition,
},
},
},
}
31 changes: 25 additions & 6 deletions src/_custom/root.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,20 @@ local withProvider(name, attrs, alias=null, src=null, version=null) =
// type (string): The resource type to create (e.g., aws_instance, null_resource, etc).
// label (string): The label to apply to the instance of the resource.
// attrs (obj): The attributes for the instance of the resource being created.
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
// the meta-arguments on the attrs object, it is recommended to use the `_meta` arg to highlight the
// meta-arguments.
// TODO: add type checking
//
// Returns:
// A mixin object that injects the new resource into the root Terraform configuration.
local withResource(type, label, attrs) = {
local withResource(type, label, attrs, _meta={}) = {
resource+: {
[type]+: {
[label]: attrs,
[label]: (
attrs
+ _meta
),
},
},

Expand Down Expand Up @@ -114,13 +121,20 @@ local withResource(type, label, attrs) = {
// type (string): The data source type to create (e.g., aws_instance, local_file, etc).
// label (string): The label to apply to the instance of the data source.
// attrs (obj): The attributes for the instance of the data source to read.
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
// the meta-arguments on the attrs object, it is recommended to use the `_meta` arg to highlight the
// meta-arguments.
// TODO: add type checking
//
// Returns:
// A mixin object that injects the new data source into the root Terraform configuration.
local withData(type, label, attrs) = {
local withData(type, label, attrs, _meta={}) = {
data+: {
[type]+: {
[label]: attrs,
[label]: (
attrs
+ _meta
),
},
},

Expand Down Expand Up @@ -161,10 +175,14 @@ local withData(type, label, attrs) = {
// inputs (obj): The input values to pass into the module block.
// version (string): The version of the module source to pull in, if the module source references a registry. When
// null, the version field is omitted from the resulting module block.
// _meta (obj): An optional meta-argument object that (see meta.libsonnet). Note that while technically you can set
// the meta-arguments on the inputs object, it is recommended to use the `_meta` arg to highlight the
// meta-arguments.
// TODO: add type checking
//
// Returns:
// A mixin object that injects the new module block into the root Terraform configuration.
local withModule(name, source, inputs, version=null) =
local withModule(name, source, inputs, version=null, _meta={}) =
local maybeVersion =
if version != null then
{ version: version }
Expand All @@ -176,7 +194,8 @@ local withModule(name, source, inputs, version=null) =
[name]:
{ source: source }
+ maybeVersion
+ inputs,
+ inputs
+ _meta,
},

_ref+:: {
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/helpers/is_string_array/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"arrayNestedString": false,
"arrayNestedStringFlattened": true,
"arrayNumber": false,
"arrayObject": false,
"arrayString": true,
"emptyArray": true,
"number": false,
"object": false,
"string": false
}
21 changes: 21 additions & 0 deletions test/fixtures/helpers/is_string_array/test.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local h = import 'src/_custom/helpers.libsonnet';

{
number: h.isStringArray(42),
string: h.isStringArray('hello world'),
object: h.isStringArray({ msg: 'hello world' }),
emptyArray: h.isStringArray([]),
arrayNumber: h.isStringArray([42]),
arrayObject: h.isStringArray([{ msg: 'hello world' }]),
arrayString: h.isStringArray(['hello', 'world']),
arrayNestedString: h.isStringArray([
['hello'],
['world'],
]),
arrayNestedStringFlattened: h.isStringArray(
std.flattenArrays([
['hello'],
['world'],
]),
),
}
9 changes: 9 additions & 0 deletions test/fixtures/tfunit/meta/main.tf.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
local tf = import 'main.libsonnet';

tf.withResource(
'null_resource',
'foo',
{},
_meta=tf.meta.new(count=5),
)
+ tf.withOutput('output', { num_created: '${length(null_resource.foo)}' })
15 changes: 15 additions & 0 deletions test/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ func TestUnitRef(t *testing.T) {
g.Expect(out.NullResourceID).NotTo(Equal(""))
}

func TestUnitMeta(t *testing.T) {
t.Parallel()
g := NewGomegaWithT(t)

var out struct {
NumCreated int `json:"num_created"`
}

metaPath := filepath.Join(unitTestFixtureDir, "meta")
jsonnetFPath := filepath.Join(metaPath, "main.tf.jsonnet")
err := renderAndApplyE(t, jsonnetFPath, nil, &out)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(out.NumCreated).To(Equal(5))
}

func renderAndApplyE(
t *testing.T,
jsonnetFPath string,
Expand Down

0 comments on commit 6baf1f0

Please sign in to comment.