Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Elastic Beanstalk Application Version #3871

Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8d018ae
Added new resource as_elastic_beanstalk_application_version.
dharrisio Nov 12, 2015
c408511
Adding version_label option to beanstalk_environment resource.
dharrisio Nov 13, 2015
3307b09
Handle InvalidParameterValue error when running terraform destroy on…
dharrisio Nov 15, 2015
67fcae1
Merge remote-tracking branch 'origin/f-aws-elastic-beanstalk' into f-…
dharrisio Nov 15, 2015
4c3fd07
Add an update check for version_label and create function to update e…
dharrisio Nov 16, 2015
7b76e60
Adding basic test for Elastic Beanstalk Application Version.
dharrisio Nov 17, 2015
6c944b3
Renaming AWSClient connections to conn for consistency.
dharrisio Nov 19, 2015
68bd845
Fixed failed testAccBeanstalkEnvConfig by updating solution_stack_name.
dharrisio Nov 19, 2015
d06ce54
Updated resourceAwsElasticBeanstalkEnviornmentCreate to treat version…
dharrisio Nov 19, 2015
8d9c718
Adding new documentation for elastic_beanstalk_application_version re…
dharrisio Nov 19, 2015
01bebdd
Removed auto create application option. This option is unnecessary as…
dharrisio Jan 8, 2016
0c8843f
ElasticBeanstalkEnvironmentUpdate with one beanstalk api call.
dharrisio Jan 10, 2016
80f4194
Added new resource as_elastic_beanstalk_application_version.
dharrisio Nov 12, 2015
e241a4c
Adding version_label option to beanstalk_environment resource.
dharrisio Nov 13, 2015
26b830c
Handle InvalidParameterValue error when running terraform destroy on…
dharrisio Nov 15, 2015
b73af4e
Add an update check for version_label and create function to update e…
dharrisio Nov 16, 2015
dd999a9
Adding basic test for Elastic Beanstalk Application Version.
dharrisio Nov 17, 2015
9cafc94
Renaming AWSClient connections to conn for consistency.
dharrisio Nov 19, 2015
c20e638
Updated resourceAwsElasticBeanstalkEnviornmentCreate to treat version…
dharrisio Nov 19, 2015
3566c54
Adding new documentation for elastic_beanstalk_application_version re…
dharrisio Nov 19, 2015
4df23d3
Removed auto create application option. This option is unnecessary as…
dharrisio Jan 8, 2016
3cbf5b9
gofmt
dharrisio Jan 22, 2016
20b7b67
Merge branch 'f-aws-elastic-beanstalk-update-once' into f-aws-elastic…
dharrisio Feb 1, 2016
28010c9
Minor formating and wording changes to documentation.
dharrisio Feb 3, 2016
e132ac9
Cleaned up BeanstalkAppVersion_basic test. Added text fixture for sam…
dharrisio Feb 11, 2016
251f151
Cleaned up test config name. Moved random bucket name creation to _ba…
dharrisio Feb 11, 2016
6de38ca
clean up test terraform document.
dharrisio Feb 12, 2016
b495df7
Adding TestAccAWSBeanstalkEnv_version_label test.
dharrisio Feb 12, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ func Provider() terraform.ResourceProvider {
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
"aws_elastic_beanstalk_application": resourceAwsElasticBeanstalkApplication(),
"aws_elastic_beanstalk_configuration_template": resourceAwsElasticBeanstalkConfigurationTemplate(),
"aws_elastic_beanstalk_application_version": resourceAwsElasticBeanstalkApplicationVersion(),
"aws_elastic_beanstalk_environment": resourceAwsElasticBeanstalkEnvironment(),
"aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(),
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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"
)

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,
Optional: true,
ForceNew: true,
},
"key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

name := d.Id()

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

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

_, err := conn.DeleteApplicationVersion(&elasticbeanstalk.DeleteApplicationVersionInput{
ApplicationName: aws.String(application),
VersionLabel: aws.String(name),
})

d.SetId("")

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

}
return err
}

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package aws

import (
"testing"

"fmt"
"io/ioutil"
"log"

"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/resource"
"github.com/hashicorp/terraform/terraform"
)

var s3File, s3Err = ioutil.TempFile("", "tf.zip")

