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