diff --git a/resources/user-data-env.yaml b/resources/user-data-env.yaml new file mode 100644 index 0000000..f1954c6 --- /dev/null +++ b/resources/user-data-env.yaml @@ -0,0 +1,9 @@ +UserData: + "Fn::Base64": !Sub | + #!/bin/bash + yum install -y aws-cfn-bootstrap + ${Local::IncludeEnv TEMP_ENV_VAL} + /opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSLaunchConfiguration + /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSAutoScalingGroup +UserData2-${Local::IncludeEnv TEMP_ENV_VAL}: + temp: !Local::IncludeEnv TEMP_ENV_VAL \ No newline at end of file diff --git a/utils/include_env.go b/utils/include_env.go new file mode 100644 index 0000000..cbf4387 --- /dev/null +++ b/utils/include_env.go @@ -0,0 +1,56 @@ +package utils + +import ( + "bufio" + "bytes" + "io" + "os" + "regexp" + + log "github.com/Sirupsen/logrus" +) + +var ( + // Matches literal string: ${Local::IncludeEnv } + // example: ${Local::IncludeEnv HOME} + literalIncludeEnvRe = regexp.MustCompile(`\${[ ]*Local::IncludeEnv[ ]+([[:ascii:]]+)}`) + // Matches tag !Local::IncludeEnv + // for example: !Local::IncludeEnv HOME + valueIncludeEnvTagRe = regexp.MustCompile(`!Local::IncludeEnv[ ]+([[:ascii:]]+)`) +) + +func ApplyIncludeEnvDirective(reader io.Reader) []byte { + output := bytes.NewBuffer([]byte{}) + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Bytes() + + loc := literalIncludeEnvRe.FindIndex(line) + tagLoc := valueIncludeEnvTagRe.FindIndex(line) + + if len(loc) == 2 { + envName := string(literalIncludeEnvRe.FindSubmatch(line)[1]) + log.Debugf("loading include env: %s", envName) + + output.Write(line[0:loc[0]]) + envValue := os.Getenv(envName) + output.WriteString(envValue) + output.Write(line[loc[1]:]) + + } else if len(tagLoc) == 2 { + envName := string(valueIncludeEnvTagRe.FindSubmatch(line)[1]) + log.Debugf("loading include env: %s", envName) + + output.Write(line[0:tagLoc[0]]) + envValue := os.Getenv(envName) + output.WriteString(envValue) + output.Write(line[tagLoc[1]:]) + + } else { + output.Write(line) + } + + output.WriteByte('\n') + } + return output.Bytes() +} diff --git a/utils/include_env_test.go b/utils/include_env_test.go new file mode 100644 index 0000000..141e653 --- /dev/null +++ b/utils/include_env_test.go @@ -0,0 +1,51 @@ +// +// Copyright 2016 Capital One Services, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License 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. +// +// SPDX-Copyright: Copyright (c) Capital One Services, LLC +// SPDX-License-Identifier: Apache-2.0 +// +package utils + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +var expectedEnvIncl = `UserData: + "Fn::Base64": !Sub | + #!/bin/bash + yum install -y aws-cfn-bootstrap + something + /opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSLaunchConfiguration + /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource ECSAutoScalingGroup +UserData2-something: + temp: something +` + +func TestApplyIncludeEnvDirective(t *testing.T) { + fname := "../resources/" + "user-data-env.yaml" + TemporaryChdir("../resources", func() { + f, err := os.Open(fname) + assert.Nil(t, err) + defer f.Close() + var result []byte + TemporaryEnv("TEMP_ENV_VAL", "something", func() { + result = ApplyIncludeEnvDirective(f) + }) + + assert.Equal(t, expectedEnvIncl, string(result)) + }) +} diff --git a/utils/util.go b/utils/util.go index a7eb853..99fbe1f 100644 --- a/utils/util.go +++ b/utils/util.go @@ -315,3 +315,19 @@ func MaxInt(x, y int) int { } return y } + +// testing util + +func TemporaryEnv(name, value string, runIt func()) { + old, ok := os.LookupEnv(name) + os.Setenv(name, value) + defer func() { + if !ok { + os.Unsetenv(name) + } else { + os.Setenv(name, old) + } + }() + + runIt() +} diff --git a/utils/util_test.go b/utils/util_test.go index 3fdce63..42e45cd 100644 --- a/utils/util_test.go +++ b/utils/util_test.go @@ -19,11 +19,13 @@ package utils import ( "io/ioutil" + "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -124,3 +126,29 @@ func TestSpecialFnIncludeLinesMissingFile(t *testing.T) { assert.Equal(t, valid, string(cftJson)) } + +func TestTemporaryEnvNew(t *testing.T) { + const key = "TEMP_ENV_1" + require.Equal(t, "", os.Getenv(key)) + TemporaryEnv(key, "asdf", func() { + require.Equal(t, "asdf", os.Getenv(key)) + }) + require.Equal(t, "", os.Getenv(key)) + v, ok := os.LookupEnv(key) + require.False(t, ok) + require.Equal(t, "", v) +} + +func TestTemporaryEnvExisting(t *testing.T) { + const key = "TEMP_ENV_2" + os.Setenv(key, "qwerty") + + require.Equal(t, "qwerty", os.Getenv(key)) + TemporaryEnv(key, "asdf", func() { + require.Equal(t, "asdf", os.Getenv(key)) + }) + require.Equal(t, "qwerty", os.Getenv(key)) + v, ok := os.LookupEnv(key) + require.True(t, ok) + require.Equal(t, "qwerty", v) +}