Skip to content

Commit 2ef6f97

Browse files
authored
Merge pull request #42 from brycenichols/saved-vars
New -u and -x flags for sending vars in and out via base64 blobs
2 parents ff5d1ee + 225d79a commit 2ef6f97

File tree

5 files changed

+190
-4
lines changed

5 files changed

+190
-4
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,33 @@ list-buckets: creds.yml
8383
buildenv -e stage -f $< -r "aws s3 ls"
8484
```
8585

86-
*A Note About Vault:* If you have `secrets` or `kv_secrets` defined in either the global or environment scope, it's a mapping from environment variable to the path & key in vault. Buildenv uses all the standard vault environment variables to communicate with vault (`VAULT_ADDR` and `VAULT_TOKEN` being the two you're most likely to use.) You can find the complete list [in the vault client docs](https://pkg.go.dev/github.com/hashicorp/[email protected]#WithEnvironment).
86+
If it's necessary to merge or save a set of variables (for example, so that vault does not need to be called repeatedly), the -u option allows for saving and using a set of variables from the environment without writing possibly sensitive data out to a file:
87+
88+
```bash
89+
% export SAVED_ENV=`echo '{"example_var": "the value"}' | base64`
90+
% buildenv -u SAVED_ENV -f /dev/null
91+
export example_var="the value"
92+
```
93+
94+
This takes a base64 encoded json object with key-value pairs and treats them as additional input variables. The corresponding flag for export in the same format is -x:
95+
96+
```bash
97+
% buildenv -u SAVED_ENV -f /dev/null -x | base64 -d
98+
{"example_var":"the value"}
99+
```
100+
101+
Multiple -u options can be used as well as combined with -f to combine multiple sources. Given the above variables.yml:
87102

103+
```bash
104+
% export SAVED_ENV=`echo '{"example_var": "the value"}' | base64`
105+
% export SAVED_ENV2=`echo '{"another_var": "another value"}' | base64`
106+
% buildenv -u SAVED_ENV -u SAVED_ENV2 -v
107+
export GLOBAL="global"
108+
export example_var="the value"
109+
export another_var="another value"
110+
```
111+
112+
*A Note About Vault:* If you have `secrets` or `kv_secrets` defined in either the global or environment scope, it's a mapping from environment variable to the path & key in vault. Buildenv uses all the standard vault environment variables to communicate with vault (`VAULT_ADDR` and `VAULT_TOKEN` being the two you're most likely to use.) You can find the complete list [in the vault client docs](https://pkg.go.dev/github.com/hashicorp/[email protected]#WithEnvironment).
88113

89114
Running on Linux or in Docker container
90115
----------

cmd/root.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package cmd
55

66
import (
77
"context"
8+
"encoding/base64"
89
"encoding/json"
910
"fmt"
1011
"os"
@@ -24,6 +25,10 @@ const (
2425
ErrorCodeYaml = 5
2526
// ErrorCodeVault Exit Code for Vault Errors
2627
ErrorCodeVault = 6
28+
// ErrorCodeInput Exit Code for Bad Input
29+
ErrorCodeInput = 7
30+
// ErrorCodeOutput Exit Code for Failed Serialization/Output
31+
ErrorCodeOutput = 8
2732
)
2833

2934
var cfgFile string
@@ -78,7 +83,7 @@ Values can be specified in plain text, or set from a vault server.`,
7883
skip_vault, _ := cmd.Flags().GetBool("skip-vault")
7984

8085
// Setup the Reader
81-
reader, err := reader.NewReader(reader.WithSkipVault(skip_vault))
86+
rdr, err := reader.NewReader(reader.WithSkipVault(skip_vault))
8287
if err != nil {
8388
fmt.Printf("Failure creating Reader: %v", err)
8489
os.Exit(ErrorCodeVault)
@@ -89,7 +94,7 @@ Values can be specified in plain text, or set from a vault server.`,
8994
run, _ := cmd.Flags().GetString("run")
9095
dc, _ := cmd.Flags().GetString("datacenter")
9196

92-
out, err := reader.Read(ctx, &data, env, dc)
97+
out, err := rdr.Read(ctx, &data, env, dc)
9398
if err != nil {
9499
fmt.Printf("Failure reading data: %v", err)
95100
os.Exit(ErrorCodeVault)
@@ -100,12 +105,54 @@ Values can be specified in plain text, or set from a vault server.`,
100105
fmt.Printf("Output:\n%s\n\n", outData)
101106
}
102107

