github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_opsworks_application.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "time" 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/opsworks" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceAwsOpsworksApplication() *schema.Resource { 17 return &schema.Resource{ 18 19 Create: resourceAwsOpsworksApplicationCreate, 20 Read: resourceAwsOpsworksApplicationRead, 21 Update: resourceAwsOpsworksApplicationUpdate, 22 Delete: resourceAwsOpsworksApplicationDelete, 23 Schema: map[string]*schema.Schema{ 24 "id": &schema.Schema{ 25 Type: schema.TypeString, 26 Computed: true, 27 }, 28 "name": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 }, 32 "short_name": &schema.Schema{ 33 Type: schema.TypeString, 34 Computed: true, 35 Optional: true, 36 }, 37 // aws-flow-ruby | java | rails | php | nodejs | static | other 38 "type": &schema.Schema{ 39 Type: schema.TypeString, 40 Required: true, 41 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 42 value := v.(string) 43 44 expected := [7]string{"aws-flow-ruby", "java", "rails", "php", "nodejs", "static", "other"} 45 46 found := false 47 for _, b := range expected { 48 if b == value { 49 found = true 50 } 51 } 52 if !found { 53 errors = append(errors, fmt.Errorf( 54 "%q has to be one of [aws-flow-ruby, java, rails, php, nodejs, static, other]", k)) 55 } 56 return 57 }, 58 }, 59 "stack_id": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 }, 63 // TODO: the following 4 vals are really part of the Attributes array. We should validate that only ones relevant to the chosen type are set, perhaps. (what is the default type? how do they map?) 64 "document_root": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 //Default: "public", 68 }, 69 "rails_env": &schema.Schema{ 70 Type: schema.TypeString, 71 Optional: true, 72 //Default: "production", 73 }, 74 "auto_bundle_on_deploy": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 //Default: true, 78 }, 79 "aws_flow_ruby_settings": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 }, 83 "app_source": &schema.Schema{ 84 Type: schema.TypeList, 85 Optional: true, 86 Computed: true, 87 Elem: &schema.Resource{ 88 Schema: map[string]*schema.Schema{ 89 "type": &schema.Schema{ 90 Type: schema.TypeString, 91 Required: true, 92 }, 93 94 "url": &schema.Schema{ 95 Type: schema.TypeString, 96 Optional: true, 97 }, 98 99 "username": &schema.Schema{ 100 Type: schema.TypeString, 101 Optional: true, 102 }, 103 104 "password": &schema.Schema{ 105 Type: schema.TypeString, 106 Optional: true, 107 }, 108 109 "revision": &schema.Schema{ 110 Type: schema.TypeString, 111 Optional: true, 112 }, 113 114 "ssh_key": &schema.Schema{ 115 Type: schema.TypeString, 116 Optional: true, 117 }, 118 }, 119 }, 120 }, 121 // AutoSelectOpsworksMysqlInstance, OpsworksMysqlInstance, or RdsDbInstance. 122 // anything beside auto select will lead into failure in case the instance doesn't exist 123 // XXX: validation? 124 "data_source_type": &schema.Schema{ 125 Type: schema.TypeString, 126 Optional: true, 127 }, 128 "data_source_database_name": &schema.Schema{ 129 Type: schema.TypeString, 130 Optional: true, 131 }, 132 "data_source_arn": &schema.Schema{ 133 Type: schema.TypeString, 134 Optional: true, 135 }, 136 "description": &schema.Schema{ 137 Type: schema.TypeString, 138 Optional: true, 139 }, 140 "domains": &schema.Schema{ 141 Type: schema.TypeList, 142 Optional: true, 143 Elem: &schema.Schema{Type: schema.TypeString}, 144 }, 145 "environment": &schema.Schema{ 146 Type: schema.TypeSet, 147 Optional: true, 148 Elem: &schema.Resource{ 149 Schema: map[string]*schema.Schema{ 150 "key": &schema.Schema{ 151 Type: schema.TypeString, 152 Required: true, 153 }, 154 "value": &schema.Schema{ 155 Type: schema.TypeString, 156 Required: true, 157 }, 158 "secure": &schema.Schema{ 159 Type: schema.TypeBool, 160 Optional: true, 161 Default: true, 162 }, 163 }, 164 }, 165 }, 166 "enable_ssl": &schema.Schema{ 167 Type: schema.TypeBool, 168 Optional: true, 169 Default: false, 170 }, 171 "ssl_configuration": &schema.Schema{ 172 Type: schema.TypeList, 173 Optional: true, 174 //Computed: true, 175 Elem: &schema.Resource{ 176 Schema: map[string]*schema.Schema{ 177 "certificate": &schema.Schema{ 178 Type: schema.TypeString, 179 Required: true, 180 StateFunc: func(v interface{}) string { 181 switch v.(type) { 182 case string: 183 return strings.TrimSpace(v.(string)) 184 default: 185 return "" 186 } 187 }, 188 }, 189 "private_key": &schema.Schema{ 190 Type: schema.TypeString, 191 Required: true, 192 StateFunc: func(v interface{}) string { 193 switch v.(type) { 194 case string: 195 return strings.TrimSpace(v.(string)) 196 default: 197 return "" 198 } 199 }, 200 }, 201 "chain": &schema.Schema{ 202 Type: schema.TypeString, 203 Optional: true, 204 StateFunc: func(v interface{}) string { 205 switch v.(type) { 206 case string: 207 return strings.TrimSpace(v.(string)) 208 default: 209 return "" 210 } 211 }, 212 }, 213 }, 214 }, 215 }, 216 }, 217 } 218 } 219 220 func resourceAwsOpsworksApplicationValidate(d *schema.ResourceData) error { 221 appSourceCount := d.Get("app_source.#").(int) 222 if appSourceCount > 1 { 223 return fmt.Errorf("Only one app_source is permitted.") 224 } 225 226 sslCount := d.Get("ssl_configuration.#").(int) 227 if sslCount > 1 { 228 return fmt.Errorf("Only one ssl_configuration is permitted.") 229 } 230 231 if d.Get("type") == opsworks.AppTypeNodejs || d.Get("type") == opsworks.AppTypeJava { 232 // allowed attributes: none 233 if d.Get("document_root").(string) != "" || d.Get("rails_env").(string) != "" || d.Get("auto_bundle_on_deploy").(string) != "" || d.Get("aws_flow_ruby_settings").(string) != "" { 234 return fmt.Errorf("No additional attributes are allowed for app type '%s'.", d.Get("type").(string)) 235 } 236 } else if d.Get("type") == opsworks.AppTypeRails { 237 // allowed attributes: document_root, rails_env, auto_bundle_on_deploy 238 if d.Get("aws_flow_ruby_settings").(string) != "" { 239 return fmt.Errorf("Only 'document_root, rails_env, auto_bundle_on_deploy' are allowed for app type '%s'.", opsworks.AppTypeRails) 240 } 241 // rails_env is required 242 if _, ok := d.GetOk("rails_env"); !ok { 243 return fmt.Errorf("Set rails_env must be set if type is set to rails.") 244 } 245 } else if d.Get("type") == opsworks.AppTypePhp || d.Get("type") == opsworks.AppTypeStatic || d.Get("type") == opsworks.AppTypeOther { 246 log.Printf("[DEBUG] the app type is : %s", d.Get("type").(string)) 247 log.Printf("[DEBUG] the attributes are: document_root '%s', rails_env '%s', auto_bundle_on_deploy '%s', aws_flow_ruby_settings '%s'", d.Get("document_root").(string), d.Get("rails_env").(string), d.Get("auto_bundle_on_deploy").(string), d.Get("aws_flow_ruby_settings").(string)) 248 // allowed attributes: document_root 249 if d.Get("rails_env").(string) != "" || d.Get("auto_bundle_on_deploy").(string) != "" || d.Get("aws_flow_ruby_settings").(string) != "" { 250 return fmt.Errorf("Only 'document_root' is allowed for app type '%s'.", d.Get("type").(string)) 251 } 252 } else if d.Get("type") == opsworks.AppTypeAwsFlowRuby { 253 // allowed attributes: aws_flow_ruby_settings 254 if d.Get("document_root").(string) != "" || d.Get("rails_env").(string) != "" || d.Get("auto_bundle_on_deploy").(string) != "" { 255 return fmt.Errorf("Only 'aws_flow_ruby_settings' is allowed for app type '%s'.", d.Get("type").(string)) 256 } 257 } 258 259 return nil 260 } 261 262 func resourceAwsOpsworksApplicationRead(d *schema.ResourceData, meta interface{}) error { 263 client := meta.(*AWSClient).opsworksconn 264 265 req := &opsworks.DescribeAppsInput{ 266 AppIds: []*string{ 267 aws.String(d.Id()), 268 }, 269 } 270 271 log.Printf("[DEBUG] Reading OpsWorks app: %s", d.Id()) 272 273 resp, err := client.DescribeApps(req) 274 if err != nil { 275 if awserr, ok := err.(awserr.Error); ok { 276 if awserr.Code() == "ResourceNotFoundException" { 277 log.Printf("[INFO] App not found: %s", d.Id()) 278 d.SetId("") 279 return nil 280 } 281 } 282 return err 283 } 284 285 app := resp.Apps[0] 286 287 d.Set("name", app.Name) 288 d.Set("stack_id", app.StackId) 289 d.Set("type", app.Type) 290 d.Set("description", app.Description) 291 d.Set("domains", flattenStringList(app.Domains)) 292 d.Set("enable_ssl", app.EnableSsl) 293 resourceAwsOpsworksSetApplicationSsl(d, app.SslConfiguration) 294 resourceAwsOpsworksSetApplicationSource(d, app.AppSource) 295 resourceAwsOpsworksSetApplicationDataSources(d, app.DataSources) 296 resourceAwsOpsworksSetApplicationEnvironmentVariable(d, app.Environment) 297 resourceAwsOpsworksSetApplicationAttributes(d, app.Attributes) 298 return nil 299 } 300 301 func resourceAwsOpsworksApplicationCreate(d *schema.ResourceData, meta interface{}) error { 302 client := meta.(*AWSClient).opsworksconn 303 304 err := resourceAwsOpsworksApplicationValidate(d) 305 if err != nil { 306 return err 307 } 308 309 req := &opsworks.CreateAppInput{ 310 Name: aws.String(d.Get("name").(string)), 311 Shortname: aws.String(d.Get("short_name").(string)), 312 StackId: aws.String(d.Get("stack_id").(string)), 313 Type: aws.String(d.Get("type").(string)), 314 Description: aws.String(d.Get("description").(string)), 315 Domains: expandStringList(d.Get("domains").([]interface{})), 316 EnableSsl: aws.Bool(d.Get("enable_ssl").(bool)), 317 SslConfiguration: resourceAwsOpsworksApplicationSsl(d), 318 AppSource: resourceAwsOpsworksApplicationSource(d), 319 DataSources: resourceAwsOpsworksApplicationDataSources(d), 320 Environment: resourceAwsOpsworksApplicationEnvironmentVariable(d), 321 Attributes: resourceAwsOpsworksApplicationAttributes(d), 322 } 323 324 var resp *opsworks.CreateAppOutput 325 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 326 var cerr error 327 resp, cerr = client.CreateApp(req) 328 if cerr != nil { 329 log.Printf("[INFO] client error") 330 if opserr, ok := cerr.(awserr.Error); ok { 331 // XXX: handle errors 332 log.Printf("[ERROR] OpsWorks error: %s message: %s", opserr.Code(), opserr.Message()) 333 return resource.RetryableError(cerr) 334 } 335 return resource.NonRetryableError(cerr) 336 } 337 return nil 338 }) 339 340 if err != nil { 341 return err 342 } 343 344 appID := *resp.AppId 345 d.SetId(appID) 346 d.Set("id", appID) 347 348 return resourceAwsOpsworksApplicationRead(d, meta) 349 } 350 351 func resourceAwsOpsworksApplicationUpdate(d *schema.ResourceData, meta interface{}) error { 352 client := meta.(*AWSClient).opsworksconn 353 354 err := resourceAwsOpsworksApplicationValidate(d) 355 if err != nil { 356 return err 357 } 358 359 req := &opsworks.UpdateAppInput{ 360 AppId: aws.String(d.Id()), 361 Name: aws.String(d.Get("name").(string)), 362 Type: aws.String(d.Get("type").(string)), 363 Description: aws.String(d.Get("description").(string)), 364 Domains: expandStringList(d.Get("domains").([]interface{})), 365 EnableSsl: aws.Bool(d.Get("enable_ssl").(bool)), 366 SslConfiguration: resourceAwsOpsworksApplicationSsl(d), 367 AppSource: resourceAwsOpsworksApplicationSource(d), 368 DataSources: resourceAwsOpsworksApplicationDataSources(d), 369 Environment: resourceAwsOpsworksApplicationEnvironmentVariable(d), 370 Attributes: resourceAwsOpsworksApplicationAttributes(d), 371 } 372 373 log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id()) 374 375 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 376 _, cerr := client.UpdateApp(req) 377 if cerr != nil { 378 log.Printf("[INFO] client error") 379 if opserr, ok := cerr.(awserr.Error); ok { 380 // XXX: handle errors 381 log.Printf("[ERROR] OpsWorks error: %s message: %s", opserr.Code(), opserr.Message()) 382 return resource.NonRetryableError(cerr) 383 } 384 return resource.RetryableError(cerr) 385 } 386 return nil 387 }) 388 389 if err != nil { 390 return err 391 } 392 return resourceAwsOpsworksApplicationRead(d, meta) 393 } 394 395 func resourceAwsOpsworksApplicationDelete(d *schema.ResourceData, meta interface{}) error { 396 client := meta.(*AWSClient).opsworksconn 397 398 req := &opsworks.DeleteAppInput{ 399 AppId: aws.String(d.Id()), 400 } 401 402 log.Printf("[DEBUG] Deleting OpsWorks application: %s", d.Id()) 403 404 _, err := client.DeleteApp(req) 405 return err 406 } 407 408 func resourceAwsOpsworksSetApplicationEnvironmentVariable(d *schema.ResourceData, v []*opsworks.EnvironmentVariable) { 409 log.Printf("[DEBUG] envs: %s %d", v, len(v)) 410 if len(v) == 0 { 411 d.Set("environment", nil) 412 return 413 } 414 newValue := make([]*map[string]interface{}, len(v)) 415 416 for i := 0; i < len(v); i++ { 417 config := v[i] 418 data := make(map[string]interface{}) 419 newValue[i] = &data 420 421 if config.Key != nil { 422 data["key"] = *config.Key 423 } 424 if config.Value != nil { 425 data["value"] = *config.Value 426 } 427 if config.Secure != nil { 428 429 if bool(*config.Secure) { 430 data["secure"] = &opsworksTrueString 431 } else { 432 data["secure"] = &opsworksFalseString 433 } 434 } 435 log.Printf("[DEBUG] v: %s", data) 436 } 437 438 d.Set("environment", newValue) 439 } 440 441 func resourceAwsOpsworksApplicationEnvironmentVariable(d *schema.ResourceData) []*opsworks.EnvironmentVariable { 442 environmentVariables := d.Get("environment").(*schema.Set).List() 443 result := make([]*opsworks.EnvironmentVariable, len(environmentVariables)) 444 445 for i := 0; i < len(environmentVariables); i++ { 446 env := environmentVariables[i].(map[string]interface{}) 447 448 result[i] = &opsworks.EnvironmentVariable{ 449 Key: aws.String(env["key"].(string)), 450 Value: aws.String(env["value"].(string)), 451 Secure: aws.Bool(env["secure"].(bool)), 452 } 453 } 454 return result 455 } 456 457 func resourceAwsOpsworksApplicationSource(d *schema.ResourceData) *opsworks.Source { 458 count := d.Get("app_source.#").(int) 459 if count == 0 { 460 return nil 461 } 462 463 return &opsworks.Source{ 464 Type: aws.String(d.Get("app_source.0.type").(string)), 465 Url: aws.String(d.Get("app_source.0.url").(string)), 466 Username: aws.String(d.Get("app_source.0.username").(string)), 467 Password: aws.String(d.Get("app_source.0.password").(string)), 468 Revision: aws.String(d.Get("app_source.0.revision").(string)), 469 SshKey: aws.String(d.Get("app_source.0.ssh_key").(string)), 470 } 471 } 472 473 func resourceAwsOpsworksSetApplicationSource(d *schema.ResourceData, v *opsworks.Source) { 474 nv := make([]interface{}, 0, 1) 475 if v != nil { 476 m := make(map[string]interface{}) 477 if v.Type != nil { 478 m["type"] = *v.Type 479 } 480 if v.Url != nil { 481 m["url"] = *v.Url 482 } 483 if v.Username != nil { 484 m["username"] = *v.Username 485 } 486 if v.Password != nil { 487 m["password"] = *v.Password 488 } 489 if v.Revision != nil { 490 m["revision"] = *v.Revision 491 } 492 nv = append(nv, m) 493 } 494 495 err := d.Set("app_source", nv) 496 if err != nil { 497 // should never happen 498 panic(err) 499 } 500 } 501 502 func resourceAwsOpsworksApplicationDataSources(d *schema.ResourceData) []*opsworks.DataSource { 503 arn := d.Get("data_source_arn").(string) 504 databaseName := d.Get("data_source_database_name").(string) 505 databaseType := d.Get("data_source_type").(string) 506 507 result := make([]*opsworks.DataSource, 1) 508 509 if len(arn) > 0 || len(databaseName) > 0 || len(databaseType) > 0 { 510 result[0] = &opsworks.DataSource{ 511 Arn: aws.String(arn), 512 DatabaseName: aws.String(databaseName), 513 Type: aws.String(databaseType), 514 } 515 } 516 return result 517 } 518 519 func resourceAwsOpsworksSetApplicationDataSources(d *schema.ResourceData, v []*opsworks.DataSource) { 520 d.Set("data_source_arn", nil) 521 d.Set("data_source_database_name", nil) 522 d.Set("data_source_type", nil) 523 524 if len(v) == 0 { 525 return 526 } 527 528 d.Set("data_source_arn", v[0].Arn) 529 d.Set("data_source_database_name", v[0].DatabaseName) 530 d.Set("data_source_type", v[0].Type) 531 } 532 533 func resourceAwsOpsworksApplicationSsl(d *schema.ResourceData) *opsworks.SslConfiguration { 534 count := d.Get("ssl_configuration.#").(int) 535 if count == 0 { 536 return nil 537 } 538 539 return &opsworks.SslConfiguration{ 540 PrivateKey: aws.String(d.Get("ssl_configuration.0.private_key").(string)), 541 Certificate: aws.String(d.Get("ssl_configuration.0.certificate").(string)), 542 Chain: aws.String(d.Get("ssl_configuration.0.chain").(string)), 543 } 544 } 545 546 func resourceAwsOpsworksSetApplicationSsl(d *schema.ResourceData, v *opsworks.SslConfiguration) { 547 nv := make([]interface{}, 0, 1) 548 set := false 549 if v != nil { 550 m := make(map[string]interface{}) 551 if v.PrivateKey != nil { 552 m["private_key"] = *v.PrivateKey 553 set = true 554 } 555 if v.Certificate != nil { 556 m["certificate"] = *v.Certificate 557 set = true 558 } 559 if v.Chain != nil { 560 m["chain"] = *v.Chain 561 set = true 562 } 563 if set { 564 nv = append(nv, m) 565 } 566 } 567 568 err := d.Set("ssl_configuration", nv) 569 if err != nil { 570 // should never happen 571 panic(err) 572 } 573 } 574 575 func resourceAwsOpsworksApplicationAttributes(d *schema.ResourceData) map[string]*string { 576 attributes := make(map[string]*string) 577 578 if val := d.Get("document_root").(string); len(val) > 0 { 579 attributes[opsworks.AppAttributesKeysDocumentRoot] = aws.String(val) 580 } 581 if val := d.Get("aws_flow_ruby_settings").(string); len(val) > 0 { 582 attributes[opsworks.AppAttributesKeysAwsFlowRubySettings] = aws.String(val) 583 } 584 if val := d.Get("rails_env").(string); len(val) > 0 { 585 attributes[opsworks.AppAttributesKeysRailsEnv] = aws.String(val) 586 } 587 if val := d.Get("auto_bundle_on_deploy").(string); len(val) > 0 { 588 if val == "1" { 589 val = "true" 590 } else if val == "0" { 591 val = "false" 592 } 593 attributes[opsworks.AppAttributesKeysAutoBundleOnDeploy] = aws.String(val) 594 } 595 596 return attributes 597 } 598 599 func resourceAwsOpsworksSetApplicationAttributes(d *schema.ResourceData, v map[string]*string) { 600 d.Set("document_root", nil) 601 d.Set("rails_env", nil) 602 d.Set("aws_flow_ruby_settings", nil) 603 d.Set("auto_bundle_on_deploy", nil) 604 605 if d.Get("type") == opsworks.AppTypeNodejs || d.Get("type") == opsworks.AppTypeJava { 606 return 607 } else if d.Get("type") == opsworks.AppTypeRails { 608 if val, ok := v[opsworks.AppAttributesKeysDocumentRoot]; ok { 609 d.Set("document_root", val) 610 } 611 if val, ok := v[opsworks.AppAttributesKeysRailsEnv]; ok { 612 d.Set("rails_env", val) 613 } 614 if val, ok := v[opsworks.AppAttributesKeysAutoBundleOnDeploy]; ok { 615 d.Set("auto_bundle_on_deploy", val) 616 } 617 return 618 } else if d.Get("type") == opsworks.AppTypePhp || d.Get("type") == opsworks.AppTypeStatic || d.Get("type") == opsworks.AppTypeOther { 619 if val, ok := v[opsworks.AppAttributesKeysDocumentRoot]; ok { 620 d.Set("document_root", val) 621 } 622 return 623 } else if d.Get("type") == opsworks.AppTypeAwsFlowRuby { 624 if val, ok := v[opsworks.AppAttributesKeysAwsFlowRubySettings]; ok { 625 d.Set("aws_flow_ruby_settings", val) 626 } 627 return 628 } 629 630 return 631 }