Skip to content

Commit

Permalink
feat(file) implement env var substitution for state files
Browse files Browse the repository at this point in the history
Introducing support for envirment variable substitution in state files.
This is primarily intended for injecting sensitive data at runtime.

Fix #191
  • Loading branch information
hbagdi committed Apr 13, 2021
1 parent 0658867 commit 9ec30d3
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 7 deletions.
44 changes: 38 additions & 6 deletions file/readfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package file

import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"

ghodss "github.com/ghodss/yaml"
"github.com/imdario/mergo"
Expand Down Expand Up @@ -80,22 +83,26 @@ func getReaders(fileOrDir string) ([]io.Reader, error) {
// readContent reads all the byes until io.EOF and unmarshals the read
// bytes into Content.
func readContent(reader io.Reader) (*Content, error) {
var content Content
var bytes []byte
var err error
bytes, err = ioutil.ReadAll(reader)
contentBytes, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
err = validate(bytes)
renderedContent, err := renderTemplate(string(contentBytes))
if err != nil {
return nil, fmt.Errorf("parsing file: %w", err)
}
renderedContentBytes := []byte(renderedContent)
err = validate(renderedContentBytes)
if err != nil {
return nil, errors.Wrap(err, "validating file content")
}
err = yamlUnmarshal(bytes, &content)
var result Content
err = yamlUnmarshal(renderedContentBytes, &result)
if err != nil {
return nil, err
}
return &content, nil
return &result, nil
}

// yamlUnmarshal is a wrapper around yaml.Unmarshal to ensure that the right
Expand Down Expand Up @@ -132,3 +139,28 @@ func configFilesInDir(dir string) ([]string, error) {
}
return res, nil
}

func getPrefixedEnvVar(key string) (string, error) {
const envVarPrefix = "DECK_"
if !strings.HasPrefix(key, envVarPrefix) {
return "", fmt.Errorf("environment variables in the state file must "+
"be prefixed with 'DECK_', found: '%s'", key)
}
return os.Getenv(key), nil
}

func renderTemplate(content string) (string, error) {
t := template.New("state").Funcs(template.FuncMap{
"env": getPrefixedEnvVar,
}).Delims("${{", "}}")
t, err := t.Parse(content)
if err != nil {
return "", err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, nil)
if err != nil {
return "", err
}
return buffer.String(), nil
}
20 changes: 20 additions & 0 deletions file/readfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func Test_getContent(t *testing.T) {
tests := []struct {
name string
args args
envVars map[string]string
want *Content
wantErr bool
}{
Expand Down Expand Up @@ -167,12 +168,16 @@ func Test_getContent(t *testing.T) {
{
name: "single file",
args: args{[]string{"testdata/file.yaml"}},
envVars: map[string]string{
"DECK_SVC2_HOST": "2.example.com",
},
want: &Content{
Services: []FService{
{
Service: kong.Service{
Name: kong.String("svc2"),
Host: kong.String("2.example.com"),
Tags: kong.StringSlice("<"),
},
Routes: []*FRoute{
{
Expand All @@ -194,15 +199,24 @@ func Test_getContent(t *testing.T) {
},
wantErr: false,
},
{
name: "file with bad environment variable",
args: args{[]string{"testdata/bad-env-var/file.yaml"}},
wantErr: true,
},
{
name: "multiple files",
args: args{[]string{"testdata/file.yaml", "testdata/file.json"}},
envVars: map[string]string{
"DECK_SVC2_HOST": "2.example.com",
},
want: &Content{
Services: []FService{
{
Service: kong.Service{
Name: kong.String("svc2"),
Host: kong.String("2.example.com"),
Tags: kong.StringSlice("<"),
},
Routes: []*FRoute{
{
Expand Down Expand Up @@ -304,6 +318,12 @@ func Test_getContent(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.envVars {
os.Setenv(k, v)
defer func(k string) {
os.Unsetenv(k)
}(k)
}
got, err := getContent(tt.args.filenames)
if (err != nil) != tt.wantErr {
t.Errorf("getContent() error = %v, wantErr %v", err, tt.wantErr)
Expand Down
9 changes: 9 additions & 0 deletions file/testdata/bad-env-var/file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
- name: svc2
host: ${{ env "SVC2_HOST" }}
routes:
- name: r2
paths:
- /r2
plugins:
- name: prometheus
4 changes: 3 additions & 1 deletion file/testdata/file.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
services:
- name: svc2
host: 2.example.com
host: ${{ env "DECK_SVC2_HOST" }}
routes:
- name: r2
paths:
- /r2
tags:
- '<'
plugins:
- name: prometheus

0 comments on commit 9ec30d3

Please sign in to comment.