github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/tfenv.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/aws/awserr" 11 "github.com/aws/aws-sdk-go/service/s3" 12 "github.com/aws/aws-sdk-go/service/sts" 13 "github.com/hazelops/ize/internal/config" 14 "github.com/hazelops/ize/internal/template" 15 "github.com/hazelops/ize/pkg/templates" 16 "github.com/pterm/pterm" 17 "github.com/sirupsen/logrus" 18 "github.com/spf13/cobra" 19 ) 20 21 type TfenvOptions struct { 22 Config *config.Project 23 TerraformStateBucketName string 24 } 25 26 var tfenvLongDesc = templates.LongDesc(` 27 tfenv generates backend.tf and variable.tfvars files. 28 `) 29 30 var tfenvExample = templates.Examples(` 31 # Generate files 32 ize tfenv 33 34 # Generate files via config file 35 ize --config-file /path/to/config tfenv 36 37 # Generate files via config file installed from env 38 export IZE_CONFIG_FILE=/path/to/config 39 ize tfenv 40 `) 41 42 func NewTfenvFlags(project *config.Project) *TfenvOptions { 43 return &TfenvOptions{ 44 Config: project, 45 } 46 } 47 48 func NewCmdTfenv(project *config.Project) *cobra.Command { 49 o := NewTfenvFlags(project) 50 51 cmd := &cobra.Command{ 52 Use: "tfenv", 53 Short: "Generate terraform files", 54 Long: tfenvLongDesc, 55 Example: tfenvExample, 56 Hidden: true, 57 RunE: func(cmd *cobra.Command, args []string) error { 58 cmd.SilenceUsage = true 59 60 err := o.Run() 61 if err != nil { 62 return err 63 } 64 65 return nil 66 }, 67 } 68 69 cmd.Flags().StringVar(&o.TerraformStateBucketName, "terraform-state-bucket-name", "", "set terraform state bucket name (default <NAMESPACE>-tf-state)") 70 71 return cmd 72 } 73 74 func (o *TfenvOptions) Run() error { 75 return GenerateTerraformFiles("infra", o.TerraformStateBucketName, o.Config) 76 77 } 78 79 func GenerateTerraformFiles(name string, terraformStateBucketName string, project *config.Project) error { 80 var tf config.Terraform 81 82 if project.Terraform != nil { 83 tf = *project.Terraform[name] 84 } 85 86 if len(terraformStateBucketName) != 0 { 87 tf.StateBucketName = terraformStateBucketName 88 } 89 90 if len(tf.StateBucketName) == 0 { 91 legacyBucketExists := checkTFStateBucket(project, fmt.Sprintf("%s-tf-state", project.Namespace)) 92 // If we found an existing bucket that conforms with the legacy format use it. 93 if legacyBucketExists { 94 tf.StateBucketName = fmt.Sprintf("%s-tf-state", project.Namespace) 95 } else { 96 resp, err := project.AWSClient.STSClient.GetCallerIdentity( 97 &sts.GetCallerIdentityInput{}, 98 ) 99 if err != nil { 100 return err 101 } 102 103 // If we haven't found an existing legacy format state bucket use a <NAMESPACE>-<AWS_ACCOUNT>-tf-state bucket as default (unless overridden with other parameters). 104 tf.StateBucketName = fmt.Sprintf("%s-%s-tf-state", project.Namespace, *resp.Account) 105 } 106 } 107 108 stateKey := fmt.Sprintf("%v/%v.tfstate", project.Env, name) 109 if len(tf.StateName) != 0 { 110 stateKey = fmt.Sprintf("%v/%v.tfstate", project.Env, tf.StateName) 111 } else if name == "infra" { 112 stateKey = filepath.Join(project.Env, "terraform.tfstate") 113 } 114 115 if len(tf.StateBucketRegion) == 0 { 116 tf.StateBucketRegion = project.AwsRegion 117 } 118 119 backendOpts := template.BackendOpts{ 120 ENV: project.Env, 121 LOCALSTACK_ENDPOINT: "", 122 TERRAFORM_STATE_BUCKET_NAME: tf.StateBucketName, 123 TERRAFORM_STATE_KEY: stateKey, 124 TERRAFORM_STATE_REGION: tf.StateBucketRegion, 125 TERRAFORM_STATE_PROFILE: project.AwsProfile, 126 TERRAFORM_STATE_DYNAMODB_TABLE: "tf-state-lock", 127 TERRAFORM_AWS_PROVIDER_VERSION: "", 128 NAMESPACE: project.Namespace, 129 } 130 131 stackPath := filepath.Join(project.EnvDir, name) 132 if name == "infra" { 133 stackPath = project.EnvDir 134 } 135 136 if len(tf.TerraformConfigFile) == 0 { 137 tf.TerraformConfigFile = "backend.tf" 138 } 139 140 logrus.Debugf("backend opts: %s", backendOpts) 141 logrus.Debugf("state dir path: %s", stackPath) 142 logrus.Debugf("config file name: %s", tf.TerraformConfigFile) 143 144 err := template.GenerateBackendTf( 145 backendOpts, 146 filepath.Join(stackPath, tf.TerraformConfigFile), 147 ) 148 if err != nil { 149 pterm.Error.Printfln("Generate terraform file for \"%s\" not completed", name) 150 return fmt.Errorf("can't generate backent.tf: %s", err) 151 } 152 153 home, _ := os.UserHomeDir() 154 key, err := ioutil.ReadFile(fmt.Sprintf("%s/.ssh/id_rsa.pub", home)) 155 if err != nil { 156 pterm.Error.Printfln("Generate terraform file for \"%s\" not completed", name) 157 return fmt.Errorf("can't read public ssh key: %s", err) 158 159 } 160 161 varsOpts := template.VarsOpts{ 162 ENV: project.Env, 163 AWS_PROFILE: project.AwsProfile, 164 AWS_REGION: project.AwsRegion, 165 EC2_KEY_PAIR_NAME: fmt.Sprintf("%v-%v", project.Env, project.Namespace), 166 ROOT_DOMAIN_NAME: tf.RootDomainName, 167 SSH_PUBLIC_KEY: string(key)[:len(string(key))-1], 168 NAMESPACE: project.Namespace, 169 } 170 171 if len(project.Ecs) != 0 { 172 varsOpts.TAG = project.Tag 173 varsOpts.DOCKER_REGISTRY = project.DockerRegistry 174 } 175 176 logrus.Debugf("backend opts: %s", varsOpts) 177 logrus.Debugf("state dir path: %s", stackPath) 178 179 err = template.GenerateVarsTf( 180 varsOpts, 181 stackPath, 182 ) 183 if err != nil { 184 pterm.Error.Printfln("Generate terraform file for \"%s\" not completed", name) 185 return fmt.Errorf("can't generate tfvars: %s", err) 186 } 187 188 pterm.Success.Printfln("Generate terraform file for \"%s\" completed", name) 189 190 return nil 191 } 192 193 func checkTFStateBucket(project *config.Project, name string) bool { 194 _, err := project.AWSClient.S3Client.HeadBucket(&s3.HeadBucketInput{ 195 Bucket: aws.String(name), 196 }) 197 if err != nil { 198 if aerr, ok := err.(awserr.Error); ok { 199 switch aerr.Code() { 200 case s3.ErrCodeNoSuchBucket: 201 return false 202 default: 203 return false 204 } 205 } 206 } 207 208 return true 209 } 210 211 func checkTFStateKey(project *config.Project, bucket, key string) bool { 212 _, err := project.AWSClient.S3Client.HeadObject(&s3.HeadObjectInput{ 213 Bucket: aws.String(bucket), 214 Key: aws.String(key), 215 }) 216 if err != nil { 217 if aerr, ok := err.(awserr.Error); ok { 218 switch aerr.Code() { 219 case s3.ErrCodeNoSuchBucket: 220 return false 221 default: 222 return false 223 } 224 } 225 } 226 227 return true 228 }