Skip to content

Commit

Permalink
provider/aws: Elastic Beanstalk Application Version (#5770)
Browse files Browse the repository at this point in the history
* Added new resource aws_elastic_beanstalk_application_version.

* Changing bucket and key to required.

* Update to use d.Id() directly in DescribeApplicationVersions.

* Checking err to make sure that the application version is successfully deleted.

* Update `version_label` to `Computed: true`.

* provider/aws: Updating to python solution stack

* provider/aws: Beanstalk App Version delete source

The Elastic Beanstalk API call to delete `application_version` resource
should not delete the s3 bundle, as this object is managed by another
Terraform resource

* provider/aws: Update application version docs

* Fix application version test

* Add `version_label` update test

Adds test that fails after rebasing branch onto v0.8.x. `version_label`
changes do not update the `aws_elastic_beanstalk_environment` resource.

* `version_label` changes to update environment

* Prevent unintended delete of `application_version`

Prevents an `application_version` used by multiple environments from
being deleted.

* Add `force_delete` attribute

* Update documentation
  • Loading branch information
dharrisio authored and stack72 committed Feb 17, 2017
1 parent 5b7e370 commit 2ab6fcc
Show file tree
Hide file tree
Showing 9 changed files with 536 additions and 1 deletion.
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ func Provider() terraform.ResourceProvider {
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
"aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(),
"aws_elastic_beanstalk_application": resourceAwsElasticBeanstalkApplication(),
"aws_elastic_beanstalk_application_version": resourceAwsElasticBeanstalkApplicationVersion(),
"aws_elastic_beanstalk_configuration_template": resourceAwsElasticBeanstalkConfigurationTemplate(),
"aws_elastic_beanstalk_environment": resourceAwsElasticBeanstalkEnvironment(),
"aws_elasticsearch_domain": resourceAwsElasticSearchDomain(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package aws

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
"time"
)

func resourceAwsElasticBeanstalkApplicationVersion() *schema.Resource {
return &schema.Resource{
Create: resourceAwsElasticBeanstalkApplicationVersionCreate,
Read: resourceAwsElasticBeanstalkApplicationVersionRead,
Update: resourceAwsElasticBeanstalkApplicationVersionUpdate,
Delete: resourceAwsElasticBeanstalkApplicationVersionDelete,

Schema: map[string]*schema.Schema{
"application": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"bucket": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"key": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"force_delete": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}

func resourceAwsElasticBeanstalkApplicationVersionCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn

application := d.Get("application").(string)
description := d.Get("description").(string)
bucket := d.Get("bucket").(string)
key := d.Get("key").(string)
name := d.Get("name").(string)

s3Location := elasticbeanstalk.S3Location{
S3Bucket: aws.String(bucket),
S3Key: aws.String(key),
}

createOpts := elasticbeanstalk.CreateApplicationVersionInput{
ApplicationName: aws.String(application),
Description: aws.String(description),
SourceBundle: &s3Location,
VersionLabel: aws.String(name),
}

log.Printf("[DEBUG] Elastic Beanstalk Application Version create opts: %s", createOpts)
_, err := conn.CreateApplicationVersion(&createOpts)
if err != nil {
return err
}

d.SetId(name)
log.Printf("[INFO] Elastic Beanstalk Application Version Label: %s", name)

return resourceAwsElasticBeanstalkApplicationVersionRead(d, meta)
}

func resourceAwsElasticBeanstalkApplicationVersionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn

resp, err := conn.DescribeApplicationVersions(&elasticbeanstalk.DescribeApplicationVersionsInput{
VersionLabels: []*string{aws.String(d.Id())},
})

if err != nil {
return err
}

if len(resp.ApplicationVersions) == 0 {
log.Printf("[DEBUG] Elastic Beanstalk application version read: application version not found")

d.SetId("")

return nil
} else if len(resp.ApplicationVersions) != 1 {
return fmt.Errorf("Error reading application version properties: found %d application versions, expected 1", len(resp.ApplicationVersions))
}

if err := d.Set("description", resp.ApplicationVersions[0].Description); err != nil {
return err
}

return nil
}

func resourceAwsElasticBeanstalkApplicationVersionUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn

if d.HasChange("description") {
if err := resourceAwsElasticBeanstalkApplicationVersionDescriptionUpdate(conn, d); err != nil {
return err
}
}

return resourceAwsElasticBeanstalkApplicationVersionRead(d, meta)

}

func resourceAwsElasticBeanstalkApplicationVersionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).elasticbeanstalkconn

application := d.Get("application").(string)
name := d.Id()

if d.Get("force_delete").(bool) == false {
environments, err := versionUsedBy(application, name, conn)
if err != nil {
return err
}

if len(environments) > 1 {
return fmt.Errorf("Unable to delete Application Version, it is currently in use by the following environments: %s.", environments)
}
}
_, err := conn.DeleteApplicationVersion(&elasticbeanstalk.DeleteApplicationVersionInput{
ApplicationName: aws.String(application),
VersionLabel: aws.String(name),
DeleteSourceBundle: aws.Bool(false),
})

if err != nil {
if awserr, ok := err.(awserr.Error); ok {
// application version is pending delete, or no longer exists.
if awserr.Code() == "InvalidParameterValue" {
d.SetId("")
return nil
}
}
return err
}