108+
var use_vars reader.EnvVars
109+
110+
use, err := cmd.Flags().GetStringArray("use")
111+
if err != nil {
112+
fmt.Printf("Could not get \"use\" (-u) flag values: %v", err)
113+
os.Exit(ErrorCodeInput)
114+
}
115+
116+
for _, use_inst := range use {
117+
blob := os.Getenv(use_inst)
118+
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(blob)))
119+
len, err := base64.StdEncoding.Decode(decoded, []byte(blob))
120+
if err != nil {
121+
fmt.Printf("Could not decode input to flag \"use\" (-u): %v", err)
122+
os.Exit(ErrorCodeInput)
123+
}
124+
decoded = decoded[:len]
125+
/* It adds to the structure, merging matching keys */
126+
err = json.Unmarshal([]byte(decoded), &use_vars)
127+
if err != nil {
128+
fmt.Printf("Could not decode input to flag \"use\" (-u): %v", err)
129+
os.Exit(ErrorCodeInput)
130+
}
131+
}
132+
133+
vars_out := use_vars.GetOutput()
134+
out = append(out, vars_out...)
135+
103136
// Output the Exports
104137
comments, _ := cmd.Flags().GetBool("comments")
105138
if cmd.Flags().Lookup("run").Changed {
106139
os.Exit(out.Exec(run))
107140
} else {
108-
out.Print(comments)
141+
encoded_export, err := cmd.Flags().GetBool("export")
142+
if err != nil {
143+
fmt.Printf("Failure reading export flag: %v", err)
144+
os.Exit(ErrorCodeInput)
145+
}
146+
147+
if encoded_export {
148+
err = out.PrintB64Json()
149+
if err != nil {
150+
fmt.Printf("Failure printing output: %v", err)
151+
os.Exit(ErrorCodeOutput)
152+
}
153+
} else {
154+
out.Print(comments)
155+
}
109156
}
110157
},
111158
}
@@ -141,6 +188,8 @@ func init() {
141188
rootCmd.Flags().BoolP("comments", "c", false, "Comments will be included in output")
142189
rootCmd.Flags().Bool("debug", false, "Turn on debugging output")
143190
rootCmd.Flags().Bool("version", false, "Print the version number")
191+
rootCmd.Flags().StringArrayP("use", "u", []string{}, "Use Stored Vars from named environment variable. Contents should be base64 encoded JSON.")
192+
rootCmd.Flags().BoolP("export", "x", false, "Print Vars as base64 encoded json")
144193
}
145194

146195
// initConfig reads in config file and ENV variables if set.

cram_tests/codec.t

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Setup
2+
3+
$ . "$TESTDIR"/setup.sh
4+
5+
Make env vars
6+
7+
$ export INP=$(printf '{"VAR1": "VAL1", "VAR2": "VAL2"}' | base64)
8+
$ be -v -f /dev/null -u INP -x > codec_test.blob
9+
$ export BLOB=`cat codec_test.blob`
10+
$ echo "$BLOB"
11+
eyJWQVIxIjoiVkFMMSIsIlZBUjIiOiJWQUwyIn0=
12+
$ be -v -f /dev/null -u BLOB
13+
export VAR1="VAL1"
14+
export VAR2="VAL2"
15+

reader/reader.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package reader
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"encoding/json"
57
"errors"
68
"fmt"
79
"net/http"
@@ -268,6 +270,26 @@ func (o OutputList) Exec(shell_cmd string) int {
268270
return 0
269271
}
270272

273+
func (ol OutputList) PrintB64Json() error {
274+
envs := EnvVars{}
275+
276+
for _, o := range ol {
277+
if o.Key != "" {
278+
envs[o.Key] = o.Value
279+
}
280+
}
281+
282+
serialized, err := json.Marshal(envs)
283+
if err != nil {
284+
return err
285+
}
286+
encoded := base64.StdEncoding.EncodeToString(serialized)
287+
288+
fmt.Print(encoded)
289+
290+
return nil
291+
}
292+
271293
func (o OutputList) Print(showComments bool) {
272294
for _, out := range o {
273295
if out.Key == "" {

reader/reader_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package reader
33
import (
44
"bytes"
55
"context"
6+
"encoding/base64"
7+
"encoding/json"
68
"io"
79
"log"
810
"net/http"
@@ -727,3 +729,76 @@ func TestOutputList_Exec(t *testing.T) {
727729
})
728730
}
729731
}
732+
733+
func TestOutputList_PrintB64Json(t *testing.T) {
734+
envVars := EnvVars{
735+
"BuildEnvTestKey1": "BuildEnvTestVal1",
736+
"BuildEnvTestKey2": "BuildEnvTestVal2",
737+
}
738+
739+
type fields struct {
740+
Out OutputList
741+
}
742+
743+
tests := []struct {
744+
name string
745+
fields fields
746+
want EnvVars
747+
wantErr bool
748+
}{
749+
{
750+
name: "Basic 2 vars",
751+
fields: fields{Out: envVars.GetOutput()},
752+
want: envVars,
753+
},
754+
}
755+
756+
for _, tt := range tests {
757+
t.Run(tt.name, func(t *testing.T) {
758+
oldstdout := os.Stdout
759+
r, w, _ := os.Pipe()
760+
os.Stdout = w
761+
762+
outC := make(chan string)
763+
764+
go func() {
765+
var buf bytes.Buffer
766+
io.Copy(&buf, r)
767+
outC <- buf.String()
768+
}()
769+
770+
err := tt.fields.Out.PrintB64Json()
771+
if err != nil {
772+
t.Errorf("PrintB64Json() error = %v", err)
773+
}
774+
775+
os.Stdout = oldstdout
776+
777+
w.Close()
778+
779+
out := <-outC
780+
781+
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(out)))
782+
len, err := base64.StdEncoding.Decode(decoded, []byte(out))
783+
784+
var out_vars EnvVars
785+
786+
if err != nil {
787+
t.Errorf("Exception parsing output: %v", err)
788+
return
789+
}
790+
791+
decoded = decoded[:len]
792+
793+
err = json.Unmarshal([]byte(decoded), &out_vars)
794+
if err != nil {
795+
t.Errorf("Exception parsing output: %v", err)
796+
return
797+
}
798+
799+
if !reflect.DeepEqual(out_vars, tt.want) {
800+
t.Errorf("PrintB64Json() output = %v, want %v", out_vars, tt.want)
801+
}
802+
})
803+
}
804+
}

0 commit comments

Comments
 (0)