// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package userdata import ( "bytes" "io/ioutil" "mime/multipart" "os" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" "github.com/stretchr/testify/assert" ) const ( testClusterName = "cluster" testBoundary = "========multipart-boundary==" ) func newBuilderInTest(buf *bytes.Buffer, writer *multipart.Writer, tags []*ecs.Tag) *Builder { builder := &Builder{ writer: writer, clusterName: testClusterName, userdata: buf, tags: tags, } return builder } var existingMultipartArchive = `Content-Type: multipart/mixed; boundary="b19714718818a0e648a502570ed78486f7358d7d3a4d42c3716e81102b56" MIME-Version: 1.0 --b19714718818a0e648a502570ed78486f7358d7d3a4d42c3716e81102b56 Content-Type: text/text/plain; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo "Clyde spends the next year lonely and confused" >> $HOME/chapter3 echo "He knows Pudding wasn't right for him, but he doesn't know how to move on" >> $HOME/chapter3 echo "Meanwhile, Pudding and Tum Tum have the honeymoon of a lifetime" >> $HOME/chapter3 echo "They spend 3 months travelling the world, visiting DogseyLand, the Temple of Doge in Japan, and much more" >> $HOME/chapter3 echo "Tum Tum experiments with 10 different haircuts/stylings" >> $HOME/chapter3 --b19714718818a0e648a502570ed78486f7358d7d3a4d42c3716e81102b56 Content-Type: text/text/x-shellscript; charset="utf-8" Mime-Version: 1.0 #!/bin/bash aws s3 cp s3://my-scripts/setupenv $HOME/setupenv source $HOME/setupenv --b19714718818a0e648a502570ed78486f7358d7d3a4d42c3716e81102b56--` var extraUserDataShellScript = `#!/bin/bash echo "Quickly, the honeymoon bliss wears off" >> $HOME/chapter4 echo "The once happy couple, now fights over small things, like who gets to chew which toy" >> $HOME/chapter4 echo "Pudding drags her bed to away from Tum Tum's, and hides a pile of toys underneath it" >> $HOME/chapter4` var extraUserDataCloudConfig = `#cloud-config # Install additional packages on first boot packages: - pwgen - pastebinit - [libpython2.7, 2.7.3-0ubuntu3.1] - gpg` var expectedMimeMultipart = `Content-Type: multipart/mixed; boundary="========multipart-boundary==" MIME-Version: 1.0 --========multipart-boundary== Content-Type: text/text/plain; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo "Clyde spends the next year lonely and confused" >> $HOME/chapter3 echo "He knows Pudding wasn't right for him, but he doesn't know how to move on" >> $HOME/chapter3 echo "Meanwhile, Pudding and Tum Tum have the honeymoon of a lifetime" >> $HOME/chapter3 echo "They spend 3 months travelling the world, visiting DogseyLand, the Temple of Doge in Japan, and much more" >> $HOME/chapter3 echo "Tum Tum experiments with 10 different haircuts/stylings" >> $HOME/chapter3 --========multipart-boundary== Content-Type: text/text/x-shellscript; charset="utf-8" Mime-Version: 1.0 #!/bin/bash aws s3 cp s3://my-scripts/setupenv $HOME/setupenv source $HOME/setupenv --========multipart-boundary== Content-Type: text/text/plain; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo "Quickly, the honeymoon bliss wears off" >> $HOME/chapter4 echo "The once happy couple, now fights over small things, like who gets to chew which toy" >> $HOME/chapter4 echo "Pudding drags her bed to away from Tum Tum's, and hides a pile of toys underneath it" >> $HOME/chapter4 --========multipart-boundary== Content-Type: text/text/plain; charset="utf-8" Mime-Version: 1.0 #cloud-config # Install additional packages on first boot packages: - pwgen - pastebinit - [libpython2.7, 2.7.3-0ubuntu3.1] - gpg --========multipart-boundary== Content-Type: text/text/x-shellscript; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo ECS_CLUSTER=cluster >> /etc/ecs/ecs.config --========multipart-boundary==-- ` func TestBuildUserDataWithExtraData(t *testing.T) { multipartFilePath := writeTempFile(t, "existingMultipartArchive", existingMultipartArchive) defer os.Remove(multipartFilePath) shellScriptFilePath := writeTempFile(t, "extraUserDataShellScript", extraUserDataShellScript) defer os.Remove(shellScriptFilePath) cloudConfigFilePath := writeTempFile(t, "extraUserDataCloudConfig", extraUserDataCloudConfig) defer os.Remove(cloudConfigFilePath) buf := new(bytes.Buffer) writer := multipart.NewWriter(buf) // set the boundary between parts so that output is deterministic writer.SetBoundary(testBoundary) builder := newBuilderInTest(buf, writer, nil) err := builder.AddFile(multipartFilePath) assert.NoError(t, err, "Unexpected error calling AddFile()") err = builder.AddFile(shellScriptFilePath) assert.NoError(t, err, "Unexpected error calling AddFile()") err = builder.AddFile(cloudConfigFilePath) assert.NoError(t, err, "Unexpected error calling AddFile()") actual, err := builder.Build() assert.NoError(t, err, "Unexpected error calling Build()") expected := unixifyLineEndings(expectedMimeMultipart) assert.Equal(t, expected, actual, "Expected resulting mime multipart archive to match") } func TestBuildUserDataNoExtraData(t *testing.T) { var expectedUserData = `Content-Type: multipart/mixed; boundary="========multipart-boundary==" MIME-Version: 1.0 --========multipart-boundary== Content-Type: text/text/x-shellscript; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo ECS_CLUSTER=cluster >> /etc/ecs/ecs.config --========multipart-boundary==-- ` buf := new(bytes.Buffer) writer := multipart.NewWriter(buf) // set the boundary between parts so that output is deterministic writer.SetBoundary(testBoundary) builder := newBuilderInTest(buf, writer, nil) actual, err := builder.Build() assert.NoError(t, err, "Unexpected error calling Build()") expected := unixifyLineEndings(expectedUserData) assert.Equal(t, expected, actual, "Expected resulting mime multipart archive to match") } func TestBuildUserDataNoExtraDataWithTaggingEnabled(t *testing.T) { var expectedUserData = `Content-Type: multipart/mixed; boundary="========multipart-boundary==" MIME-Version: 1.0 --========multipart-boundary== Content-Type: text/text/x-shellscript; charset="utf-8" Mime-Version: 1.0 #!/bin/bash echo ECS_CLUSTER=cluster >> /etc/ecs/ecs.config echo 'ECS_CONTAINER_INSTANCE_TAGS={"mitchell":"webb"}' >> /etc/ecs/ecs.config --========multipart-boundary==-- ` tags := []*ecs.Tag{ &ecs.Tag{ Key: aws.String("mitchell"), Value: aws.String("webb"), }, } buf := new(bytes.Buffer) writer := multipart.NewWriter(buf) // set the boundary between parts so that output is deterministic writer.SetBoundary(testBoundary) builder := newBuilderInTest(buf, writer, tags) actual, err := builder.Build() assert.NoError(t, err, "Unexpected error calling Build()") expected := unixifyLineEndings(expectedUserData) assert.Equal(t, expected, actual, "Expected resulting mime multipart archive to match") } func writeTempFile(t *testing.T, name, content string) string { tmpfile, err := ioutil.TempFile("", name) assert.NoError(t, err, "Could not create tempfile") _, err = tmpfile.Write([]byte(content)) assert.NoError(t, err, "Could not write data to ecs-params tempfile") err = tmpfile.Close() assert.NoError(t, err, "Could not close tempfile") return tmpfile.Name() }