Skip to content

Commit 92f70c1

Browse files
mcalhouncoderabbitai[bot]autofix-ci[bot]
authored
allow user to specify default value when using store (cloudposse#1020)
* allow user to specify default value when using store * Update internal/exec/yaml_func_store_test.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * allow user to specify default value when using store --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 02859b2 commit 92f70c1

File tree

3 files changed

+157
-38
lines changed

3 files changed

+157
-38
lines changed

internal/exec/yaml_func_store.go

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,11 @@ import (
1111
)
1212

1313
type params struct {
14-
storeName string
15-
stack string
16-
component string
17-
key string
18-
}
19-
20-
func getParams(input string, currentStack string) (params, error) {
21-
parts := strings.Split(input, " ")
22-
23-
partsLength := len(parts)
24-
if partsLength != 3 && partsLength != 4 {
25-
return params{}, fmt.Errorf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
26-
}
27-
28-
retParams := params{storeName: strings.TrimSpace(parts[0])}
29-
30-
if partsLength == 4 {
31-
retParams.stack = strings.TrimSpace(parts[1])
32-
retParams.component = strings.TrimSpace(parts[2])
33-
retParams.key = strings.TrimSpace(parts[3])
34-
} else if partsLength == 3 {
35-
retParams.stack = currentStack
36-
retParams.component = strings.TrimSpace(parts[1])
37-
retParams.key = strings.TrimSpace(parts[2])
38-
} else {
39-
return params{}, fmt.Errorf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
40-
}
41-
42-
return retParams, nil
14+
storeName string
15+
stack string
16+
component string
17+
key string
18+
defaultValue *string
4319
}
4420

4521
func processTagStore(atmosConfig schema.AtmosConfiguration, input string, currentStack string) any {
@@ -50,20 +26,58 @@ func processTagStore(atmosConfig schema.AtmosConfiguration, input string, curren
5026
u.LogErrorAndExit(err)
5127
}
5228

53-
params, err := getParams(str, currentStack)
54-
if err != nil {
55-
u.LogErrorAndExit(err)
29+
// Split the input on the pipe symbol to separate the store parameters and default value
30+
parts := strings.Split(str, "|")
31+
storePart := strings.TrimSpace(parts[0])
32+
33+
var defaultValue *string
34+
if len(parts) > 1 {
35+
// Expecting the format: default <value>
36+
defaultParts := strings.Fields(strings.TrimSpace(parts[1]))
37+
if len(defaultParts) != 2 || defaultParts[0] != "default" {
38+
log.Error(fmt.Sprintf("invalid default value format in: %s", str))
39+
return fmt.Sprintf("invalid default value format in: %s", str)
40+
}
41+
val := strings.Trim(defaultParts[1], `"'`) // Remove surrounding quotes if present
42+
defaultValue = &val
43+
}
44+
45+
// Process the main store part
46+
storeParts := strings.Fields(storePart)
47+
partsLength := len(storeParts)
48+
if partsLength != 3 && partsLength != 4 {
49+
return fmt.Sprintf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
50+
}
51+
52+
retParams := params{
53+
storeName: strings.TrimSpace(storeParts[0]),
54+
defaultValue: defaultValue,
55+
}
56+
57+
if partsLength == 4 {
58+
retParams.stack = strings.TrimSpace(storeParts[1])
59+
retParams.component = strings.TrimSpace(storeParts[2])
60+
retParams.key = strings.TrimSpace(storeParts[3])
61+
} else if partsLength == 3 {
62+
retParams.stack = currentStack
63+
retParams.component = strings.TrimSpace(storeParts[1])
64+
retParams.key = strings.TrimSpace(storeParts[2])
5665
}
5766

58-
store := atmosConfig.Stores[params.storeName]
67+
// Retrieve the store from atmosConfig
68+
store := atmosConfig.Stores[retParams.storeName]
5969

6070
if store == nil {
61-
u.LogErrorAndExit(fmt.Errorf("invalid Atmos Store YAML function execution:: %s\nstore '%s' not found", input, params.storeName))
71+
u.LogErrorAndExit(fmt.Errorf("invalid Atmos Store YAML function execution:: %s\nstore '%s' not found", input, retParams.storeName))
6272
}
6373

64-
value, err := store.Get(params.stack, params.component, params.key)
74+
// Retrieve the value from the store
75+
value, err := store.Get(retParams.stack, retParams.component, retParams.key)
6576
if err != nil {
66-
u.LogErrorAndExit(fmt.Errorf("an error occurred while looking up key %s in stack %s and component %s from store %s\n%v", params.key, params.stack, params.component, params.storeName, err))
77+
if retParams.defaultValue != nil {
78+
return *retParams.defaultValue
79+
}
80+
u.LogErrorAndExit(fmt.Errorf("failed to get key: %s", err))
6781
}
6882

6983
return value

internal/exec/yaml_func_store_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package exec
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/alicebob/miniredis/v2"
9+
"github.com/cloudposse/atmos/pkg/schema"
10+
"github.com/cloudposse/atmos/pkg/store"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestProcessTagStore(t *testing.T) {
16+
// Start a new Redis server
17+
s := miniredis.RunT(t)
18+
defer s.Close()
19+
20+
// Setup the Redis ENV variable
21+
redisUrl := fmt.Sprintf("redis://%s", s.Addr())
22+
origRedisUrl := os.Getenv("ATMOS_REDIS_URL")
23+
os.Setenv("ATMOS_REDIS_URL", redisUrl)
24+
defer os.Setenv("ATMOS_REDIS_URL", origRedisUrl)
25+
26+
// Create a new Redis store
27+
redisStore, err := store.NewRedisStore(store.RedisStoreOptions{
28+
URL: &redisUrl,
29+
})
30+
assert.NoError(t, err)
31+
32+
// Setup test configuration
33+
atmosConfig := schema.AtmosConfiguration{
34+
Stores: map[string]store.Store{
35+
"redis": redisStore,
36+
},
37+
}
38+
39+
// Populate the store with some data
40+
require.NoError(t, redisStore.Set("dev", "vpc", "cidr", "10.0.0.0/16"))
41+
require.NoError(t, redisStore.Set("prod", "vpc", "cidr", "172.16.0.0/16"))
42+
43+
tests := []struct {
44+
name string
45+
input string
46+
currentStack string
47+
expected interface{}
48+
}{
49+
{
50+
name: "lookup using current stack",
51+
input: "!store redis vpc cidr",
52+
currentStack: "dev",
53+
expected: "10.0.0.0/16",
54+
},
55+
{
56+
name: "basic lookup cross-stack",
57+
input: "!store redis dev vpc cidr",
58+
currentStack: "prod",
59+
expected: "10.0.0.0/16",
60+
},
61+
{
62+
name: "lookup with default value without quotes",
63+
input: "!store redis staging vpc cidr | default 172.20.0.0/16",
64+
currentStack: "dev",
65+
expected: "172.20.0.0/16",
66+
},
67+
{
68+
name: "lookup with default value with single quotes",
69+
input: "!store redis staging vpc cidr | default '172.20.0.0/16'",
70+
currentStack: "dev",
71+
expected: "172.20.0.0/16",
72+
},
73+
{
74+
name: "lookup with default value with double quotes",
75+
input: "!store redis staging vpc cidr | default \"172.20.0.0/16\"",
76+
currentStack: "dev",
77+
expected: "172.20.0.0/16",
78+
},
79+
{
80+
name: "lookup with invalid default format",
81+
input: "!store redis staging vpc cidr | default",
82+
currentStack: "dev",
83+
expected: "invalid default value format in: redis staging vpc cidr | default",
84+
},
85+
{
86+
name: "lookup with extra parameters after default",
87+
input: "!store redis staging vpc cidr | default 172.20.0.0/16 extra",
88+
currentStack: "dev",
89+
expected: "invalid default value format in: redis staging vpc cidr | default 172.20.0.0/16 extra",
90+
},
91+
}
92+
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
result := processTagStore(atmosConfig, tt.input, tt.currentStack)
96+
assert.Equal(t, tt.expected, result)
97+
})
98+
}
99+
}

