1package awsat
2
3import (
4	"encoding/base64"
5	"io/ioutil"
6	"os"
7	"testing"
8
9	"github.com/aws/aws-sdk-go/aws"
10	"github.com/aws/aws-sdk-go/aws/client/metadata"
11	"github.com/aws/aws-sdk-go/aws/request"
12	"github.com/aws/aws-sdk-go/service/ec2"
13	"github.com/aws/aws-sdk-go/service/elbv2"
14	"github.com/wallix/awless/aws/spec"
15)
16
17func TestInstance(t *testing.T) {
18	t.Run("create", func(t *testing.T) {
19		t.Run("with distro", func(t *testing.T) {
20			awsspec.DefaultImageResolverCache.Store("canonical:ubuntu:xenial:x86_64:hvm:ebs", []*awsspec.AwsImage{{Id: "ami-123456"}})
21
22			Template("create instance distro=canonical name=myinstance subnet=sub_1 type=t2.nano count=1").
23				Mock(&ec2Mock{
24					RunInstancesFunc: func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) {
25						return &ec2.Reservation{Instances: []*ec2.Instance{{InstanceId: String("new-instance-id")}}}, nil
26					},
27					CreateTagsRequestFunc: func(input *ec2.CreateTagsInput) (req *request.Request, output *ec2.CreateTagsOutput) {
28						output = &ec2.CreateTagsOutput{}
29						req = request.New(aws.Config{}, metadata.ClientInfo{}, request.Handlers{}, nil, &request.Operation{}, input, output)
30						return
31					},
32				}).ExpectInput("RunInstances", &ec2.RunInstancesInput{
33				SubnetId:     String("sub_1"),
34				ImageId:      String("ami-123456"),
35				InstanceType: String("t2.nano"),
36				MinCount:     Int64(1),
37				MaxCount:     Int64(1),
38			}).ExpectInput("CreateTagsRequest", &ec2.CreateTagsInput{
39				Resources: []*string{String("new-instance-id")},
40				Tags: []*ec2.Tag{
41					{Key: String("Name"), Value: String("myinstance")},
42				},
43			}).ExpectCommandResult("new-instance-id").ExpectCalls("RunInstances", "CreateTagsRequest").
44				ExpectRevert("delete instance id=new-instance-id").Run(t)
45		})
46
47		t.Run("with user data", func(t *testing.T) {
48			_, userdataFile, cleanup := generateTmpFile("this is my content with {{ .AWLESS.oneRef }} content")
49			defer cleanup()
50
51			Template("oneRef=awesome\n"+
52				"create instance count=3 image=ami-1234 "+
53				"name=myinstance subnet=sub_1 type=t2.nano keypair=mykp ip=10.2.3.4 "+
54				"userdata="+userdataFile+" securitygroup=sg-1234 lock=true role=myrole").
55				Mock(&ec2Mock{
56					RunInstancesFunc: func(input *ec2.RunInstancesInput) (*ec2.Reservation, error) {
57						return &ec2.Reservation{Instances: []*ec2.Instance{{InstanceId: String("new-instance-id")}}}, nil
58					},
59					CreateTagsRequestFunc: func(input *ec2.CreateTagsInput) (req *request.Request, output *ec2.CreateTagsOutput) {
60						output = &ec2.CreateTagsOutput{}
61						req = request.New(aws.Config{}, metadata.ClientInfo{}, request.Handlers{}, nil, &request.Operation{}, input, output)
62						return
63					},
64				}).ExpectInput("RunInstances", &ec2.RunInstancesInput{
65				SubnetId:              String("sub_1"),
66				ImageId:               String("ami-1234"),
67				InstanceType:          String("t2.nano"),
68				MinCount:              Int64(3),
69				MaxCount:              Int64(3),
70				KeyName:               String("mykp"),
71				PrivateIpAddress:      String("10.2.3.4"),
72				SecurityGroupIds:      []*string{String("sg-1234")},
73				DisableApiTermination: Bool(true),
74				IamInstanceProfile:    &ec2.IamInstanceProfileSpecification{Name: String("myrole")},
75				UserData:              String(base64.StdEncoding.EncodeToString([]byte("this is my content with awesome content"))),
76			}).ExpectInput("CreateTagsRequest", &ec2.CreateTagsInput{
77				Resources: []*string{String("new-instance-id")},
78				Tags: []*ec2.Tag{
79					{Key: String("Name"), Value: String("myinstance")},
80				},
81			}).ExpectCommandResult("new-instance-id").ExpectCalls("RunInstances", "CreateTagsRequest").Run(t)
82		})
83	})
84
85	t.Run("update", func(t *testing.T) {
86		Template("update instance id=id-1234 type=t2.micro lock=true").Mock(&ec2Mock{
87			ModifyInstanceAttributeFunc: func(param0 *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) {
88				return nil, nil
89			},
90		}).ExpectInput("ModifyInstanceAttribute", &ec2.ModifyInstanceAttributeInput{
91			InstanceId:            String("id-1234"),
92			InstanceType:          &ec2.AttributeValue{Value: String("t2.micro")},
93			DisableApiTermination: &ec2.AttributeBooleanValue{Value: Bool(true)},
94		}).
95			ExpectCalls("ModifyInstanceAttribute").Run(t)
96	})
97
98	t.Run("delete", func(t *testing.T) {
99		t.Run("one id", func(t *testing.T) {
100			Template("delete instance id=id-1234").Mock(&ec2Mock{
101				TerminateInstancesFunc: func(param0 *ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error) { return nil, nil },
102			}).ExpectInput("TerminateInstances", &ec2.TerminateInstancesInput{InstanceIds: []*string{String("id-1234")}}).
103				ExpectCalls("TerminateInstances").Run(t)
104		})
105
106		t.Run("multiple ids", func(t *testing.T) {
107			Template("delete instance ids=id-1234,id-2345").Mock(&ec2Mock{
108				TerminateInstancesFunc: func(param0 *ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error) { return nil, nil },
109			}).ExpectInput("TerminateInstances", &ec2.TerminateInstancesInput{InstanceIds: []*string{String("id-1234"), String("id-2345")}}).
110				ExpectCalls("TerminateInstances").Run(t)
111		})
112	})
113
114	t.Run("start", func(t *testing.T) {
115		t.Run("one id", func(t *testing.T) {
116			Template("start instance id=id-1234").Mock(&ec2Mock{
117				StartInstancesFunc: func(param0 *ec2.StartInstancesInput) (*ec2.StartInstancesOutput, error) {
118					return &ec2.StartInstancesOutput{
119						StartingInstances: []*ec2.InstanceStateChange{{InstanceId: String("id-1234")}}}, nil
120				},
121			}).ExpectInput("StartInstances", &ec2.StartInstancesInput{InstanceIds: []*string{String("id-1234")}}).
122				ExpectCalls("StartInstances").Run(t)
123		})
124
125		t.Run("multiple ids", func(t *testing.T) {
126			Template("start instance ids=id-1234,id-2345").Mock(&ec2Mock{
127				StartInstancesFunc: func(param0 *ec2.StartInstancesInput) (*ec2.StartInstancesOutput, error) {
128					return &ec2.StartInstancesOutput{
129						StartingInstances: []*ec2.InstanceStateChange{{InstanceId: String("id-1234")}, {InstanceId: String("id-2345")}}}, nil
130				},
131			}).ExpectInput("StartInstances", &ec2.StartInstancesInput{InstanceIds: []*string{String("id-1234"), String("id-2345")}}).
132				ExpectCalls("StartInstances").Run(t)
133		})
134	})
135
136	t.Run("stop", func(t *testing.T) {
137		t.Run("one id", func(t *testing.T) {
138			Template("stop instance id=id-1234").Mock(&ec2Mock{
139				StopInstancesFunc: func(param0 *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error) {
140					return &ec2.StopInstancesOutput{
141						StoppingInstances: []*ec2.InstanceStateChange{{InstanceId: String("id-1234")}}}, nil
142				},
143			}).ExpectInput("StopInstances", &ec2.StopInstancesInput{InstanceIds: []*string{String("id-1234")}}).
144				ExpectCalls("StopInstances").Run(t)
145		})
146
147		t.Run("multiple ids", func(t *testing.T) {
148			Template("stop instance ids=id-1234,id-2345").Mock(&ec2Mock{
149				StopInstancesFunc: func(param0 *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error) {
150					return &ec2.StopInstancesOutput{
151						StoppingInstances: []*ec2.InstanceStateChange{{InstanceId: String("id-1234")}, {InstanceId: String("id-2345")}}}, nil
152				},
153			}).ExpectInput("StopInstances", &ec2.StopInstancesInput{InstanceIds: []*string{String("id-1234"), String("id-2345")}}).
154				ExpectCalls("StopInstances").Run(t)
155		})
156	})
157
158	t.Run("restart", func(t *testing.T) {
159		t.Run("one id", func(t *testing.T) {
160			Template("restart instance id=id-1234").Mock(&ec2Mock{
161				RebootInstancesFunc: func(param0 *ec2.RebootInstancesInput) (*ec2.RebootInstancesOutput, error) {
162					return &ec2.RebootInstancesOutput{}, nil
163				},
164			}).ExpectInput("RebootInstances", &ec2.RebootInstancesInput{InstanceIds: []*string{String("id-1234")}}).
165				ExpectCalls("RebootInstances").Run(t)
166		})
167
168		t.Run("multiple ids", func(t *testing.T) {
169			Template("restart instance ids=id-1234,id-2345").Mock(&ec2Mock{
170				RebootInstancesFunc: func(param0 *ec2.RebootInstancesInput) (*ec2.RebootInstancesOutput, error) {
171					return &ec2.RebootInstancesOutput{}, nil
172				},
173			}).ExpectInput("RebootInstances", &ec2.RebootInstancesInput{InstanceIds: []*string{String("id-1234"), String("id-2345")}}).
174				ExpectCalls("RebootInstances").Run(t)
175		})
176	})
177
178	t.Run("check", func(t *testing.T) {
179		Template("check instance id=id-1234 state=running timeout=1").Mock(&ec2Mock{
180			DescribeInstancesFunc: func(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
181				return &ec2.DescribeInstancesOutput{Reservations: []*ec2.Reservation{
182					{Instances: []*ec2.Instance{{InstanceId: input.InstanceIds[0], State: &ec2.InstanceState{Name: String("running")}}}},
183				}}, nil
184			}}).ExpectInput("DescribeInstances", &ec2.DescribeInstancesInput{InstanceIds: []*string{String("id-1234")}}).
185			ExpectCalls("DescribeInstances").Run(t)
186	})
187
188	t.Run("attach", func(t *testing.T) {
189		Template("attach instance id=id-1234 targetgroup=arn:of:target:group port=8080").Mock(&elbv2Mock{
190			RegisterTargetsFunc: func(param0 *elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) {
191				return nil, nil
192			},
193		}).ExpectInput("RegisterTargets", &elbv2.RegisterTargetsInput{
194			TargetGroupArn: String("arn:of:target:group"),
195			Targets: []*elbv2.TargetDescription{
196				{Id: String("id-1234"), Port: Int64(8080)},
197			},
198		}).
199			ExpectCalls("RegisterTargets").Run(t)
200	})
201
202	t.Run("detach", func(t *testing.T) {
203		Template("detach instance id=id-1234 targetgroup=arn:of:target:group").Mock(&elbv2Mock{
204			DeregisterTargetsFunc: func(param0 *elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) {
205				return nil, nil
206			},
207		}).ExpectInput("DeregisterTargets", &elbv2.DeregisterTargetsInput{
208			TargetGroupArn: String("arn:of:target:group"),
209			Targets: []*elbv2.TargetDescription{
210				{Id: String("id-1234")},
211			},
212		}).
213			ExpectCalls("DeregisterTargets").Run(t)
214	})
215}
216
217func generateTmpFile(content string) (*os.File, string, func()) {
218	file, err := ioutil.TempFile("", "awless-at-tmpfile")
219	if err != nil {
220		panic(err)
221	}
222	if err := ioutil.WriteFile(file.Name(), []byte(content), 0644); err != nil {
223		panic(err)
224	}
225
226	cleanup := func() {
227		file.Close()
228		if err := os.Remove(file.Name()); err != nil {
229			panic(err)
230		}
231	}
232	return file, file.Name(), cleanup
233}
234