func TestAccAWSBeanstalkAppVersion_basic(t *testing.T) {
ioutil.WriteFile(s3File.Name(), []byte("{anything will do }"), 0644)

var appVersion elasticbeanstalk.ApplicationVersionDescription

resource.Test(t, resource.TestCase{
PreCheck: func() {
if s3Err != nil {
panic(s3Err)
}
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckApplicationVersionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccApplicationVersionConfig,
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
}
}

var randomBeanstalkBucket = randInt
var testAccApplicationVersionConfig = 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/go-v1.zip"
source = "%s"
}

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}"
}
`, randomBeanstalkBucket, s3File.Name())
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func resourceAwsElasticBeanstalkEnvironment() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
"version_label": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"cname": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -93,6 +97,7 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
cname := d.Get("cname").(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 @@ -123,6 +128,10 @@ func resourceAwsElasticBeanstalkEnvironmentCreate(d *schema.ResourceData, meta i
createOpts.TemplateName = aws.String(templateName)
}

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

log.Printf("[DEBUG] Elastic Beanstalk Environment create opts: %s", createOpts)
resp, err := conn.CreateEnvironment(&createOpts)
if err != nil {
Expand Down Expand Up @@ -172,6 +181,12 @@ func resourceAwsElasticBeanstalkEnvironmentUpdate(d *schema.ResourceData, meta i
}
}

if d.HasChange("version_label") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you attempt to update a setting option and the version label at the same time AWS returns this error

* aws_elastic_beanstalk_environment.default: InvalidParameterValue: Environment named tf-test-name is in an invalid state for this operation. Must be Ready.

Here is an example document that demonstrates the issue.

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "default" {
  bucket = "tftest.applicationversion.bucket"
}

resource "aws_s3_bucket_object" "default" {
  bucket = "${aws_s3_bucket.default.id}"
  key    = "beanstalk/go-v1.zip"
  source = "go-v1.zip"
}

resource "aws_elastic_beanstalk_application" "default" {
  name        = "tf-test-name"
  description = "tf-test-desc"
}

resource "aws_elastic_beanstalk_environment" "default" {
  name                = "tf-test-name"
  application         = "${aws_elastic_beanstalk_application.default.name}"
  version_label       = "${aws_elastic_beanstalk_application_version.default.name}"
  solution_stack_name = "64bit Amazon Linux 2015.09 v2.0.4 running Go 1.4"

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MinSize"
    value     = "1"
  }

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MaxSize"
    value     = "1"
  }
}

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

Change document to

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "default" {
  bucket = "tftest.applicationversion.bucket"
}

resource "aws_s3_bucket_object" "default" {
  bucket = "${aws_s3_bucket.default.id}"
  key    = "beanstalk/go-v1.zip"
  source = "go-v1.zip"
}

resource "aws_elastic_beanstalk_application" "default" {
  name        = "tf-test-name"
  description = "tf-test-desc"
}

resource "aws_elastic_beanstalk_environment" "default" {
  name                = "tf-test-name"
  application         = "${aws_elastic_beanstalk_application.default.name}"
  version_label       = "${aws_elastic_beanstalk_application_version.default.name}"
  solution_stack_name = "64bit Amazon Linux 2015.09 v2.0.4 running Go 1.4"

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MinSize"
    value     = "1"
  }

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MaxSize"
    value     = "2"
  }
}

resource "aws_elastic_beanstalk_application_version" "default" {
  application = "tf-test-name"
  name        = "tf-test-version-label-new"
  bucket      = "${aws_s3_bucket.default.id}"
  key         = "${aws_s3_bucket_object.default.id}"
}

This causes an issue because the environment is still making the option setting changes when it tries to update the environment with the new application version.

I think there are two potential ways to fix this.

  1. Instead of calling update environment for each changed attribute, build one struct that contains all the attribute changes and make one api call to update the environment.
  2. Before calling update environment wait for the state to be ready.

@catsby I'm not sure which of those would be the preferred way, so let me know what you think.

if err := resourceAwsElasticBeanstalkEnvironmentApplicationVersionUpdate(conn, d); err != nil {
return err
}
}

return resourceAwsElasticBeanstalkEnvironmentRead(d, meta)
}

Expand Down Expand Up @@ -237,6 +252,21 @@ func resourceAwsElasticBeanstalkEnvironmentSolutionStackUpdate(conn *elasticbean
return err
}

func resourceAwsElasticBeanstalkEnvironmentApplicationVersionUpdate(conn *elasticbeanstalk.ElasticBeanstalk, d *schema.ResourceData) error {
name := d.Get("name").(string)
version := d.Get("version_label").(string)
envId := d.Id()

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

_, err := conn.UpdateEnvironment(&elasticbeanstalk.UpdateEnvironmentInput{
EnvironmentId: aws.String(envId),
VersionLabel: aws.String(version),
})

return err
}

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

Expand Down Expand Up @@ -273,6 +303,10 @@ func resourceAwsElasticBeanstalkEnvironmentRead(d *schema.ResourceData, meta int
return err
}

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

return resourceAwsElasticBeanstalkEnvironmentSettingsRead(d, meta)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ resource "aws_elastic_beanstalk_application" "tftest" {
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name"
application = "${aws_elastic_beanstalk_application.tftest.name}"
solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4"
solution_stack_name = "64bit Amazon Linux 2015.09 v2.0.4 running Go 1.4"
#solution_stack_name =
}
`
Loading