website/docs/core-concepts/stacks/yaml-functions/store.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@ import Intro from '@site/src/components/Intro'
99
import Terminal from '@site/src/components/Terminal'
1010

1111
<Intro>
12-
The `!store` YAML function allows reading the values from a remote [store](/core-concepts/projects/configuration/stores) (e.g. SSM Parameter Store, Artifactory, etc.)
12+
The `!store` YAML function allows reading the values from a remote [store](/core-concepts/projects/configuration/stores) (e.g. SSM Parameter Store, Artifactory, Redis, etc.)
1313
into Atmos stack manifests.
1414
</Intro>
1515

1616
## Usage
1717

18-
The `!store` function can be called with either two or three parameters:
18+
The `!store` function can be called with either two or three parameters, and optionally a default value:
1919

2020
```yaml
2121
# Get the `key` from the store of a `component` in the current stack
2222
!store <component> <key>
2323

2424
# Get the `key` from the store of a `component` in a different stack
2525
!store <component> <stack> <key>
26+
27+
# Get the `key` from the store of a `component` in a different stack, with a default value
28+
!store <component> <stack> <key> | default <default_value>
2629
```
2730

2831
## Arguments
@@ -36,6 +39,9 @@ The `!store` function can be called with either two or three parameters:
3639

3740
<dt>`key`</dt>
3841
<dd>The key to read from the store</dd>
42+
43+
<dt>`default_value`</dt>
44+
<dd>(optional) The default value to return if the key is not found in the store</dd>
3945
</dl>
4046

4147

0 commit comments

Comments
 (0)