Skip to content

Commit

Permalink
allow user to specify default value when using store
Browse files Browse the repository at this point in the history
  • Loading branch information
mcalhoun committed Feb 4, 2025
1 parent 75f70b6 commit 2dc79e1
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 38 deletions.
86 changes: 50 additions & 36 deletions internal/exec/yaml_func_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,11 @@ import (
)

type params struct {
storeName string
stack string
component string
key string
}

func getParams(input string, currentStack string) (params, error) {
parts := strings.Split(input, " ")

partsLength := len(parts)
if partsLength != 3 && partsLength != 4 {
return params{}, fmt.Errorf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
}

retParams := params{storeName: strings.TrimSpace(parts[0])}

if partsLength == 4 {
retParams.stack = strings.TrimSpace(parts[1])
retParams.component = strings.TrimSpace(parts[2])
retParams.key = strings.TrimSpace(parts[3])
} else if partsLength == 3 {
retParams.stack = currentStack
retParams.component = strings.TrimSpace(parts[1])
retParams.key = strings.TrimSpace(parts[2])
} else {
return params{}, fmt.Errorf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
}

return retParams, nil
storeName string
stack string
component string
key string
defaultValue *string
}

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

params, err := getParams(str, currentStack)
if err != nil {
u.LogErrorAndExit(err)
// Split the input on the pipe symbol to separate the store parameters and default value
parts := strings.Split(str, "|")
storePart := strings.TrimSpace(parts[0])

var defaultValue *string
if len(parts) > 1 {
// Expecting the format: default <value>
defaultParts := strings.Fields(strings.TrimSpace(parts[1]))
if len(defaultParts) != 2 || defaultParts[0] != "default" {
log.Error(fmt.Sprintf("invalid default value format in: %s", str))
return fmt.Sprintf("invalid default value format in: %s", str)
}
val := strings.Trim(defaultParts[1], `"'`) // Remove surrounding quotes if present
defaultValue = &val
}

// Process the main store part
storeParts := strings.Fields(storePart)
partsLength := len(storeParts)
if partsLength != 3 && partsLength != 4 {
return fmt.Sprintf("invalid Atmos Store YAML function execution:: %s\ninvalid parameters: store_name, {stack}, component, key", input)
}

retParams := params{
storeName: strings.TrimSpace(storeParts[0]),
defaultValue: defaultValue,
}

if partsLength == 4 {
retParams.stack = strings.TrimSpace(storeParts[1])
retParams.component = strings.TrimSpace(storeParts[2])
retParams.key = strings.TrimSpace(storeParts[3])
} else if partsLength == 3 {
retParams.stack = currentStack
retParams.component = strings.TrimSpace(storeParts[1])
retParams.key = strings.TrimSpace(storeParts[2])
}

store := atmosConfig.Stores[params.storeName]
// Retrieve the store from atmosConfig
store := atmosConfig.Stores[retParams.storeName]

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

value, err := store.Get(params.stack, params.component, params.key)
// Retrieve the value from the store
value, err := store.Get(retParams.stack, retParams.component, retParams.key)
if err != nil {
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))
if retParams.defaultValue != nil {
return *retParams.defaultValue
}
u.LogErrorAndExit(fmt.Errorf("failed to get key: %s", err))
}

return value
Expand Down
98 changes: 98 additions & 0 deletions internal/exec/yaml_func_store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package exec

import (
"fmt"
"os"
"testing"

"github.com/alicebob/miniredis/v2"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/store"
"github.com/stretchr/testify/assert"
)

func TestProcessTagStore(t *testing.T) {
// Start a new Redis server
s := miniredis.RunT(t)
defer s.Close()

// Setup the Redis ENV variable
redisUrl := fmt.Sprintf("redis://%s", s.Addr())
origRedisUrl := os.Getenv("ATMOS_REDIS_URL")
os.Setenv("ATMOS_REDIS_URL", redisUrl)
defer os.Setenv("ATMOS_REDIS_URL", origRedisUrl)

// Create a new Redis store
redisStore, err := store.NewRedisStore(store.RedisStoreOptions{
URL: &redisUrl,
})
assert.NoError(t, err)

// Setup test configuration
atmosConfig := schema.AtmosConfiguration{
Stores: map[string]store.Store{
"redis": redisStore,
},
}

// Populate the store with some data
redisStore.Set("dev", "vpc", "cidr", "10.0.0.0/16")
redisStore.Set("prod", "vpc", "cidr", "172.16.0.0/16")

tests := []struct {
name string
input string
currentStack string
expected interface{}
}{
{
name: "lookup using current stack",
input: "!store redis vpc cidr",
currentStack: "dev",
expected: "10.0.0.0/16",
},
{
name: "basic lookup cross-stack",
input: "!store redis dev vpc cidr",
currentStack: "prod",
expected: "10.0.0.0/16",
},
{
name: "lookup with default value without quotes",
input: "!store redis staging vpc cidr | default 172.20.0.0/16",
currentStack: "dev",
expected: "172.20.0.0/16",
},
{
name: "lookup with default value with single quotes",
input: "!store redis staging vpc cidr | default '172.20.0.0/16'",
currentStack: "dev",
expected: "172.20.0.0/16",
},
{
name: "lookup with default value with double quotes",
input: "!store redis staging vpc cidr | default \"172.20.0.0/16\"",
currentStack: "dev",
expected: "172.20.0.0/16",
},
{
name: "lookup with invalid default format",
input: "!store redis staging vpc cidr | default",
currentStack: "dev",
expected: "invalid default value format in: redis staging vpc cidr | default",
},
{
name: "lookup with extra parameters after default",
input: "!store redis staging vpc cidr | default 172.20.0.0/16 extra",
currentStack: "dev",
expected: "invalid default value format in: redis staging vpc cidr | default 172.20.0.0/16 extra",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := processTagStore(atmosConfig, tt.input, tt.currentStack)
assert.Equal(t, tt.expected, result)
})
}
}
10 changes: 8 additions & 2 deletions website/docs/core-concepts/stacks/yaml-functions/store.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@ import Intro from '@site/src/components/Intro'
import Terminal from '@site/src/components/Terminal'

<Intro>
The `!store` YAML function allows reading the values from a remote [store](/core-concepts/projects/configuration/stores) (e.g. SSM Parameter Store, Artifactory, etc.)
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.)
into Atmos stack manifests.
</Intro>

## Usage

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

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

# Get the `key` from the store of a `component` in a different stack
!store <component> <stack> <key>

# Get the `key` from the store of a `component` in a different stack, with a default value
!store <component> <stack> <key> | default <default_value>
```

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

<dt>`key`</dt>
<dd>The key to read from the store</dd>

<dt>`default_value`</dt>
<dd>(optional) The default value to return if the key is not found in the store</dd>
</dl>


Expand Down

0 comments on commit 2dc79e1

Please sign in to comment.