Skip to content

Commit

Permalink
interpolate aws vars (#7)
Browse files Browse the repository at this point in the history
* refactor interpolation

* interpolate aws region and account
  • Loading branch information
adikari authored Nov 21, 2022
1 parent d7878e9 commit 9d9a500
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 65 deletions.
25 changes: 13 additions & 12 deletions cloudformation/cloudformation.go → aws/cloudformation.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package cloudformation
package aws

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"
)

Expand All @@ -14,6 +13,8 @@ var (
throttleDelay = client.DefaultRetryerMinRetryDelay
)

var cfClient *cloudformation.CloudFormation

type Cloudformation struct {
client *cloudformation.CloudFormation
}
Expand All @@ -39,16 +40,16 @@ func (c *Cloudformation) GetOutput(stackname string) (map[string]string, error)
}

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

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

cfClient = cloudformation.New(Session, &aws.Config{
Retryer: retryer,
})
}

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

return Cloudformation{client: c}
return Cloudformation{client: cfClient}
}
5 changes: 5 additions & 0 deletions aws/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package aws

import "github.com/aws/aws-sdk-go/aws/session"

var Session = session.Must(session.NewSession())
23 changes: 23 additions & 0 deletions aws/sts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package aws

import (
"github.com/aws/aws-sdk-go/service/sts"
)

type Sts struct {
client *sts.STS
}

var stsClient *sts.STS

func NewSts() Sts {
if stsClient == nil {
stsClient = sts.New(Session)
}

return Sts{client: stsClient}
}

func (s *Sts) GetCallerIdentity() (*sts.GetCallerIdentityOutput, error) {
return s.client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
}
38 changes: 5 additions & 33 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ 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 @@ -41,24 +39,6 @@ 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 @@ -108,29 +88,21 @@ func deploy(cmd *cobra.Command, args []string) error {
}

// filter configs with changed values
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
for _, c := range config.Configs {
found := false
for _, a := range all {
if co.Name == *a.Name {
if c.Name == *a.Name {
found = true

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

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

Expand Down
64 changes: 54 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"html/template"
"io/ioutil"

"github.com/adikari/safebox/v2/aws"
"github.com/adikari/safebox/v2/store"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
Expand All @@ -23,6 +24,7 @@ type rawConfig struct {
type Config struct {
Provider string
Service string
Stage string
Prefix string
All []store.ConfigInput
Configs []store.ConfigInput
Expand Down Expand Up @@ -58,30 +60,30 @@ func Load(param LoadConfigInput) (*Config, error) {

c := Config{}
c.Service = rc.Service
c.Stage = param.Stage
c.Provider = rc.Provider
c.Prefix = getPrefix(param.Stage, c.Service)

if c.Provider == "" {
c.Provider = store.SsmProvider
}

variables := map[string]string{
"stage": param.Stage,
"service": c.Service,
variables, err := loadVariables(c, rc)

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

for _, name := range rc.CloudformationStacks {
value, err := Interpolate(name, variables)
for key, value := range rc.Config["defaults"] {
val, err := Interpolate(value, variables)

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

for key, value := range rc.Config["defaults"] {
c.Configs = append(c.Configs, store.ConfigInput{
Name: formatPath(c.Prefix, key),
Value: value,
Value: val,
Secret: false,
})
}
Expand Down Expand Up @@ -149,6 +151,48 @@ func validateConfig(rc rawConfig) error {
return nil
}

func loadVariables(c Config, rc rawConfig) (map[string]string, error) {
st := aws.NewSts()

id, err := st.GetCallerIdentity()

if err != nil {
return nil, err
}

variables := map[string]string{
"stage": c.Stage,
"service": c.Service,
"region": *aws.Session.Config.Region,
"account": *id.Account,
}

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

// add cloudformation outputs to variables available for interpolation
if len(c.Stacks) > 0 {
cf := aws.NewCloudformation()
// TODO: support multiple cf stack outputs
outputs, err := cf.GetOutput(c.Stacks[0])

if err != nil {
return nil, err
}

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

return variables, nil
}

func Interpolate(value string, variables map[string]string) (string, error) {
var result bytes.Buffer
tmpl, _ := template.New("interpolate").Option("missingkey=error").Parse(value)
Expand Down
6 changes: 5 additions & 1 deletion example/safebox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ config:
DB_NAME: "database name updated"
API_ENDPOINT: "{{.internalDomainName}}"
NEW: "endpoint-{{.stage}}"
NEW5: "endpoint updated"
NEW2: "endpoint updated"
NEW3: "endpoint updated"
AWS_REGION: "{{.region}}"
AWS_ACCOUNT: "{{.account}}"
NEW6: "endpoint updated"
NEW7: "endpoint updated"

prod:
DB_NAME: "production db name"
Expand Down
20 changes: 11 additions & 9 deletions store/ssmstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package store
import (
"strings"

a "github.com/adikari/safebox/v2/aws"
"github.com/adikari/safebox/v2/util"
"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/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
)
Expand All @@ -18,22 +18,24 @@ var (
throttleDelay = client.DefaultRetryerMinRetryDelay
)

var svc *ssm.SSM

type SSMStore struct {
svc ssmiface.SSMAPI
}

func NewSSMStore() (*SSMStore, error) {
ssmSession := session.Must(session.NewSession())
if svc == nil {
retryer := client.DefaultRetryer{
NumMaxRetries: numberOfRetries,
MinThrottleDelay: throttleDelay,
}

retryer := client.DefaultRetryer{
NumMaxRetries: numberOfRetries,
MinThrottleDelay: throttleDelay,
svc = ssm.New(a.Session, &aws.Config{
Retryer: retryer,
})
}

svc := ssm.New(ssmSession, &aws.Config{
Retryer: retryer,
})

return &SSMStore{
svc: svc,
}, nil
Expand Down

0 comments on commit 9d9a500

Please sign in to comment.