Skip to content

Commit 5b836ba

Browse files
authored
feat: add env subst command [CPE-1773] (#910)
* feat: add env subst command * chore: lint
1 parent 66c2bfe commit 5b836ba

File tree

6 files changed

+149
-1
lines changed

6 files changed

+149
-1
lines changed

cmd/env.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/a8m/envsubst"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func newEnvCmd() *cobra.Command {
12+
var envCmd = &cobra.Command{
13+
Use: "env",
14+
Short: "Manage environment variables",
15+
}
16+
var substCmd = &cobra.Command{
17+
Use: "subst",
18+
Short: "Substitute environment variables in a string",
19+
RunE: substRunE,
20+
}
21+
envCmd.AddCommand(substCmd)
22+
return envCmd
23+
}
24+
25+
// Accepts a string as an argument, or reads from stdin if no argument is provided.
26+
func substRunE(cmd *cobra.Command, args []string) error {
27+
var input string
28+
if len(args) > 0 {
29+
input = args[0]
30+
} else {
31+
// Read from stdin
32+
b, err := io.ReadAll(cmd.InOrStdin())
33+
if err != nil {
34+
return err
35+
}
36+
input = string(b)
37+
}
38+
if input == "" {
39+
return nil
40+
}
41+
output, err := envsubst.String(input)
42+
if err != nil {
43+
return err
44+
}
45+
_, err = fmt.Fprint(cmd.OutOrStdout(), output)
46+
return err
47+
}

cmd/env_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"testing"
7+
8+
"gotest.tools/v3/assert"
9+
)
10+
11+
func TestSubstRunE(t *testing.T) {
12+
// Set environment variables for testing
13+
err := os.Setenv("ENV_NAME", "world")
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
testCases := []struct {
19+
name string
20+
input string
21+
output string
22+
}{
23+
{
24+
name: "substitute variables",
25+
input: "Hello $ENV_NAME!",
26+
output: "Hello world!",
27+
},
28+
{
29+
name: "no variables to substitute",
30+
input: "Hello, world!",
31+
output: "Hello, world!",
32+
},
33+
{
34+
name: "empty input",
35+
input: "",
36+
output: "",
37+
},
38+
{
39+
name: "no variables JSON",
40+
input: `{"foo": "bar"}`,
41+
output: `{"foo": "bar"}`,
42+
},
43+
{
44+
name: "substitute variables JSON",
45+
input: `{"foo": "$ENV_NAME"}`,
46+
output: `{"foo": "world"}`,
47+
},
48+
{
49+
name: "no variables key=value",
50+
input: `foo=bar`,
51+
output: `foo=bar`,
52+
},
53+
}
54+
55+
// Run tests for each test case as argument
56+
for _, tc := range testCases {
57+
t.Run("arg: "+tc.name, func(t *testing.T) {
58+
// Set up test command
59+
cmd := newEnvCmd()
60+
61+
// Capture output
62+
outputBuf := bytes.Buffer{}
63+
cmd.SetOut(&outputBuf)
64+
65+
// Run command
66+
cmd.SetArgs([]string{"subst", tc.input})
67+
err := cmd.Execute()
68+
69+
// Check output and error
70+
assert.NilError(t, err)
71+
assert.Equal(t, tc.output, outputBuf.String())
72+
})
73+
}
74+
// Run tests for each test case as stdin
75+
for _, tc := range testCases {
76+
t.Run("stdin: "+tc.name, func(t *testing.T) {
77+
// Set up test command
78+
cmd := newEnvCmd()
79+
80+
// Set up input
81+
inputBuf := bytes.NewBufferString(tc.input)
82+
cmd.SetIn(inputBuf)
83+
84+
// Capture output
85+
outputBuf := bytes.Buffer{}
86+
cmd.SetOut(&outputBuf)
87+
88+
// Run command
89+
cmd.SetArgs([]string{"subst"})
90+
err = cmd.Execute()
91+
92+
// Check output and error
93+
assert.NilError(t, err)
94+
assert.Equal(t, tc.output, outputBuf.String())
95+
})
96+
}
97+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func MakeCommands() *cobra.Command {
174174
rootCmd.AddCommand(newSwitchCommand(rootOptions))
175175
rootCmd.AddCommand(newAdminCommand(rootOptions))
176176
rootCmd.AddCommand(newCompletionCommand())
177+
rootCmd.AddCommand(newEnvCmd())
177178

178179
flags := rootCmd.PersistentFlags()
179180

cmd/root_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var _ = Describe("Root", func() {
1616
Describe("subcommands", func() {
1717
It("can create commands", func() {
1818
commands := cmd.MakeCommands()
19-
Expect(len(commands.Commands())).To(Equal(23))
19+
Expect(len(commands.Commands())).To(Equal(24))
2020
})
2121
})
2222

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ require (
4141

4242
require (
4343
github.com/OneOfOne/xxhash v1.2.8 // indirect
44+
github.com/a8m/envsubst v1.4.2 // indirect
4445
github.com/agnivade/levenshtein v1.1.1 // indirect
4546
github.com/atotto/clipboard v0.1.4 // indirect
4647
github.com/charmbracelet/bubbles v0.11.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nB
4343
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
4444
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
4545
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
46+
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
47+
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
4648
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
4749
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
4850
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=

0 commit comments

Comments
 (0)