d.SetId("")
return nil
}

func resourceAwsElasticBeanstalkApplicationVersionDescriptionUpdate(conn *elasticbeanstalk.ElasticBeanstalk, d *schema.ResourceData) error {
application := d.Get("application").(string)
description := d.Get("description").(string)
name := d.Get("name").(string)

log.Printf("[DEBUG] Elastic Beanstalk application version: %s, update description: %s", name, description)

_, err := conn.UpdateApplicationVersion(&elasticbeanstalk.UpdateApplicationVersionInput{
ApplicationName: aws.String(application),
Description: aws.String(description),
VersionLabel: aws.String(name),
})

return err
}

func versionUsedBy(applicationName, versionLabel string, conn *elasticbeanstalk.ElasticBeanstalk) ([]string, error) {
now := time.Now()
resp, err := conn.DescribeEnvironments(&elasticbeanstalk.DescribeEnvironmentsInput{
ApplicationName: aws.String(applicationName),
VersionLabel: aws.String(versionLabel),
IncludeDeleted: aws.Bool(true),
IncludedDeletedBackTo: aws.Time(now.Add(-1 * time.Minute)),
})

if err != nil {
return nil, err
}

var environmentIDs []string
for _, environment := range resp.Environments {
environmentIDs = append(environmentIDs, *environment.EnvironmentId)
}

return environmentIDs, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package aws

import (
"fmt"
"log"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/elasticbeanstalk"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSBeanstalkAppVersion_basic(t *testing.T) {

var appVersion elasticbeanstalk.ApplicationVersionDescription

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckApplicationVersionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccBeanstalkApplicationVersionConfig(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckApplicationVersionExists("aws_elastic_beanstalk_application_version.default", &appVersion),
),
},
},
})
}

func testAccCheckApplicationVersionDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).elasticbeanstalkconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_elastic_beanstalk_application_version" {
continue
}

describeApplicationVersionOpts := &elasticbeanstalk.DescribeApplicationVersionsInput{
VersionLabels: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribeApplicationVersions(describeApplicationVersionOpts)
if err == nil {
if len(resp.ApplicationVersions) > 0 {
return fmt.Errorf("Elastic Beanstalk Application Verson still exists.")
}

return nil
}
ec2err, ok := err.(awserr.Error)
if !ok {
return err
}
if ec2err.Code() != "InvalidParameterValue" {
return err
}
}

return nil
}

func testAccCheckApplicationVersionExists(n string, app *elasticbeanstalk.ApplicationVersionDescription) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("Elastic Beanstalk Application Version is not set")
}

conn := testAccProvider.Meta().(*AWSClient).elasticbeanstalkconn
describeApplicationVersionOpts := &elasticbeanstalk.DescribeApplicationVersionsInput{
VersionLabels: []*string{aws.String(rs.Primary.ID)},
}

log.Printf("[DEBUG] Elastic Beanstalk Application Version TEST describe opts: %s", describeApplicationVersionOpts)

resp, err := conn.DescribeApplicationVersions(describeApplicationVersionOpts)
if err != nil {
return err
}
if len(resp.ApplicationVersions) == 0 {
return fmt.Errorf("Elastic Beanstalk Application Version not found.")
}

*app = *resp.ApplicationVersions[0]

return nil
}
}

func testAccBeanstalkApplicationVersionConfig(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "default" {
bucket = "tftest.applicationversion.bucket-%d"
}
resource "aws_s3_bucket_object" "default" {
bucket = "${aws_s3_bucket.default.id}"
key = "beanstalk/python-v1.zip"
source = "test-fixtures/python-v1.zip"
}
resource "aws_elastic_beanstalk_application" "default" {
name = "tf-test-name"
description = "tf-test-desc"
}
resource "aws_elastic_beanstalk_application_version" "default" {
application = "tf-test-name"
name = "tf-test-version-label"
bucket = "${aws_s3_bucket.default.id}"
key = "${aws_s3_bucket_object.default.id}"
}
`, randInt)
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
"version_label": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cname": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -196,6 +201,7 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
tier := d.Get("tier").(string)
app := d.Get("application").(string)
desc := d.Get("description").(string)
version := d.Get("version_label").(string)
settings := d.Get("setting").(*schema.Set)
solutionStack := d.Get("solution_stack_name").(string)
templateName := d.Get("template_name").(string)
Expand Down Expand Up @@ -245,6 +251,10 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
createOpts.TemplateName = aws.String(templateName)
}

if version != "" {
createOpts.VersionLabel = aws.String(version)
}

// Get the current time to filter describeBeanstalkEvents messages
t := time.Now()
log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts)
Expand Down Expand Up @@ -387,6 +397,11 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
}
}

if d.HasChange("version_label") {
hasChange = true
updateOpts.VersionLabel = aws.String(d.Get("version_label").(string))
}

if hasChange {
// Get the current time to filter describeBeanstalkEvents messages
t := time.Now()
Expand Down Expand Up @@ -489,6 +504,10 @@ func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta int
return err
}

if err := d.Set("version_label", env.VersionLabel); err != nil {
return err
}

if err := d.Set("tier", *env.Tier.Name); err != nil {
return err
}
Expand Down
Loading

0 comments on commit 2ab6fcc

Please sign in to comment.