Skip to content

Commit c935eaf

Browse files
authored
feat: add envsubst support for fromstr command and ReplaceStringInput API method (#36)
+semver: feat +semver: feature +semver: Feature added envsubst support * fix: sonarqube and release tags fixed
1 parent 103f5d8 commit c935eaf

File tree

13 files changed

+105
-249
lines changed

13 files changed

+105
-249
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
test:
4242
runs-on: ubuntu-latest
4343
container:
44-
image: golang:1.23-bullseye
44+
image: golang:1.24-bookworm
4545
needs: set-version
4646
env:
4747
SEMVER: ${{ needs.set-version.outputs.semVer }}
@@ -70,6 +70,8 @@ jobs:
7070
- name: Run Tests
7171
run: |
7272
task coverage
73+
ls -alt .coverage/out
74+
ls -lat .coverage/report-junit.xml
7375
- name: Publish Junit style Test Report
7476
uses: mikepenz/action-junit-report@v4
7577
if: always() # always run even if the previous step fails
@@ -84,8 +86,8 @@ jobs:
8486
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
8587
with:
8688
# Additional arguments for the sonarcloud scanner
87-
args:
88-
# mandatory
89+
# mandatory
90+
args: >
8991
-Dsonar.projectVersion=${{ needs.set-version.outputs.semVer }}
90-
-Dsonar.go.coverage.reportPaths=/github/workspace/.coverage/out
91-
-Dsonar.go.tests.reportPaths=/github/workspace/.coverage/report-junit.xml
92+
-Dsonar.go.coverage.reportPaths=.coverage/out
93+
-Dsonar.go.tests.reportPaths=.coverage/report-junit.xml

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
release:
4242
runs-on: ubuntu-latest
4343
container:
44-
image: golang:1.23-bullseye
44+
image: golang:1.24-bookworm
4545
env:
4646
FOO: Bar
4747
needs: set-version
@@ -73,7 +73,7 @@ jobs:
7373
- name: Release binary
7474
uses: softprops/action-gh-release@v2
7575
with:
76-
tag_name: ${{ needs.set-version.outputs.semVer }}
76+
tag_name: v${{ needs.set-version.outputs.semVer }}
7777
# TODO: add additional info to the release
7878
generate_release_notes: true
7979
token: ${{ secrets.GITHUB_TOKEN }}

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 dnitsch
3+
Copyright (c) 2025 DevLabFoundry
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

cmd/configmanager/configmanager.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type rootCmdFlags struct {
2222
verbose bool
2323
tokenSeparator string
2424
keySeparator string
25+
enableEnvSubst bool
2526
}
2627

2728
type Root struct {
@@ -47,6 +48,7 @@ func NewRootCmd(logger log.ILogger) *Root { //channelOut, channelErr io.Writer
4748
rc.Cmd.PersistentFlags().BoolVarP(&rc.rootFlags.verbose, "verbose", "v", false, "Verbosity level")
4849
rc.Cmd.PersistentFlags().StringVarP(&rc.rootFlags.tokenSeparator, "token-separator", "s", "#", "Separator to use to mark concrete store and the key within it")
4950
rc.Cmd.PersistentFlags().StringVarP(&rc.rootFlags.keySeparator, "key-separator", "k", "|", "Separator to use to mark a key look up in a map. e.g. AWSSECRETS#/token/map|key1")
51+
rc.Cmd.PersistentFlags().BoolVarP(&rc.rootFlags.enableEnvSubst, "enable-envsubst", "e", false, "Enable envsubst on input. This will fail on any unset or empty variables")
5052
addSubCmds(rc)
5153
return rc
5254
}
@@ -70,7 +72,7 @@ func cmdutilsInit(rootCmd *Root, cmd *cobra.Command, path string) (*cmdutils.Cmd
7072
}
7173

7274
cm := configmanager.New(cmd.Context())
73-
cm.Config.WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithOutputPath(path).WithKeySeparator(rootCmd.rootFlags.keySeparator)
75+
cm.Config.WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithOutputPath(path).WithKeySeparator(rootCmd.rootFlags.keySeparator).WithEnvSubst(rootCmd.rootFlags.enableEnvSubst)
7476
gnrtr := generator.NewGenerator(cmd.Context(), func(gv *generator.GenVars) {
7577
if rootCmd.rootFlags.verbose {
7678
rootCmd.logger.SetLevel(log.DebugLvl)

cmd/configmanager/fromfileinput_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ func TestFromStr_command(t *testing.T) {
1313
t.Run("should error on missing flag", func(t *testing.T) {
1414
cmdRunTestHelper(t, &cmdTestInput{args: []string{"fromstr", "--path", "testdata/input.yml"}, errored: true})
1515
})
16+
t.Run("should error on missing var when envsubst enabled", func(t *testing.T) {
17+
cmdRunTestHelper(t, &cmdTestInput{args: []string{"fromstr", "--input", "foo AWSPARAMSTR://${NOT_FOUND}", "-s", "://", "--enable-envsubst", "--path", "stdout"}, errored: true})
18+
})
1619
}

cmd/configmanager/insert.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ func newInsertCmd(rootCmd *Root) {
2020
Long: ``,
2121
RunE: func(cmd *cobra.Command, args []string) error {
2222
return fmt.Errorf("not yet implemented")
23-
2423
},
2524
PreRunE: func(cmd *cobra.Command, args []string) error {
2625
if len(f.insertKv) < 1 {

configmanager.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package configmanager
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"regexp"
89
"slices"
910
"strings"
1011

1112
"github.com/DevLabFoundry/configmanager/internal/config"
1213
"github.com/DevLabFoundry/configmanager/pkg/generator"
14+
"github.com/a8m/envsubst"
1315
"gopkg.in/yaml.v3"
1416
)
1517

@@ -66,10 +68,24 @@ func (c *ConfigManager) retrieve(tokens []string) (generator.ParsedMap, error) {
6668
return c.generator.Generate(tokens)
6769
}
6870

71+
var ErrEnvSubst = errors.New("envsubst enabled and errored on")
72+
6973
// RetrieveWithInputReplaced parses given input against all possible token strings
7074
// using regex to grab a list of found tokens in the given string and returns the replaced string
7175
func (c *ConfigManager) RetrieveWithInputReplaced(input string) (string, error) {
72-
76+
// replaces all env vars using strict mode of no unset and no empty
77+
//
78+
// NOTE: this happens before the FindTokens is called
79+
// currently it uses a regex, and envsubst uses a more robust lexer => parser mechanism
80+
//
81+
// NOTE: configmanager needs an own lexer => parser to allow for easier modification extension in the future
82+
if c.GeneratorConfig().EnvSubstEnabled() {
83+
var err error
84+
input, err = envsubst.StringRestrictedNoDigit(input, true, true, false)
85+
if err != nil {
86+
return "", fmt.Errorf("%w\n%v", ErrEnvSubst, err)
87+
}
88+
}
7389
m, err := c.retrieve(FindTokens(input))
7490

7591
if err != nil {

configmanager_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package configmanager_test
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"reflect"
78
"sort"
89
"testing"
@@ -169,6 +170,45 @@ foo23 = val1
169170
}
170171
}
171172

173+
func Test_replaceString_with_envsubst(t *testing.T) {
174+
t.Parallel()
175+
ttests := map[string]struct {
176+
expect string
177+
setup func() func()
178+
input string
179+
genvar *mockGenerator
180+
}{
181+
"replaced successfully": {
182+
input: `{"patchPayloadTemplate":"{"password":"FOO#/${BAR}","passwordConfirm":"FOO#/${BAZ:-test}"}}`,
183+
expect: `{"patchPayloadTemplate":"{"password":"val1","passwordConfirm":"val1"}}`,
184+
genvar: &mockGenerator{},
185+
setup: func() func() {
186+
os.Setenv("BAR", "test")
187+
return func() {
188+
os.Unsetenv("BAR")
189+
}
190+
},
191+
},
192+
}
193+
for name, tt := range ttests {
194+
t.Run(name, func(t *testing.T) {
195+
tearDown := tt.setup()
196+
defer tearDown()
197+
198+
cm := configmanager.New(context.TODO())
199+
cm.WithGenerator(tt.genvar)
200+
cm.Config.WithEnvSubst(true)
201+
got, err := cm.RetrieveWithInputReplaced(tt.input)
202+
if err != nil {
203+
t.Errorf("failed with %v", err)
204+
}
205+
if got != tt.expect {
206+
t.Errorf(testutils.TestPhrase, got, tt.expect)
207+
}
208+
})
209+
}
210+
}
211+
172212
type testSimpleStruct struct {
173213
Foo string `json:"foo" yaml:"foo"`
174214
Bar string `json:"bar" yaml:"bar"`
@@ -583,7 +623,7 @@ func Test_Generator_Config_(t *testing.T) {
583623
}
584624
got := cm.GeneratorConfig()
585625
if diff := deep.Equal(got, &tt.expect); diff != nil {
586-
t.Errorf(testutils.TestPhraseWithContext, "generator config", fmt.Sprintf("%q", got), fmt.Sprintf("%q", tt.expect))
626+
t.Errorf(testutils.TestPhraseWithContext, "generator config", fmt.Sprintf("%v", got), fmt.Sprintf("%v", tt.expect))
587627
}
588628
})
589629
}

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module github.com/DevLabFoundry/configmanager
22

3-
go 1.23.2
3+
go 1.24
4+
5+
toolchain go1.24.2
46

57
require (
68
cloud.google.com/go/secretmanager v1.14.6
@@ -30,6 +32,7 @@ require (
3032
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
3133
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
3234
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
35+
github.com/a8m/envsubst v1.4.3
3336
github.com/aws/aws-sdk-go v1.55.6 // indirect
3437
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect
3538
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
@@ -49,7 +52,6 @@ require (
4952
github.com/go-logr/logr v1.4.2 // indirect
5053
github.com/go-logr/stdr v1.2.2 // indirect
5154
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
52-
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
5355
github.com/google/s2a-go v0.1.9 // indirect
5456
github.com/google/uuid v1.6.0 // indirect
5557
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@@ -76,7 +78,6 @@ require (
7678
github.com/pkg/errors v0.9.1 // indirect
7779
github.com/ryanuber/go-glob v1.0.0 // indirect
7880
github.com/spf13/pflag v1.0.5 // indirect
79-
go.opencensus.io v0.24.0 // indirect
8081
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
8182
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
8283
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect

0 commit comments

Comments
 (0)