1package template_test
2
3import (
4	"reflect"
5	"strings"
6	"testing"
7
8	"github.com/wallix/awless/aws/spec"
9	"github.com/wallix/awless/template"
10	"github.com/wallix/awless/template/env"
11	"github.com/wallix/awless/template/internal/ast"
12)
13
14func TestDryRun(t *testing.T) {
15	env := template.NewEnv().WithLookupCommandFunc(func(tokens ...string) interface{} {
16		return awsspec.MockAWSSessionFactory.Build(strings.Join(tokens, ""))()
17	}).Build()
18
19	t.Run("return error", func(t *testing.T) {
20		tpl := template.MustParse("create instance userdata=/invalid-file count=1 image=ami-123456 name=any subnet=any type=t2.micro")
21		_, _, err := template.Compile(tpl, env, template.NewRunnerCompileMode)
22		if err != nil {
23			t.Fatal(err)
24		}
25		if _, err := tpl.DryRun(template.NewRunEnv(env)); err == nil {
26			t.Fatal("expected error got none")
27		}
28	})
29}
30
31func TestParamsProcessing(t *testing.T) {
32	env := template.NewEnv().WithLookupCommandFunc(func(tokens ...string) interface{} {
33		return awsspec.MockAWSSessionFactory.Build(strings.Join(tokens, ""))()
34	}).Build()
35
36	t.Run("unexpected param", func(t *testing.T) {
37		tpl := template.MustParse("create instance invalid=any")
38		_, _, err := template.Compile(tpl, env, template.NewRunnerCompileMode)
39		if err == nil {
40			t.Fatal("expected err got none")
41		}
42		if got, want := err.Error(), "create instance: unexpected param(s): invalid"; !strings.Contains(got, want) {
43			t.Fatalf("%s should contain %s", got, want)
44		}
45	})
46
47	t.Run("normalizing missing required params as holes", func(t *testing.T) {
48		tpl := template.MustParse("create instance image=ami-123456")
49		compiled, _, _ := template.Compile(tpl, env, template.NewRunnerCompileMode)
50		if got, want := compiled.String(), "create instance count={instance.count} image=ami-123456 name={instance.name} subnet={instance.subnet} type={instance.type}"; got != want {
51			t.Fatalf("%s should contain %s", got, want)
52		}
53	})
54
55	t.Run("format validation", func(t *testing.T) {
56		tpl := template.MustParse("check instance state=woot id=i-45678 timeout=180")
57		_, _, err := template.Compile(tpl, env, template.NewRunnerCompileMode)
58		if err == nil {
59			t.Fatal("expected err got none")
60		}
61		if got, want := err.Error(), "expected any of"; !strings.Contains(got, want) {
62			t.Fatalf("%s should contain %s", got, want)
63		}
64	})
65}
66
67func TestWholeCompilation(t *testing.T) {
68	tcases := []struct {
69		tpl                  string
70		expect               string
71		expProcessedFillers  map[string]interface{}
72		expResolvedVariables map[string]interface{}
73	}{
74		{
75			tpl: `subnetname = my-subnet
76vpcref=@vpc
77testsubnet = create subnet cidr={test.cidr} vpc=$vpcref name=$subnetname
78update subnet id=$testsubnet public=true
79instancecount = {instance.count}
80create instance subnet=$testsubnet image=ami-12345 count=$instancecount name='my test instance'`,
81			expect: `testsubnet = create subnet cidr=10.0.2.0/24 name=my-subnet vpc=vpc-1234
82update subnet id=$testsubnet public=true
83create instance count=42 image=ami-12345 name='my test instance' subnet=$testsubnet type=t2.micro`,
84			expProcessedFillers:  map[string]interface{}{"instance.type": "t2.micro", "test.cidr": "10.0.2.0/24", "instance.count": 42},
85			expResolvedVariables: map[string]interface{}{"subnetname": "my-subnet", "vpcref": "vpc-1234", "instancecount": 42},
86		},
87		{
88			tpl: `
89create loadbalancer subnets=[sub-1234, sub-2345,@subalias,@subalias] name=mylb
90sub1 = create subnet cidr={test.cidr} vpc=@vpc name=subnet1
91sub2 = create subnet cidr=10.0.3.0/24 vpc=@vpc name=subnet2
92create loadbalancer subnets=[$sub1, $sub2, sub-3456,{backup-subnet}] name=mylb2
93`,
94			expect: `create loadbalancer name=mylb subnets=[sub-1234,sub-2345,sub-1111,sub-1111]
95sub1 = create subnet cidr=10.0.2.0/24 name=subnet1 vpc=vpc-1234
96sub2 = create subnet cidr=10.0.3.0/24 name=subnet2 vpc=vpc-1234
97create loadbalancer name=mylb2 subnets=[$sub1,$sub2,sub-3456,sub-0987]`,
98			expProcessedFillers:  map[string]interface{}{"test.cidr": "10.0.2.0/24", "backup-subnet": "sub-0987"},
99			expResolvedVariables: map[string]interface{}{},
100		},
101		{
102			tpl: `
103lb0 = create loadbalancer subnets=[sub-1234, sub-2345,@subalias,@subalias] name=mylb
104sub1 = create subnet cidr={test.cidr} vpc=@vpc name=subnet1
105sub2 = create subnet cidr=10.0.3.0/24 vpc=@vpc name=subnet2
106lb1 = create loadbalancer subnets=[$sub1, $sub2, sub-3456,{backup-subnet}] name=mylb2
107`,
108			expect: `lb0 = create loadbalancer name=mylb subnets=[sub-1234,sub-2345,sub-1111,sub-1111]
109sub1 = create subnet cidr=10.0.2.0/24 name=subnet1 vpc=vpc-1234
110sub2 = create subnet cidr=10.0.3.0/24 name=subnet2 vpc=vpc-1234
111lb1 = create loadbalancer name=mylb2 subnets=[$sub1,$sub2,sub-3456,sub-0987]`,
112			expProcessedFillers:  map[string]interface{}{"test.cidr": "10.0.2.0/24", "backup-subnet": "sub-0987"},
113			expResolvedVariables: map[string]interface{}{},
114		},
115		{
116			tpl: `
117			a = "mysubnet-1"
118b = $a
119c = {mysubnet2.hole}
120d = [$b,$c,{mysubnet3.hole},mysubnet-4]
121create loadbalancer subnets=$d name=lb1
122e=$b
123secondlb = create loadbalancer subnets=[$e,mysubnet-4,{mysubnet5.hole}] name=lb2
124`,
125			expect: `create loadbalancer name=lb1 subnets=[mysubnet-1,mysubnet-2,mysubnet-3,mysubnet-4]
126secondlb = create loadbalancer name=lb2 subnets=[mysubnet-1,mysubnet-4,mysubnet-5]`,
127			expProcessedFillers:  map[string]interface{}{"mysubnet2.hole": "mysubnet-2", "mysubnet3.hole": "mysubnet-3", "mysubnet5.hole": "mysubnet-5"},
128			expResolvedVariables: map[string]interface{}{"a": "mysubnet-1", "b": "mysubnet-1", "e": "mysubnet-1", "c": "mysubnet-2", "d": []interface{}{"mysubnet-1", "mysubnet-2", "mysubnet-3", "mysubnet-4"}},
129		},
130		{
131			tpl: `
132name = instance-{instance.name}-{version}
133name2 = my-test-{hole}
134create instance image=ami-1234 name=$name subnet=subnet-{version}
135create instance image=ami-1234 name=$name2 subnet=sub1234
136`,
137			expect: `create instance count=42 image=ami-1234 name=instance-myinstance-10 subnet=subnet-10 type=t2.micro
138create instance count=42 image=ami-1234 name=my-test-sub-2345 subnet=sub1234 type=t2.micro`,
139			expProcessedFillers:  map[string]interface{}{"instance.name": "myinstance", "version": 10, "instance.type": "t2.micro", "instance.count": 42, "hole": "@sub"},
140			expResolvedVariables: map[string]interface{}{"name": "instance-myinstance-10", "name2": "my-test-sub-2345"},
141		},
142		{
143			tpl: `
144name = "ins$\ta{nce}-"+{instance.name}+{version}
145name2 = {hole}+{hole}+"text-with $Special {char-s"
146create instance image=ami-1234 name=$name subnet=subnet-{version}
147create instance image=ami-1234 name=$name2 subnet=sub1234
148`,
149			expect: `create instance count=42 image=ami-1234 name='ins$\ta{nce}-myinstance10' subnet=subnet-10 type=t2.micro
150create instance count=42 image=ami-1234 name='sub-2345sub-2345text-with $Special {char-s' subnet=sub1234 type=t2.micro`,
151			expProcessedFillers:  map[string]interface{}{"instance.name": "myinstance", "version": 10, "instance.type": "t2.micro", "instance.count": 42, "hole": "@sub"},
152			expResolvedVariables: map[string]interface{}{"name": "ins$\\ta{nce}-myinstance10", "name2": "sub-2345sub-2345text-with $Special {char-s"},
153		},
154		{
155			tpl: `
156create loadbalancer name=mylb subnets={private.subnets}
157`,
158			expect:               `create loadbalancer name=mylb subnets=[sub-1234,sub-2345]`,
159			expProcessedFillers:  map[string]interface{}{"private.subnets": []interface{}{"sub-1234", "sub-2345"}},
160			expResolvedVariables: map[string]interface{}{},
161		},
162		{
163			tpl: `
164create loadbalancer name=mylb subnets=subnet-1, subnet-2
165`,
166			expect:               `create loadbalancer name=mylb subnets=[subnet-1,subnet-2]`,
167			expProcessedFillers:  map[string]interface{}{},
168			expResolvedVariables: map[string]interface{}{},
169		}, //retro-compatibility with old list style, without brackets
170	}
171
172	for i, tcase := range tcases {
173		cenv := template.NewEnv().WithAliasFunc(func(p, v string) string {
174			vals := map[string]string{
175				"vpc":      "vpc-1234",
176				"subalias": "sub-1111",
177				"sub":      "sub-2345",
178			}
179			return vals[v]
180		}).WithLookupCommandFunc(func(tokens ...string) interface{} {
181			return awsspec.MockAWSSessionFactory.Build(strings.Join(tokens, ""))()
182		}).Build()
183
184		cenv.Push(env.FILLERS, map[string]interface{}{
185			"instance.type":   "t2.micro",
186			"test.cidr":       "10.0.2.0/24",
187			"instance.count":  42,
188			"unused":          "filler",
189			"backup-subnet":   "sub-0987",
190			"mysubnet2.hole":  "mysubnet-2",
191			"mysubnet3.hole":  "mysubnet-3",
192			"mysubnet5.hole":  "mysubnet-5",
193			"version":         10,
194			"instance.name":   "myinstance",
195			"hole":            ast.NewAliasNode("sub"),
196			"private.subnets": ast.NewListNode([]interface{}{"sub-1234", "sub-2345"}),
197		})
198
199		inTpl := template.MustParse(tcase.tpl)
200
201		compiled, _, err := template.Compile(inTpl, cenv, template.NewRunnerCompileMode)
202		if err != nil {
203			t.Fatalf("%d: %s", i+1, err)
204		}
205
206		if got, want := compiled.String(), tcase.expect; got != want {
207			t.Fatalf("%d: got\n%s\nwant\n%s", i+1, got, want)
208		}
209
210		if got, want := cenv.Get(env.PROCESSED_FILLERS), tcase.expProcessedFillers; !reflect.DeepEqual(got, want) {
211			t.Fatalf("%d: got %v, want %v", i+1, got, want)
212		}
213
214		if got, want := cenv.Get(env.RESOLVED_VARS), tcase.expResolvedVariables; !reflect.DeepEqual(got, want) {
215			t.Fatalf("%d: got\n%#v\nwant\n%#v\n", i+1, got, want)
216		}
217	}
218}
219
220func TestExternallyProvidedParams(t *testing.T) {
221	tcases := []struct {
222		template            string
223		externalParams      string
224		expect              string
225		expProcessedFillers map[string]interface{}
226	}{
227		{
228			template:            `create instance count=1 image=ami-123 name=test subnet={hole.name} type=t2.micro`,
229			externalParams:      "hole.name=subnet-2345",
230			expect:              `create instance count=1 image=ami-123 name=test subnet=subnet-2345 type=t2.micro`,
231			expProcessedFillers: map[string]interface{}{"hole.name": "subnet-2345"},
232		},
233		{
234			template:            `create instance count=1 image=ami-123 name=test subnet={hole.name} type={instance.type}`,
235			externalParams:      "instance.type=t2.nano hole.name=@subalias",
236			expect:              `create instance count=1 image=ami-123 name=test subnet=subnet-111 type=t2.nano`,
237			expProcessedFillers: map[string]interface{}{"hole.name": "@subalias", "instance.type": "t2.nano"},
238		},
239		{
240			template:            `create loadbalancer name=elbv2 subnets={my.subnets}`,
241			externalParams:      "my.subnets=[@sub1, @sub2]",
242			expect:              `create loadbalancer name=elbv2 subnets=[subnet-123,subnet-234]`,
243			expProcessedFillers: map[string]interface{}{"my.subnets": []interface{}{"@sub1", "@sub2"}},
244		},
245		{
246			template:            `create loadbalancer name={my.name} subnets={my.subnets}`,
247			externalParams:      "my.subnets=sub1, sub2 my.name=loadbalancername",
248			expect:              `create loadbalancer name=loadbalancername subnets=[sub1,sub2]`,
249			expProcessedFillers: map[string]interface{}{"my.name": "loadbalancername", "my.subnets": []interface{}{"sub1", "sub2"}},
250		}, //retro-compatibility with old list style, without brackets
251	}
252	for i, tcase := range tcases {
253		externalFillters, err := template.ParseParams(tcase.externalParams)
254		if err != nil {
255			t.Fatal(err)
256		}
257		cenv := template.NewEnv().WithLookupCommandFunc(func(tokens ...string) interface{} {
258			return awsspec.MockAWSSessionFactory.Build(strings.Join(tokens, ""))()
259		}).WithAliasFunc(func(p, v string) string {
260			vals := map[string]string{
261				"subalias": "subnet-111",
262				"sub1":     "subnet-123",
263				"sub2":     "subnet-234",
264			}
265			return vals[v]
266		}).Build()
267
268		cenv.Push(env.FILLERS, externalFillters)
269
270		inTpl := template.MustParse(tcase.template)
271
272		compiled, _, err := template.Compile(inTpl, cenv, template.NewRunnerCompileMode)
273		if err != nil {
274			t.Fatalf("%d: %s", i+1, err)
275		}
276
277		if got, want := compiled.String(), tcase.expect; got != want {
278			t.Fatalf("%d: got\n%s\nwant\n%s", i+1, got, want)
279		}
280
281		if got, want := cenv.Get(env.PROCESSED_FILLERS), tcase.expProcessedFillers; !reflect.DeepEqual(got, want) {
282			t.Fatalf("%d: got %#v, want %#v", i+1, got, want)
283		}
284	}
285}
286