github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/secrets_push.go (about) 1 package commands 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "text/template" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/ssm" 14 "github.com/hazelops/ize/internal/config" 15 "github.com/hazelops/ize/pkg/templates" 16 "github.com/pterm/pterm" 17 "github.com/spf13/cobra" 18 ) 19 20 type SecretsPushOptions struct { 21 Config *config.Project 22 AppName string 23 Backend string 24 FilePath string 25 SecretsPath string 26 Force bool 27 Explain bool 28 } 29 30 var explainSecretsPushTmpl = ` 31 SERVICE_SECRETS_FILE={{.EnvDir}}/secrets/{{svc}}.json 32 SERVICE_SECRETS=$(cat $SERVICE_SECRETS_FILE | jq -e -r '. | keys[]') 33 for item in $(echo $SERVICE_SECRETS); do 34 aws --profile={{.AwsProfile}} ssm put-parameter --name="/{{.Env}}/{{svc}}/${item}" --value="$(cat $SERVICE_SECRETS_FILE | jq -r .$item )" --type SecureString --overwrite && \ 35 aws --profile={{.AwsProfile}} ssm add-tags-to-resource --resource-type "Parameter" --resource-id "/{{.Env}}/{{svc}}/${item}" \ 36 --tags "Key=Application,Value={{svc}}" "Key=EnvVarName,Value=${item}" 37 done 38 ` 39 40 var secretsPushExample = templates.Examples(` 41 # Push secrets: 42 43 # This will push secrets for "squibby" app 44 ize secrets push squibby 45 46 # This will push secrets for "squibby" app from a "example-service.json" file to the AWS SSM storage with force option (values will be overwritten if exist) 47 ize secrets push squibby --backend ssm --file example-service.json --force 48 `) 49 50 func NewSecretsPushFlags(project *config.Project) *SecretsPushOptions { 51 return &SecretsPushOptions{ 52 Config: project, 53 } 54 } 55 56 func NewCmdSecretsPush(project *config.Project) *cobra.Command { 57 o := NewSecretsPushFlags(project) 58 59 cmd := &cobra.Command{ 60 Use: "push <app>", 61 Example: secretsPushExample, 62 Short: "Push secrets to a key-value storage (like SSM)", 63 Long: "This command pushes secrets from a local file to a key-value storage (like SSM)", 64 Args: cobra.MinimumNArgs(1), 65 ValidArgsFunction: config.GetApps, 66 RunE: func(cmd *cobra.Command, args []string) error { 67 cmd.SilenceUsage = true 68 69 err := o.Complete(cmd) 70 if err != nil { 71 return err 72 } 73 74 err = o.Validate() 75 if err != nil { 76 return err 77 } 78 79 err = o.Run() 80 if err != nil { 81 return err 82 } 83 84 return nil 85 }, 86 } 87 88 cmd.Flags().StringVar(&o.Backend, "backend", "ssm", "backend type (default=ssm)") 89 cmd.Flags().StringVar(&o.FilePath, "file", "", "file with secrets") 90 cmd.Flags().StringVar(&o.SecretsPath, "path", "", "path where to store secrets (/<env>/<app> by default)") 91 cmd.Flags().BoolVar(&o.Explain, "explain", false, "bash alternative shown") 92 cmd.Flags().BoolVar(&o.Force, "force", false, "allow values overwrite") 93 94 return cmd 95 } 96 97 func (o *SecretsPushOptions) Complete(cmd *cobra.Command) error { 98 o.AppName = cmd.Flags().Args()[0] 99 100 if o.FilePath == "" { 101 o.FilePath = fmt.Sprintf("%s/%s/%s.json", o.Config.EnvDir, "secrets", o.AppName) 102 } 103 104 if o.SecretsPath == "" { 105 o.SecretsPath = fmt.Sprintf("/%s/%s", o.Config.Env, o.AppName) 106 } 107 108 return nil 109 } 110 111 func (o *SecretsPushOptions) Validate() error { 112 if len(o.Config.Env) == 0 { 113 return fmt.Errorf("env must be specified") 114 } 115 116 return nil 117 } 118 119 func (o *SecretsPushOptions) Run() error { 120 if o.Explain { 121 err := o.Config.Generate(explainSecretsPushTmpl, template.FuncMap{ 122 "svc": func() string { 123 return o.AppName 124 }, 125 }) 126 if err != nil { 127 return err 128 } 129 130 return nil 131 } 132 133 s, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Pushing secrets for %s...", o.AppName)) 134 if o.Backend == "ssm" { 135 err := o.push(s) 136 if err != nil { 137 return fmt.Errorf("can't push secrets: %w", err) 138 } 139 } else { 140 return fmt.Errorf("backend with type %s not found or not supported", o.Backend) 141 } 142 143 s.Success("Pushing secrets complete!") 144 145 return nil 146 } 147 148 func (o *SecretsPushOptions) push(s *pterm.SpinnerPrinter) error { 149 s.UpdateText("Reading secrets from file...") 150 values, err := getKeyValuePairs(o.FilePath) 151 if err != nil { 152 return err 153 } 154 155 s.UpdateText(fmt.Sprintf("Pushing secrets to %s://%s...", o.Backend, o.SecretsPath)) 156 157 for key, value := range values { 158 name := fmt.Sprintf("%s/%s", o.SecretsPath, key) 159 160 _, err := o.Config.AWSClient.SSMClient.PutParameter(&ssm.PutParameterInput{ 161 Name: &name, 162 Value: aws.String(value), 163 Type: aws.String(ssm.ParameterTypeSecureString), 164 Overwrite: &o.Force, 165 }) 166 167 if aerr, ok := err.(awserr.Error); ok { 168 switch aerr.Code() { 169 case "ParameterAlreadyExists": 170 return fmt.Errorf("secret already exists, you can use --force to overwrite it") 171 default: 172 return err 173 } 174 } 175 176 _, err = o.Config.AWSClient.SSMClient.AddTagsToResource(&ssm.AddTagsToResourceInput{ 177 ResourceId: &name, 178 ResourceType: aws.String("Parameter"), 179 Tags: []*ssm.Tag{ 180 { 181 Key: aws.String("Application"), 182 Value: &o.AppName, 183 }, 184 { 185 Key: aws.String("EnvVarName"), 186 Value: &key, 187 }, 188 }, 189 }) 190 191 if err != nil { 192 return err 193 } 194 } 195 196 return nil 197 } 198 199 func getKeyValuePairs(filePath string) (map[string]string, error) { 200 if !filepath.IsAbs(filePath) { 201 var err error 202 wd, _ := os.Getwd() 203 filePath, err = filepath.Abs(wd + "/" + filePath) 204 if err != nil { 205 return nil, err 206 } 207 208 } 209 210 if _, err := os.Stat(filePath); err != nil { 211 pterm.Fatal.Sprintfln("%s does not exist", filePath) 212 return nil, err 213 } 214 215 f, err := os.Open(filePath) 216 if err != nil { 217 return nil, err 218 } 219 220 defer func() { 221 cerr := f.Close() 222 if err == nil { 223 err = cerr 224 } 225 }() 226 227 bytes, err := ioutil.ReadAll(f) 228 if err != nil { 229 return nil, err 230 } 231 232 var result map[string]string 233 234 err = json.Unmarshal(bytes, &result) 235 if err != nil { 236 return nil, err 237 } 238 239 return result, nil 240 }