Skip to content

Commit

Permalink
feat: load and interpolate variables from cloudformation output
Browse files Browse the repository at this point in the history
  • Loading branch information
adikari committed Oct 3, 2022
1 parent cb95523 commit 5fb96ec
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 26 deletions.
54 changes: 54 additions & 0 deletions cloudformation/cloudformation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cloudformation

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
)

var (
numberOfRetries = 10
throttleDelay = client.DefaultRetryerMinRetryDelay
)

type Cloudformation struct {
client *cloudformation.CloudFormation
}

func (c *Cloudformation) GetOutput(stackname string) (map[string]string, error) {
result := map[string]string{}

resp, _ := c.client.DescribeStacks(&cloudformation.DescribeStacksInput{
StackName: aws.String(stackname),
})

if len(resp.Stacks) <= 0 {
return nil, fmt.Errorf("%s stack does not exist", stackname)
}

stack := resp.Stacks[0]

for _, output := range stack.Outputs {
result[*output.OutputKey] = *output.OutputValue
}

return result, nil
}

func NewCloudformation() Cloudformation {
cfSession := session.Must(session.NewSession())

retryer := client.DefaultRetryer{
NumMaxRetries: numberOfRetries,
MinThrottleDelay: throttleDelay,
}

c := cloudformation.New(cfSession, &aws.Config{
Retryer: retryer,
})

return Cloudformation{client: c}
}
40 changes: 33 additions & 7 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"log"

"github.com/adikari/safebox/v2/cloudformation"
conf "github.com/adikari/safebox/v2/config"
"github.com/adikari/safebox/v2/store"
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
Expand Down Expand Up @@ -43,6 +45,24 @@ func deploy(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to load config")
}

variables := map[string]string{
"stage": stage,
"service": config.Service,
}

if len(config.Stacks) > 0 {
cf := cloudformation.NewCloudformation()
outputs, err := cf.GetOutput(config.Stacks[0])

if err != nil {
return errors.Wrap(err, "failed to load outputs")
}

for key, value := range outputs {
variables[key] = value
}
}

st, err := store.GetStore(config.Provider)

if err != nil {
Expand Down Expand Up @@ -92,21 +112,29 @@ func deploy(cmd *cobra.Command, args []string) error {
}

// filter configs with changed values
for _, c := range config.Configs {
for i, c := range config.Configs {
co := config.Configs[i]
v, err := conf.Interpolate(c.Value, variables)

if err != nil {
return errors.Wrap(err, "failed to interpolate template variables")
}

co.Value = v
found := false
for _, a := range all {
if c.Name == *a.Name {
if co.Name == *a.Name {
found = true

if c.Value != *a.Value {
configsToDeploy = append(configsToDeploy, c)
if co.Value != *a.Value {
configsToDeploy = append(configsToDeploy, co)
}
break
}
}

if !found {
configsToDeploy = append(configsToDeploy, c)
configsToDeploy = append(configsToDeploy, co)
}
}

Expand All @@ -129,8 +157,6 @@ func promptConfig(config store.ConfigInput) store.ConfigInput {
return nil
}

log.Printf("value %s", config.Value)

prompt := promptui.Prompt{
Label: config.Key(),
Validate: validate,
Expand Down
41 changes: 26 additions & 15 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ type rawConfig struct {
Provider string
Service string

Config map[string]map[string]string
Secret map[string]map[string]string
Config map[string]map[string]string
Secret map[string]map[string]string
CloudformationStacks []string `yaml:"cloudformation-stacks"`
}

type Config struct {
Expand All @@ -24,6 +25,7 @@ type Config struct {
All []store.ConfigInput
Configs []store.ConfigInput
Secrets []store.ConfigInput
Stacks []string
}

type LoadConfigInput struct {
Expand Down Expand Up @@ -58,37 +60,39 @@ func Load(param LoadConfigInput) (*Config, error) {
c.Provider = store.SsmProvider
}

parseConfig(rc, &c, param)

return &c, nil
}

func parseConfig(rc rawConfig, c *Config, param LoadConfigInput) {
variables := map[string]string{
"stage": param.Stage,
"service": c.Service,
}

for _, name := range rc.CloudformationStacks {
value, err := Interpolate(name, variables)
if err != nil {
return nil, err
}
c.Stacks = append(c.Stacks, value)
}

for key, value := range rc.Config["defaults"] {
c.Configs = append(c.Configs, store.ConfigInput{
Name: formatPath(param.Stage, c.Service, key),
Value: interpolate(value, variables),
Value: value,
Secret: false,
})
}

for key, value := range rc.Config["shared"] {
c.Configs = append(c.Configs, store.ConfigInput{
Name: formatSharedPath(param.Stage, key),
Value: interpolate(value, variables),
Value: value,
Secret: false,
})
}

for key, value := range rc.Config[param.Stage] {
c.Configs = append(c.Configs, store.ConfigInput{
Name: formatPath(param.Stage, c.Service, key),
Value: interpolate(value, variables),
Value: value,
Secret: false,
})
}
Expand All @@ -112,6 +116,8 @@ func parseConfig(rc rawConfig, c *Config, param LoadConfigInput) {
}

c.All = append(c.Secrets, c.Configs...)

return &c, nil
}

func formatSharedPath(stage string, key string) string {
Expand All @@ -122,12 +128,17 @@ func formatPath(stage string, service string, key string) string {
return fmt.Sprintf("/%s/%s/%s", stage, service, key)
}

func interpolate(value string, variables map[string]string) string {
func Interpolate(value string, variables map[string]string) (string, error) {
var result bytes.Buffer
tmpl, _ := template.New("interpolate").Parse(value)
tmpl, _ := template.New("interpolate").Option("missingkey=error").Parse(value)

err := tmpl.Execute(&result, variables)

if err != nil {
return "", err
}

tmpl.Execute(&result, variables)
return result.String()
return result.String(), nil
}

func removeDuplicate(input []store.ConfigInput) []store.ConfigInput {
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package config
import "testing"

func Test_LoadConfig_InvalidPath(t *testing.T) {
_, err := Load(LoadParam{Path: "invalid file"})
_, err := Load(LoadConfigInput{Path: "invalid file"})
expected := "missing safebox config file invalid file"

if err.Error() != expected {
Expand Down
9 changes: 6 additions & 3 deletions example/safebox.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
service: safebox
provider: ssm

cloudformation-stacks:
- "{{.stage}}-shared-infra-SharedInfraServerless"

config:
defaults:
DB_NAME: "database name updated"
API_ENDPOINT: "endpoint"
NEW: "endpoint"
API_ENDPOINT: "{{.authorizerLambaArns}}"
NEW: "endpoint-{{.stage}}"
NEW2: "endpoint updated"

prod:
DB_NAME: "production db name"

dev:
DB_NAME: "dev db name {{.stage}}"
DB_NAME: "dev db name"

shared:
SHARED_KEY: "shared key"
Expand Down

0 comments on commit 5fb96ec

Please sign in to comment.