Merge branch 'master' into brandontosch/GH-11874

This commit is contained in:
Brandon Tosch 2017-03-28 17:29:47 -07:00
commit 380f55b8a9
226 changed files with 11527 additions and 1017 deletions

View File

@ -4,8 +4,10 @@ language: go
go: go:
- 1.8 - 1.8
# add TF_CONSUL_TEST=1 to run consul tests
# they were causing timouts in travis
env: env:
- CONSUL_VERSION=0.7.5 TF_CONSUL_TEST=1 GOMAXPROCS=4 - CONSUL_VERSION=0.7.5 GOMAXPROCS=4
# Fetch consul for the backend and provider tests # Fetch consul for the backend and provider tests
before_install: before_install:

View File

@ -1,55 +1,95 @@
## 0.9.2 (unreleased) ## 0.9.3 (unreleased)
FEATURES:
* **New Resource:** `aws_api_gateway_usage_plan` [GH-12542]
* **New Resource:** `aws_api_gateway_usage_plan_key` [GH-12851]
* **New Resource:** `github_repository_webhook` [GH-12924]
* **New Interpolation:** `substr` [GH-12870]
IMPROVEMENTS: IMPROVEMENTS:
* core: fix `ignore_changes` causing fields to be removed during apply [GH-12897] * config: New interpolation functions `basename` and `dirname`, for file path manipulation [GH-13080]
* core: add `-force-copy` option to `terraform init` to supress prompts for copying state [GH-12939] * helper/resource: Allow unknown "pending" states [GH-13099]
* helper/acctest: Add NewSSHKeyPair function [GH-12894] * provider/aws: Add support to set iam_role_arn on cloudformation Stack [GH-12547]
* provider/alicloud: simplify validators [GH-12982] * provider/aws: Support priority and listener_arn update of alb_listener_rule [GH-13125]
* provider/aws: Added support for EMR AutoScalingRole [GH-12823] * provider/aws: Support priority and listener_arn update of alb_listener_rule [GH-13125]
* provider/aws: Add `name_prefix` to `aws_autoscaling_group` and `aws_elb` resources [GH-12629] * provider/aws: Deprecate roles in favour of role in iam_instance_profile [GH-13130]
* provider/aws: Updated default configuration manager version in `aws_opsworks_stack` [GH-12979]
* provider/aws: Added aws_api_gateway_api_key value attribute [GH-9462]
* provider/aws: Allow aws_alb subnets to change [GH-12850] ## 0.9.2 (March 28, 2017)
* provider/aws: Support Attachment of ALB Target Groups to Autoscaling Groups [GH-12855]
* provider/azurerm: Add support for setting the primary network interface [GH-11290] BACKWARDS IMCOMPATIBILITIES / NOTES:
* provider/cloudstack: Add `zone_id` to `cloudstack_ipaddress` resource [GH-11306]
* provider/consul: Add support for basic auth to the provider [GH-12679] * provider/openstack: Port Fixed IPs are able to be read again using the original numerical notation. However, Fixed IP configurations which are obtaining addresses via DHCP must now use the `all_fixed_ips` attribute to reference the returned IP address.
* provider/dnsimple: Allow dnsimple_record.priority attribute to be set [GH-12843] * Environment names must be safe to use as a URL path segment without escaping, and is enforced by the CLI.
* provider/google: Add support for service_account, metadata, and image_type fields in GKE cluster config [GH-12743]
* provider/google: Add local ssd count support for container clusters [GH-12281] FEATURES:
* provider/ignition: ignition_filesystem, explicit option to create the filesystem [GH-12980]
* provider/ns1: Ensure provider checks for credentials [GH-12920] * **New Resource:** `alicloud_db_instance` ([#12913](https://github.com/hashicorp/terraform/issues/12913))
* provider/openstack: Adding Timeouts to Blockstorage Resources [GH-12862] * **New Resource:** `aws_api_gateway_usage_plan` ([#12542](https://github.com/hashicorp/terraform/issues/12542))
* provider/openstack: Adding Timeouts to FWaaS v1 Resources [GH-12863] * **New Resource:** `aws_api_gateway_usage_plan_key` ([#12851](https://github.com/hashicorp/terraform/issues/12851))
* provider/openstack: Adding Timeouts to Image v2 and LBaaS v2 Resources [GH-12865] * **New Resource:** `github_repository_webhook` ([#12924](https://github.com/hashicorp/terraform/issues/12924))
* provider/openstack: Adding Timeouts to Network Resources [GH-12866] * **New Resource:** `random_pet` ([#12903](https://github.com/hashicorp/terraform/issues/12903))
* provider/openstack: Adding Timeouts to LBaaS v1 Resources [GH-12867] * **New Interpolation:** `substr` ([#12870](https://github.com/hashicorp/terraform/issues/12870))
* provider/pagerduty: Validate credentials [GH-12854] * **S3 Environments:** The S3 remote state backend now supports named environments
IMPROVEMENTS:
* core: fix interpolation error when referencing computed values from an `aws_instance` `cidr_block` ([#13046](https://github.com/hashicorp/terraform/issues/13046))
* core: fix `ignore_changes` causing fields to be removed during apply ([#12897](https://github.com/hashicorp/terraform/issues/12897))
* core: add `-force-copy` option to `terraform init` to supress prompts for copying state ([#12939](https://github.com/hashicorp/terraform/issues/12939))
* helper/acctest: Add NewSSHKeyPair function ([#12894](https://github.com/hashicorp/terraform/issues/12894))
* provider/alicloud: simplify validators ([#12982](https://github.com/hashicorp/terraform/issues/12982))
* provider/aws: Added support for EMR AutoScalingRole ([#12823](https://github.com/hashicorp/terraform/issues/12823))
* provider/aws: Add `name_prefix` to `aws_autoscaling_group` and `aws_elb` resources ([#12629](https://github.com/hashicorp/terraform/issues/12629))
* provider/aws: Updated default configuration manager version in `aws_opsworks_stack` ([#12979](https://github.com/hashicorp/terraform/issues/12979))
* provider/aws: Added aws_api_gateway_api_key value attribute ([#9462](https://github.com/hashicorp/terraform/issues/9462))
* provider/aws: Allow aws_alb subnets to change ([#12850](https://github.com/hashicorp/terraform/issues/12850))
* provider/aws: Support Attachment of ALB Target Groups to Autoscaling Groups ([#12855](https://github.com/hashicorp/terraform/issues/12855))
* provider/aws: Support Import of iam_server_certificate ([#13065](https://github.com/hashicorp/terraform/issues/13065))
* provider/azurerm: Add support for setting the primary network interface ([#11290](https://github.com/hashicorp/terraform/issues/11290))
* provider/cloudstack: Add `zone_id` to `cloudstack_ipaddress` resource ([#11306](https://github.com/hashicorp/terraform/issues/11306))
* provider/consul: Add support for basic auth to the provider ([#12679](https://github.com/hashicorp/terraform/issues/12679))
* provider/digitalocean: Support disk only resize ([#13059](https://github.com/hashicorp/terraform/issues/13059))
* provider/dnsimple: Allow dnsimple_record.priority attribute to be set ([#12843](https://github.com/hashicorp/terraform/issues/12843))
* provider/google: Add support for service_account, metadata, and image_type fields in GKE cluster config ([#12743](https://github.com/hashicorp/terraform/issues/12743))
* provider/google: Add local ssd count support for container clusters ([#12281](https://github.com/hashicorp/terraform/issues/12281))
* provider/ignition: ignition_filesystem, explicit option to create the filesystem ([#12980](https://github.com/hashicorp/terraform/issues/12980))
* provider/kubernetes: Internal K8S annotations are ignored in `config_map` ([#12945](https://github.com/hashicorp/terraform/issues/12945))
* provider/ns1: Ensure provider checks for credentials ([#12920](https://github.com/hashicorp/terraform/issues/12920))
* provider/openstack: Adding Timeouts to Blockstorage Resources ([#12862](https://github.com/hashicorp/terraform/issues/12862))
* provider/openstack: Adding Timeouts to FWaaS v1 Resources ([#12863](https://github.com/hashicorp/terraform/issues/12863))
* provider/openstack: Adding Timeouts to Image v2 and LBaaS v2 Resources ([#12865](https://github.com/hashicorp/terraform/issues/12865))
* provider/openstack: Adding Timeouts to Network Resources ([#12866](https://github.com/hashicorp/terraform/issues/12866))
* provider/openstack: Adding Timeouts to LBaaS v1 Resources ([#12867](https://github.com/hashicorp/terraform/issues/12867))
* provider/openstack: Deprecating Instance Volume attribute ([#13062](https://github.com/hashicorp/terraform/issues/13062))
* provider/openstack: Decprecating Instance Floating IP attribute ([#13063](https://github.com/hashicorp/terraform/issues/13063))
* provider/openstack: Don't log the catalog ([#13075](https://github.com/hashicorp/terraform/issues/13075))
* provider/openstack: Handle 409/500 Response on Pool Create ([#13074](https://github.com/hashicorp/terraform/issues/13074))
* provider/pagerduty: Validate credentials ([#12854](https://github.com/hashicorp/terraform/issues/12854))
* provider/openstack: Adding all_metadata attribute ([#13061](https://github.com/hashicorp/terraform/issues/13061))
* provider/profitbricks: Handling missing resources ([#13053](https://github.com/hashicorp/terraform/issues/13053))
BUG FIXES: BUG FIXES:
* core: Remove legacy remote state configuration on state migration. This fixes errors when saving plans. [GH-12888] * core: Remove legacy remote state configuration on state migration. This fixes errors when saving plans. ([#12888](https://github.com/hashicorp/terraform/issues/12888))
* provider/arukas: Default timeout for launching container increased to 15mins (was 10mins) [GH-12849] * provider/arukas: Default timeout for launching container increased to 15mins (was 10mins) ([#12849](https://github.com/hashicorp/terraform/issues/12849))
* provider/aws: Fix flattened cloudfront lambda function associations to be a set not a slice [GH-11984] * provider/aws: Fix flattened cloudfront lambda function associations to be a set not a slice ([#11984](https://github.com/hashicorp/terraform/issues/11984))
* provider/aws: Consider ACTIVE as pending state during ECS svc deletion [GH-12986] * provider/aws: Consider ACTIVE as pending state during ECS svc deletion ([#12986](https://github.com/hashicorp/terraform/issues/12986))
* provider/aws: Deprecate the usage of Api Gateway Key Stages in favor of Usage Plans [GH-12883] * provider/aws: Deprecate the usage of Api Gateway Key Stages in favor of Usage Plans ([#12883](https://github.com/hashicorp/terraform/issues/12883))
* provider/aws: prevent panic in resourceAwsSsmDocumentRead [GH-12891] * provider/aws: prevent panic in resourceAwsSsmDocumentRead ([#12891](https://github.com/hashicorp/terraform/issues/12891))
* provider/aws: Prevent panic when setting AWS CodeBuild Source to state [GH-12915] * provider/aws: Prevent panic when setting AWS CodeBuild Source to state ([#12915](https://github.com/hashicorp/terraform/issues/12915))
* provider/aws: Only call replace Iam Instance Profile on existing machines [GH-12922] * provider/aws: Only call replace Iam Instance Profile on existing machines ([#12922](https://github.com/hashicorp/terraform/issues/12922))
* provider/aws: Increase AWS AMI Destroy timeout [GH-12943] * provider/aws: Increase AWS AMI Destroy timeout ([#12943](https://github.com/hashicorp/terraform/issues/12943))
* provider/aws: Set aws_vpc ipv6 for associated only [GH-12899] * provider/aws: Set aws_vpc ipv6 for associated only ([#12899](https://github.com/hashicorp/terraform/issues/12899))
* provider/aws: Fix AWS ECS placement strategy spread fields [GH-12998] * provider/aws: Fix AWS ECS placement strategy spread fields ([#12998](https://github.com/hashicorp/terraform/issues/12998))
* provider/aws: Specify that aws_network_acl_rule requires a cidr block [GH-13013] * provider/aws: Specify that aws_network_acl_rule requires a cidr block ([#13013](https://github.com/hashicorp/terraform/issues/13013))
* provider/google: turn compute_instance_group.instances into a set [GH-12790] * provider/aws: aws_network_acl_rule treat all and -1 for protocol the same ([#13049](https://github.com/hashicorp/terraform/issues/13049))
* provider/mysql: recreate user/grant if user/grant got deleted manually [GH-12791] * provider/aws: Only allow 1 value in alb_listener_rule condition ([#13051](https://github.com/hashicorp/terraform/issues/13051))
* provider/aws: Correct handling of network ACL default IPv6 ingress/egress rules ([#12835](https://github.com/hashicorp/terraform/issues/12835))
* provider/aws: aws_ses_receipt_rule: fix off-by-one errors ([#12961](https://github.com/hashicorp/terraform/issues/12961))
* provider/aws: Fix issue upgrading to Terraform v0.9+ with AWS OpsWorks Stacks ([#13024](https://github.com/hashicorp/terraform/issues/13024))
* provider/fastly: Fix issue importing Fastly Services with Backends ([#12538](https://github.com/hashicorp/terraform/issues/12538))
* provider/google: turn compute_instance_group.instances into a set ([#12790](https://github.com/hashicorp/terraform/issues/12790))
* provider/mysql: recreate user/grant if user/grant got deleted manually ([#12791](https://github.com/hashicorp/terraform/issues/12791))
* provider/openstack: Fix monitor_id typo in LBaaS v1 Pool ([#13069](https://github.com/hashicorp/terraform/issues/13069))
* provider/openstack: Resolve issues with Port Fixed IPs ([#13056](https://github.com/hashicorp/terraform/issues/13056))
* provider/rancher: error when no api_url is provided ([#13086](https://github.com/hashicorp/terraform/issues/13086))
* provider/scaleway: work around parallel request limitation ([#13045](https://github.com/hashicorp/terraform/issues/13045))
## 0.9.1 (March 17, 2017) ## 0.9.1 (March 17, 2017)

View File

@ -152,3 +152,13 @@ $ tree ./pkg/ -P "terraform|*.zip"
``` ```
_Note: Cross-compilation uses [gox](https://github.com/mitchellh/gox), which requires toolchains to be built with versions of Go prior to 1.5. In order to successfully cross-compile with older versions of Go, you will need to run `gox -build-toolchain` before running the commands detailed above._ _Note: Cross-compilation uses [gox](https://github.com/mitchellh/gox), which requires toolchains to be built with versions of Go prior to 1.5. In order to successfully cross-compile with older versions of Go, you will need to run `gox -build-toolchain` before running the commands detailed above._
#### Docker
When using docker you don't need to have any of the Go development tools installed and you can clone terraform to any location on disk (doesn't have to be in your $GOPATH). This is useful for users who want to build `master` or a specific branch for testing without setting up a proper Go environment.
For example, run the following command to build terraform in a linux-based container for macOS.
```sh
docker run --rm -v $(pwd):/go/src/github.com/hashicorp/terraform -w /go/src/github.com/hashicorp/terraform -e XC_OS=darwin -e XC_ARCH=amd64 golang:latest bash -c "apt-get update && apt-get install -y zip && make bin"
```

View File

@ -2,6 +2,7 @@ package alicloud
import ( import (
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
@ -12,8 +13,12 @@ const (
VpcNet = InstanceNetWork("vpc") VpcNet = InstanceNetWork("vpc")
) )
// timeout for common product, ecs e.g.
const defaultTimeout = 120 const defaultTimeout = 120
// timeout for long time progerss product, rds e.g.
const defaultLongTimeout = 800
func getRegion(d *schema.ResourceData, meta interface{}) common.Region { func getRegion(d *schema.ResourceData, meta interface{}) common.Region {
return meta.(*AliyunClient).Region return meta.(*AliyunClient).Region
} }
@ -50,3 +55,26 @@ func isProtocalValid(value string) bool {
} }
return res return res
} }
var DefaultBusinessInfo = ecs.BusinessInfo{
Pack: "terraform",
}
// default region for all resource
const DEFAULT_REGION = "cn-beijing"
// default security ip for db
const DEFAULT_DB_SECURITY_IP = "127.0.0.1"
// we the count of create instance is only one
const DEFAULT_INSTANCE_COUNT = 1
// symbol of multiIZ
const MULTI_IZ_SYMBOL = "MAZ"
// default connect port of db
const DB_DEFAULT_CONNECT_PORT = "3306"
const COMMA_SEPARATED = ","
const LOCAL_HOST_IP = "127.0.0.1"

View File

@ -5,6 +5,7 @@ import (
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ecs"
"github.com/denverdino/aliyungo/rds"
"github.com/denverdino/aliyungo/slb" "github.com/denverdino/aliyungo/slb"
) )
@ -19,6 +20,9 @@ type Config struct {
type AliyunClient struct { type AliyunClient struct {
Region common.Region Region common.Region
ecsconn *ecs.Client ecsconn *ecs.Client
rdsconn *rds.Client
// use new version
ecsNewconn *ecs.Client
vpcconn *ecs.Client vpcconn *ecs.Client
slbconn *slb.Client slbconn *slb.Client
} }
@ -35,6 +39,17 @@ func (c *Config) Client() (*AliyunClient, error) {
return nil, err return nil, err
} }
ecsNewconn, err := c.ecsConn()
if err != nil {
return nil, err
}
ecsNewconn.SetVersion(EcsApiVersion20160314)
rdsconn, err := c.rdsConn()
if err != nil {
return nil, err
}
slbconn, err := c.slbConn() slbconn, err := c.slbConn()
if err != nil { if err != nil {
return nil, err return nil, err
@ -48,11 +63,15 @@ func (c *Config) Client() (*AliyunClient, error) {
return &AliyunClient{ return &AliyunClient{
Region: c.Region, Region: c.Region,
ecsconn: ecsconn, ecsconn: ecsconn,
ecsNewconn: ecsNewconn,
vpcconn: vpcconn, vpcconn: vpcconn,
slbconn: slbconn, slbconn: slbconn,
rdsconn: rdsconn,
}, nil }, nil
} }
const BusinessInfoKey = "Terraform"
func (c *Config) loadAndValidate() error { func (c *Config) loadAndValidate() error {
err := c.validateRegion() err := c.validateRegion()
if err != nil { if err != nil {
@ -74,7 +93,9 @@ func (c *Config) validateRegion() error {
} }
func (c *Config) ecsConn() (*ecs.Client, error) { func (c *Config) ecsConn() (*ecs.Client, error) {
client := ecs.NewClient(c.AccessKey, c.SecretKey) client := ecs.NewECSClient(c.AccessKey, c.SecretKey, c.Region)
client.SetBusinessInfo(BusinessInfoKey)
_, err := client.DescribeRegions() _, err := client.DescribeRegions()
if err != nil { if err != nil {
@ -84,20 +105,21 @@ func (c *Config) ecsConn() (*ecs.Client, error) {
return client, nil return client, nil
} }
func (c *Config) slbConn() (*slb.Client, error) { func (c *Config) rdsConn() (*rds.Client, error) {
client := slb.NewClient(c.AccessKey, c.SecretKey) client := rds.NewRDSClient(c.AccessKey, c.SecretKey, c.Region)
client.SetBusinessInfo(BusinessInfoKey)
return client, nil
}
func (c *Config) slbConn() (*slb.Client, error) {
client := slb.NewSLBClient(c.AccessKey, c.SecretKey, c.Region)
client.SetBusinessInfo(BusinessInfoKey)
return client, nil return client, nil
} }
func (c *Config) vpcConn() (*ecs.Client, error) { func (c *Config) vpcConn() (*ecs.Client, error) {
_, err := c.ecsConn() client := ecs.NewVPCClient(c.AccessKey, c.SecretKey, c.Region)
client.SetBusinessInfo(BusinessInfoKey)
if err != nil {
return nil, err
}
client := &ecs.Client{}
client.Init("https://vpc.aliyuncs.com/", "2016-04-28", c.AccessKey, c.SecretKey)
return client, nil return client, nil
} }

View File

@ -5,10 +5,10 @@ import (
"log" "log"
"regexp" "regexp"
"sort" "sort"
"time"
"github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"time"
) )
func dataSourceAlicloudImages() *schema.Resource { func dataSourceAlicloudImages() *schema.Resource {
@ -175,15 +175,28 @@ func dataSourceAlicloudImagesRead(d *schema.ResourceData, meta interface{}) erro
params.ImageOwnerAlias = ecs.ImageOwnerAlias(owners.(string)) params.ImageOwnerAlias = ecs.ImageOwnerAlias(owners.(string))
} }
resp, _, err := conn.DescribeImages(params) var allImages []ecs.ImageType
for {
images, paginationResult, err := conn.DescribeImages(params)
if err != nil { if err != nil {
return err break
}
allImages = append(allImages, images...)
pagination := paginationResult.NextPage()
if pagination == nil {
break
}
params.Pagination = *pagination
} }
var filteredImages []ecs.ImageType var filteredImages []ecs.ImageType
if nameRegexOk { if nameRegexOk {
r := regexp.MustCompile(nameRegex.(string)) r := regexp.MustCompile(nameRegex.(string))
for _, image := range resp { for _, image := range allImages {
// Check for a very rare case where the response would include no // Check for a very rare case where the response would include no
// image name. No name means nothing to attempt a match against, // image name. No name means nothing to attempt a match against,
// therefore we are skipping such image. // therefore we are skipping such image.
@ -198,7 +211,7 @@ func dataSourceAlicloudImagesRead(d *schema.ResourceData, meta interface{}) erro
} }
} }
} else { } else {
filteredImages = resp[:] filteredImages = allImages[:]
} }
var images []ecs.ImageType var images []ecs.ImageType

View File

@ -97,6 +97,22 @@ func TestAccAlicloudImagesDataSource_nameRegexFilter(t *testing.T) {
}) })
} }
func TestAccAlicloudImagesDataSource_imageNotInFirstPage(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAlicloudImagesDataSourceImageNotInFirstPageConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_images.name_regex_filtered_image"),
resource.TestMatchResourceAttr("data.alicloud_images.name_regex_filtered_image", "images.0.image_id", regexp.MustCompile("^ubuntu_14")),
),
},
},
})
}
// Instance store test - using centos images // Instance store test - using centos images
const testAccCheckAlicloudImagesDataSourceImagesConfig = ` const testAccCheckAlicloudImagesDataSourceImagesConfig = `
data "alicloud_images" "multi_image" { data "alicloud_images" "multi_image" {
@ -128,3 +144,12 @@ data "alicloud_images" "name_regex_filtered_image" {
name_regex = "^centos_6\\w{1,5}[64]{1}.*" name_regex = "^centos_6\\w{1,5}[64]{1}.*"
} }
` `
// Testing image not in first page response
const testAccCheckAlicloudImagesDataSourceImageNotInFirstPageConfig = `
data "alicloud_images" "name_regex_filtered_image" {
most_recent = true
owners = "system"
name_regex = "^ubuntu_14.*_64"
}
`

View File

@ -17,8 +17,6 @@ func TestAccAlicloudInstanceTypesDataSource_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_instance_types.4c8g"), testAccCheckAlicloudDataSourceID("data.alicloud_instance_types.4c8g"),
resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.#", "4"),
resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.cpu_core_count", "4"), resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.cpu_core_count", "4"),
resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.memory_size", "8"), resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.memory_size", "8"),
resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.id", "ecs.s3.large"), resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.id", "ecs.s3.large"),

View File

@ -71,11 +71,6 @@ func TestAccAlicloudRegionsDataSource_empty(t *testing.T) {
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_regions.empty_params_region"), testAccCheckAlicloudDataSourceID("data.alicloud_regions.empty_params_region"),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "name", ""),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "current", ""),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.#", "13"),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.id", "cn-shenzhen"), resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.id", "cn-shenzhen"),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.region_id", "cn-shenzhen"), resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.region_id", "cn-shenzhen"),
resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.local_name", "华南 1"), resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.local_name", "华南 1"),

View File

@ -1,7 +1,10 @@
package alicloud package alicloud
import ( import (
"fmt"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"strconv"
"testing" "testing"
) )
@ -23,6 +26,7 @@ func TestAccAlicloudZonesDataSource_basic(t *testing.T) {
} }
func TestAccAlicloudZonesDataSource_filter(t *testing.T) { func TestAccAlicloudZonesDataSource_filter(t *testing.T) {
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { PreCheck: func() {
testAccPreCheck(t) testAccPreCheck(t)
@ -33,7 +37,7 @@ func TestAccAlicloudZonesDataSource_filter(t *testing.T) {
Config: testAccCheckAlicloudZonesDataSourceFilter, Config: testAccCheckAlicloudZonesDataSourceFilter,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_zones.foo"), testAccCheckAlicloudDataSourceID("data.alicloud_zones.foo"),
resource.TestCheckResourceAttr("data.alicloud_zones.foo", "zones.#", "2"), testCheckZoneLength("data.alicloud_zones.foo"),
), ),
}, },
@ -41,13 +45,59 @@ func TestAccAlicloudZonesDataSource_filter(t *testing.T) {
Config: testAccCheckAlicloudZonesDataSourceFilterIoOptimized, Config: testAccCheckAlicloudZonesDataSourceFilterIoOptimized,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_zones.foo"), testAccCheckAlicloudDataSourceID("data.alicloud_zones.foo"),
resource.TestCheckResourceAttr("data.alicloud_zones.foo", "zones.#", "1"), testCheckZoneLength("data.alicloud_zones.foo"),
), ),
}, },
}, },
}) })
} }
func TestAccAlicloudZonesDataSource_unitRegion(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAlicloudZonesDataSource_unitRegion,
Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_zones.foo"),
),
},
},
})
}
// the zone length changed occasionally
// check by range to avoid test case failure
func testCheckZoneLength(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
ms := s.RootModule()
rs, ok := ms.Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
is := rs.Primary
if is == nil {
return fmt.Errorf("No primary instance: %s", name)
}
i, err := strconv.Atoi(is.Attributes["zones.#"])
if err != nil {
return fmt.Errorf("convert zone length err: %#v", err)
}
if i <= 0 {
return fmt.Errorf("zone length expected greater than 0 got err: %d", i)
}
return nil
}
}
const testAccCheckAlicloudZonesDataSourceBasicConfig = ` const testAccCheckAlicloudZonesDataSourceBasicConfig = `
data "alicloud_zones" "foo" { data "alicloud_zones" "foo" {
} }
@ -55,16 +105,28 @@ data "alicloud_zones" "foo" {
const testAccCheckAlicloudZonesDataSourceFilter = ` const testAccCheckAlicloudZonesDataSourceFilter = `
data "alicloud_zones" "foo" { data "alicloud_zones" "foo" {
"available_instance_type"= "ecs.c2.xlarge" available_instance_type= "ecs.c2.xlarge"
"available_resource_creation"= "VSwitch" available_resource_creation= "VSwitch"
"available_disk_category"= "cloud_efficiency" available_disk_category= "cloud_efficiency"
} }
` `
const testAccCheckAlicloudZonesDataSourceFilterIoOptimized = ` const testAccCheckAlicloudZonesDataSourceFilterIoOptimized = `
data "alicloud_zones" "foo" { data "alicloud_zones" "foo" {
"available_instance_type"= "ecs.c2.xlarge" available_instance_type= "ecs.c2.xlarge"
"available_resource_creation"= "IoOptimized" available_resource_creation= "IoOptimized"
"available_disk_category"= "cloud" available_disk_category= "cloud"
}
`
const testAccCheckAlicloudZonesDataSource_unitRegion = `
provider "alicloud" {
alias = "northeast"
region = "ap-northeast-1"
}
data "alicloud_zones" "foo" {
provider = "alicloud.northeast"
available_resource_creation= "VSwitch"
} }
` `

View File

@ -9,6 +9,7 @@ const (
DiskIncorrectStatus = "IncorrectDiskStatus" DiskIncorrectStatus = "IncorrectDiskStatus"
DiskCreatingSnapshot = "DiskCreatingSnapshot" DiskCreatingSnapshot = "DiskCreatingSnapshot"
InstanceLockedForSecurity = "InstanceLockedForSecurity" InstanceLockedForSecurity = "InstanceLockedForSecurity"
SystemDiskNotFound = "SystemDiskNotFound"
// eip // eip
EipIncorrectStatus = "IncorrectEipStatus" EipIncorrectStatus = "IncorrectEipStatus"
InstanceIncorrectStatus = "IncorrectInstanceStatus" InstanceIncorrectStatus = "IncorrectInstanceStatus"

View File

@ -30,3 +30,8 @@ const (
GroupRulePolicyAccept = GroupRulePolicy("accept") GroupRulePolicyAccept = GroupRulePolicy("accept")
GroupRulePolicyDrop = GroupRulePolicy("drop") GroupRulePolicyDrop = GroupRulePolicy("drop")
) )
const (
EcsApiVersion20160314 = "2016-03-14"
EcsApiVersion20140526 = "2014-05-26"
)

View File

@ -8,13 +8,41 @@ import (
) )
type Listener struct { type Listener struct {
slb.HTTPListenerType
InstancePort int InstancePort int
LoadBalancerPort int LoadBalancerPort int
Protocol string Protocol string
//tcp & udp
PersistenceTimeout int
//https
SSLCertificateId string SSLCertificateId string
Bandwidth int
//tcp
HealthCheckType slb.HealthCheckType
//api interface: http & https is HealthCheckTimeout, tcp & udp is HealthCheckConnectTimeout
HealthCheckConnectTimeout int
} }
type ListenerErr struct {
ErrType string
Err error
}
func (e *ListenerErr) Error() string {
return e.ErrType + " " + e.Err.Error()
}
const (
HealthCheckErrType = "healthCheckErrType"
StickySessionErrType = "stickySessionErrType"
CookieTimeOutErrType = "cookieTimeoutErrType"
CookieErrType = "cookieErrType"
)
// Takes the result of flatmap.Expand for an array of listeners and // Takes the result of flatmap.Expand for an array of listeners and
// returns ELB API compatible objects // returns ELB API compatible objects
func expandListeners(configured []interface{}) ([]*Listener, error) { func expandListeners(configured []interface{}) ([]*Listener, error) {
@ -31,13 +59,78 @@ func expandListeners(configured []interface{}) ([]*Listener, error) {
InstancePort: ip, InstancePort: ip,
LoadBalancerPort: lp, LoadBalancerPort: lp,
Protocol: data["lb_protocol"].(string), Protocol: data["lb_protocol"].(string),
Bandwidth: data["bandwidth"].(int), }
l.Bandwidth = data["bandwidth"].(int)
if v, ok := data["scheduler"]; ok {
l.Scheduler = slb.SchedulerType(v.(string))
} }
if v, ok := data["ssl_certificate_id"]; ok { if v, ok := data["ssl_certificate_id"]; ok {
l.SSLCertificateId = v.(string) l.SSLCertificateId = v.(string)
} }
if v, ok := data["sticky_session"]; ok {
l.StickySession = slb.FlagType(v.(string))
}
if v, ok := data["sticky_session_type"]; ok {
l.StickySessionType = slb.StickySessionType(v.(string))
}
if v, ok := data["cookie_timeout"]; ok {
l.CookieTimeout = v.(int)
}
if v, ok := data["cookie"]; ok {
l.Cookie = v.(string)
}
if v, ok := data["persistence_timeout"]; ok {
l.PersistenceTimeout = v.(int)
}
if v, ok := data["health_check"]; ok {
l.HealthCheck = slb.FlagType(v.(string))
}
if v, ok := data["health_check_type"]; ok {
l.HealthCheckType = slb.HealthCheckType(v.(string))
}
if v, ok := data["health_check_domain"]; ok {
l.HealthCheckDomain = v.(string)
}
if v, ok := data["health_check_uri"]; ok {
l.HealthCheckURI = v.(string)
}
if v, ok := data["health_check_connect_port"]; ok {
l.HealthCheckConnectPort = v.(int)
}
if v, ok := data["healthy_threshold"]; ok {
l.HealthyThreshold = v.(int)
}
if v, ok := data["unhealthy_threshold"]; ok {
l.UnhealthyThreshold = v.(int)
}
if v, ok := data["health_check_timeout"]; ok {
l.HealthCheckTimeout = v.(int)
}
if v, ok := data["health_check_interval"]; ok {
l.HealthCheckInterval = v.(int)
}
if v, ok := data["health_check_http_code"]; ok {
l.HealthCheckHttpCode = slb.HealthCheckHttpCodeType(v.(string))
}
var valid bool var valid bool
if l.SSLCertificateId != "" { if l.SSLCertificateId != "" {
// validate the protocol is correct // validate the protocol is correct

View File

@ -26,7 +26,7 @@ func Provider() terraform.ResourceProvider {
"region": &schema.Schema{ "region": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", "cn-beijing"), DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", DEFAULT_REGION),
Description: descriptions["region"], Description: descriptions["region"],
}, },
}, },
@ -43,6 +43,7 @@ func Provider() terraform.ResourceProvider {
"alicloud_disk_attachment": resourceAliyunDiskAttachment(), "alicloud_disk_attachment": resourceAliyunDiskAttachment(),
"alicloud_security_group": resourceAliyunSecurityGroup(), "alicloud_security_group": resourceAliyunSecurityGroup(),
"alicloud_security_group_rule": resourceAliyunSecurityGroupRule(), "alicloud_security_group_rule": resourceAliyunSecurityGroupRule(),
"alicloud_db_instance": resourceAlicloudDBInstance(),
"alicloud_vpc": resourceAliyunVpc(), "alicloud_vpc": resourceAliyunVpc(),
"alicloud_nat_gateway": resourceAliyunNatGateway(), "alicloud_nat_gateway": resourceAliyunNatGateway(),
//both subnet and vswith exists,cause compatible old version, and compatible aws habit. //both subnet and vswith exists,cause compatible old version, and compatible aws habit.

View File

@ -0,0 +1,545 @@
package alicloud
import (
"bytes"
"encoding/json"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/rds"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"log"
"strconv"
"strings"
"time"
)
func resourceAlicloudDBInstance() *schema.Resource {
return &schema.Resource{
Create: resourceAlicloudDBInstanceCreate,
Read: resourceAlicloudDBInstanceRead,
Update: resourceAlicloudDBInstanceUpdate,
Delete: resourceAlicloudDBInstanceDelete,
Schema: map[string]*schema.Schema{
"engine": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{"MySQL", "SQLServer", "PostgreSQL", "PPAS"}),
ForceNew: true,
Required: true,
},
"engine_version": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{"5.5", "5.6", "5.7", "2008r2", "2012", "9.4", "9.3"}),
ForceNew: true,
Required: true,
},
"db_instance_class": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"db_instance_storage": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"instance_charge_type": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{string(rds.Postpaid), string(rds.Prepaid)}),
Optional: true,
ForceNew: true,
Default: rds.Postpaid,
},
"period": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateAllowedIntValue([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 24, 36}),
Optional: true,
ForceNew: true,
Default: 1,
},
"zone_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"multi_az": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"db_instance_net_type": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{string(common.Internet), string(common.Intranet)}),
Optional: true,
},
"allocate_public_connection": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"instance_network_type": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{string(common.VPC), string(common.Classic)}),
Optional: true,
Computed: true,
},
"vswitch_id": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
"master_user_name": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
"master_user_password": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Sensitive: true,
},
"preferred_backup_period": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
// terraform does not support ValidateFunc of TypeList attr
// ValidateFunc: validateAllowedStringValue([]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}),
Optional: true,
},
"preferred_backup_time": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue(rds.BACKUP_TIME),
Optional: true,
},
"backup_retention_period": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateIntegerInRange(7, 730),
Optional: true,
},
"security_ips": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Optional: true,
},
"port": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"connections": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"connection_string": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ip_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ip_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
Computed: true,
},
"db_mappings": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"db_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"character_set_name": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue(rds.CHARACTER_SET_NAME),
Required: true,
},
"db_description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
Optional: true,
Set: resourceAlicloudDatabaseHash,
},
},
}
}
func resourceAlicloudDatabaseHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["db_name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["character_set_name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["db_description"].(string)))
return hashcode.String(buf.String())
}
func resourceAlicloudDBInstanceCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient)
conn := client.rdsconn
args, err := buildDBCreateOrderArgs(d, meta)
if err != nil {
return err
}
resp, err := conn.CreateOrder(args)
if err != nil {
return fmt.Errorf("Error creating Alicloud db instance: %#v", err)
}
instanceId := resp.DBInstanceId
if instanceId == "" {
return fmt.Errorf("Error get Alicloud db instance id")
}
d.SetId(instanceId)
d.Set("instance_charge_type", d.Get("instance_charge_type"))
d.Set("period", d.Get("period"))
d.Set("period_type", d.Get("period_type"))
// wait instance status change from Creating to running
if err := conn.WaitForInstance(d.Id(), rds.Running, defaultLongTimeout); err != nil {
log.Printf("[DEBUG] WaitForInstance %s got error: %#v", rds.Running, err)
}
if err := modifySecurityIps(d.Id(), d.Get("security_ips"), meta); err != nil {
return err
}
masterUserName := d.Get("master_user_name").(string)
masterUserPwd := d.Get("master_user_password").(string)
if masterUserName != "" && masterUserPwd != "" {
if err := client.CreateAccountByInfo(d.Id(), masterUserName, masterUserPwd); err != nil {
return fmt.Errorf("Create db account %s error: %v", masterUserName, err)
}
}
if d.Get("allocate_public_connection").(bool) {
if err := client.AllocateDBPublicConnection(d.Id(), DB_DEFAULT_CONNECT_PORT); err != nil {
return fmt.Errorf("Allocate public connection error: %v", err)
}
}
return resourceAlicloudDBInstanceUpdate(d, meta)
}
func modifySecurityIps(id string, ips interface{}, meta interface{}) error {
client := meta.(*AliyunClient)
ipList := expandStringList(ips.([]interface{}))
ipstr := strings.Join(ipList[:], COMMA_SEPARATED)
// default disable connect from outside
if ipstr == "" {
ipstr = LOCAL_HOST_IP
}
if err := client.ModifyDBSecurityIps(id, ipstr); err != nil {
return fmt.Errorf("Error modify security ips %s: %#v", ipstr, err)
}
return nil
}
func resourceAlicloudDBInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient)
conn := client.rdsconn
d.Partial(true)
if d.HasChange("db_mappings") {
o, n := d.GetChange("db_mappings")
os := o.(*schema.Set)
ns := n.(*schema.Set)
var allDbs []string
remove := os.Difference(ns).List()
add := ns.Difference(os).List()
if len(remove) > 0 && len(add) > 0 {
return fmt.Errorf("Failure modify database, we neither support create and delete database simultaneous nor modify database attributes.")
}
if len(remove) > 0 {
for _, db := range remove {
dbm, _ := db.(map[string]interface{})
if err := conn.DeleteDatabase(d.Id(), dbm["db_name"].(string)); err != nil {
return fmt.Errorf("Failure delete database %s: %#v", dbm["db_name"].(string), err)
}
}
}
if len(add) > 0 {
for _, db := range add {
dbm, _ := db.(map[string]interface{})
dbName := dbm["db_name"].(string)
allDbs = append(allDbs, dbName)
if err := client.CreateDatabaseByInfo(d.Id(), dbName, dbm["character_set_name"].(string), dbm["db_description"].(string)); err != nil {
return fmt.Errorf("Failure create database %s: %#v", dbName, err)
}
}
}
if err := conn.WaitForAllDatabase(d.Id(), allDbs, rds.Running, 600); err != nil {
return fmt.Errorf("Failure create database %#v", err)
}
if user := d.Get("master_user_name").(string); user != "" {
for _, dbName := range allDbs {
if err := client.GrantDBPrivilege2Account(d.Id(), user, dbName); err != nil {
return fmt.Errorf("Failed to grant database %s readwrite privilege to account %s: %#v", dbName, user, err)
}
}
}
d.SetPartial("db_mappings")
}
if d.HasChange("preferred_backup_period") || d.HasChange("preferred_backup_time") || d.HasChange("backup_retention_period") {
period := d.Get("preferred_backup_period").([]interface{})
periodList := expandStringList(period)
time := d.Get("preferred_backup_time").(string)
retention := d.Get("backup_retention_period").(int)
if time == "" || retention == 0 || len(periodList) < 1 {
return fmt.Errorf("Both backup_time, backup_period and retention_period are required to set backup policy.")
}
ps := strings.Join(periodList[:], COMMA_SEPARATED)
if err := client.ConfigDBBackup(d.Id(), time, ps, retention); err != nil {
return fmt.Errorf("Error set backup policy: %#v", err)
}
d.SetPartial("preferred_backup_period")
d.SetPartial("preferred_backup_time")
d.SetPartial("backup_retention_period")
}
if d.HasChange("security_ips") {
if err := modifySecurityIps(d.Id(), d.Get("security_ips"), meta); err != nil {
return err
}
d.SetPartial("security_ips")
}
if d.HasChange("db_instance_class") || d.HasChange("db_instance_storage") {
co, cn := d.GetChange("db_instance_class")
so, sn := d.GetChange("db_instance_storage")
classOld := co.(string)
classNew := cn.(string)
storageOld := so.(int)
storageNew := sn.(int)
// update except the first time, because we will do it in create function
if classOld != "" && storageOld != 0 {
chargeType := d.Get("instance_charge_type").(string)
if chargeType == string(rds.Prepaid) {
return fmt.Errorf("Prepaid db instance does not support modify db_instance_class or db_instance_storage")
}
if err := client.ModifyDBClassStorage(d.Id(), classNew, strconv.Itoa(storageNew)); err != nil {
return fmt.Errorf("Error modify db instance class or storage error: %#v", err)
}
}
}
d.Partial(false)
return resourceAlicloudDBInstanceRead(d, meta)
}
func resourceAlicloudDBInstanceRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient)
conn := client.rdsconn
instance, err := client.DescribeDBInstanceById(d.Id())
if err != nil {
if notFoundError(err) {
d.SetId("")
return nil
}
return fmt.Errorf("Error Describe DB InstanceAttribute: %#v", err)
}
args := rds.DescribeDatabasesArgs{
DBInstanceId: d.Id(),
}
resp, err := conn.DescribeDatabases(&args)
if err != nil {
return err
}
d.Set("db_mappings", flattenDatabaseMappings(resp.Databases.Database))
argn := rds.DescribeDBInstanceNetInfoArgs{
DBInstanceId: d.Id(),
}
resn, err := conn.DescribeDBInstanceNetInfo(&argn)
if err != nil {
return err
}
d.Set("connections", flattenDBConnections(resn.DBInstanceNetInfos.DBInstanceNetInfo))
ips, err := client.GetSecurityIps(d.Id())
if err != nil {
log.Printf("Describe DB security ips error: %#v", err)
}
d.Set("security_ips", ips)
d.Set("engine", instance.Engine)
d.Set("engine_version", instance.EngineVersion)
d.Set("db_instance_class", instance.DBInstanceClass)
d.Set("port", instance.Port)
d.Set("db_instance_storage", instance.DBInstanceStorage)
d.Set("zone_id", instance.ZoneId)
d.Set("db_instance_net_type", instance.DBInstanceNetType)
d.Set("instance_network_type", instance.InstanceNetworkType)
return nil
}
func resourceAlicloudDBInstanceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AliyunClient).rdsconn
return resource.Retry(5*time.Minute, func() *resource.RetryError {
err := conn.DeleteInstance(d.Id())
if err != nil {
return resource.RetryableError(fmt.Errorf("DB Instance in use - trying again while it is deleted."))
}
args := &rds.DescribeDBInstancesArgs{
DBInstanceId: d.Id(),
}
resp, err := conn.DescribeDBInstanceAttribute(args)
if err != nil {
return resource.NonRetryableError(err)
} else if len(resp.Items.DBInstanceAttribute) < 1 {
return nil
}
return resource.RetryableError(fmt.Errorf("DB in use - trying again while it is deleted."))
})
}
func buildDBCreateOrderArgs(d *schema.ResourceData, meta interface{}) (*rds.CreateOrderArgs, error) {
client := meta.(*AliyunClient)
args := &rds.CreateOrderArgs{
RegionId: getRegion(d, meta),
// we does not expose this param to user,
// because create prepaid instance progress will be stopped when set auto_pay to false,
// then could not get instance info, cause timeout error
AutoPay: "true",
EngineVersion: d.Get("engine_version").(string),
Engine: rds.Engine(d.Get("engine").(string)),
DBInstanceStorage: d.Get("db_instance_storage").(int),
DBInstanceClass: d.Get("db_instance_class").(string),
Quantity: DEFAULT_INSTANCE_COUNT,
Resource: rds.DefaultResource,
}
bussStr, err := json.Marshal(DefaultBusinessInfo)
if err != nil {
return nil, fmt.Errorf("Failed to translate bussiness info %#v from json to string", DefaultBusinessInfo)
}
args.BusinessInfo = string(bussStr)
zoneId := d.Get("zone_id").(string)
args.ZoneId = zoneId
multiAZ := d.Get("multi_az").(bool)
if multiAZ {
if zoneId != "" {
return nil, fmt.Errorf("You cannot set the ZoneId parameter when the MultiAZ parameter is set to true")
}
izs, err := client.DescribeMultiIZByRegion()
if err != nil {
return nil, fmt.Errorf("Get multiAZ id error")
}
if len(izs) < 1 {
return nil, fmt.Errorf("Current region does not support MultiAZ.")
}
args.ZoneId = izs[0]
}
vswitchId := d.Get("vswitch_id").(string)
networkType := d.Get("instance_network_type").(string)
args.InstanceNetworkType = common.NetworkType(networkType)
if vswitchId != "" {
args.VSwitchId = vswitchId
// check InstanceNetworkType with vswitchId
if networkType == string(common.Classic) {
return nil, fmt.Errorf("When fill vswitchId, you shold set instance_network_type to VPC")
} else if networkType == "" {
args.InstanceNetworkType = common.VPC
}
// get vpcId
vpcId, err := client.GetVpcIdByVSwitchId(vswitchId)
if err != nil {
return nil, fmt.Errorf("VswitchId %s is not valid of current region", vswitchId)
}
// fill vpcId by vswitchId
args.VPCId = vpcId
// check vswitchId in zone
vsw, err := client.QueryVswitchById(vpcId, vswitchId)
if err != nil {
return nil, fmt.Errorf("VswitchId %s is not valid of current region", vswitchId)
}
if zoneId == "" {
args.ZoneId = vsw.ZoneId
} else if vsw.ZoneId != zoneId {
return nil, fmt.Errorf("VswitchId %s is not belong to the zone %s", vswitchId, zoneId)
}
}
if v := d.Get("db_instance_net_type").(string); v != "" {
args.DBInstanceNetType = common.NetType(v)
}
chargeType := d.Get("instance_charge_type").(string)
if chargeType != "" {
args.PayType = rds.DBPayType(chargeType)
} else {
args.PayType = rds.Postpaid
}
// if charge type is postpaid, the commodity code must set to bards
if chargeType == string(rds.Postpaid) {
args.CommodityCode = rds.Bards
} else {
args.CommodityCode = rds.Rds
}
period := d.Get("period").(int)
args.UsedTime, args.TimeType = TransformPeriod2Time(period, chargeType)
return args, nil
}

View File

@ -0,0 +1,765 @@
package alicloud
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/rds"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"log"
"strings"
"testing"
)
func TestAccAlicloudDBInstance_basic(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstanceConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"port",
"3306"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_storage",
"10"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"instance_network_type",
"Classic"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_net_type",
"Intranet"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine_version",
"5.6"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine",
"MySQL"),
),
},
},
})
}
func TestAccAlicloudDBInstance_vpc(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_vpc,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"port",
"3306"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_storage",
"10"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"instance_network_type",
"VPC"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_net_type",
"Intranet"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine_version",
"5.6"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine",
"MySQL"),
),
},
},
})
}
func TestC2CAlicloudDBInstance_prepaid_order(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_prepaid_order,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"port",
"3306"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_storage",
"10"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"instance_network_type",
"VPC"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"db_instance_net_type",
"Intranet"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine_version",
"5.6"),
resource.TestCheckResourceAttr(
"alicloud_db_instance.foo",
"engine",
"MySQL"),
),
},
},
})
}
func TestAccAlicloudDBInstance_multiIZ(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_multiIZ,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
testAccCheckDBInstanceMultiIZ(&instance),
),
},
},
})
}
func TestAccAlicloudDBInstance_database(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_database,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "db_mappings.#", "2"),
),
},
resource.TestStep{
Config: testAccDBInstance_database_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "db_mappings.#", "3"),
),
},
},
})
}
func TestAccAlicloudDBInstance_account(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_grantDatabasePrivilege2Account,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "db_mappings.#", "2"),
testAccCheckAccountHasPrivilege2Database("alicloud_db_instance.foo", "tester", "foo", "ReadWrite"),
),
},
},
})
}
func TestAccAlicloudDBInstance_allocatePublicConnection(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_allocatePublicConnection,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "connections.#", "2"),
testAccCheckHasPublicConnection("alicloud_db_instance.foo"),
),
},
},
})
}
func TestAccAlicloudDBInstance_backupPolicy(t *testing.T) {
var policies []map[string]interface{}
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_backup,
Check: resource.ComposeTestCheckFunc(
testAccCheckBackupPolicyExists(
"alicloud_db_instance.foo", policies),
testAccCheckKeyValueInMaps(policies, "backup policy", "preferred_backup_period", "Wednesday,Thursday"),
testAccCheckKeyValueInMaps(policies, "backup policy", "preferred_backup_time", "00:00Z-01:00Z"),
),
},
},
})
}
func TestAccAlicloudDBInstance_securityIps(t *testing.T) {
var ips []map[string]interface{}
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_securityIps,
Check: resource.ComposeTestCheckFunc(
testAccCheckSecurityIpExists(
"alicloud_db_instance.foo", ips),
testAccCheckKeyValueInMaps(ips, "security ip", "security_ips", "127.0.0.1"),
),
},
resource.TestStep{
Config: testAccDBInstance_securityIpsConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckSecurityIpExists(
"alicloud_db_instance.foo", ips),
testAccCheckKeyValueInMaps(ips, "security ip", "security_ips", "10.168.1.12,100.69.7.112"),
),
},
},
})
}
func TestAccAlicloudDBInstance_upgradeClass(t *testing.T) {
var instance rds.DBInstanceAttribute
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_db_instance.foo",
Providers: testAccProviders,
CheckDestroy: testAccCheckDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDBInstance_class,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "db_instance_class", "rds.mysql.t1.small"),
),
},
resource.TestStep{
Config: testAccDBInstance_classUpgrade,
Check: resource.ComposeTestCheckFunc(
testAccCheckDBInstanceExists(
"alicloud_db_instance.foo", &instance),
resource.TestCheckResourceAttr("alicloud_db_instance.foo", "db_instance_class", "rds.mysql.s1.small"),
),
},
},
})
}
func testAccCheckSecurityIpExists(n string, ips []map[string]interface{}) 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("No DB Instance ID is set")
}
conn := testAccProvider.Meta().(*AliyunClient).rdsconn
args := rds.DescribeDBInstanceIPsArgs{
DBInstanceId: rs.Primary.ID,
}
resp, err := conn.DescribeDBInstanceIPs(&args)
log.Printf("[DEBUG] check instance %s security ip %#v", rs.Primary.ID, resp)
if err != nil {
return err
}
p := resp.Items.DBInstanceIPArray
if len(p) < 1 {
return fmt.Errorf("DB security ip not found")
}
ips = flattenDBSecurityIPs(p)
return nil
}
}
func testAccCheckDBInstanceMultiIZ(i *rds.DBInstanceAttribute) resource.TestCheckFunc {
return func(s *terraform.State) error {
if !strings.Contains(i.ZoneId, MULTI_IZ_SYMBOL) {
return fmt.Errorf("Current region does not support multiIZ.")
}
return nil
}
}
func testAccCheckAccountHasPrivilege2Database(n, accountName, dbName, privilege string) 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("No DB instance ID is set")
}
conn := testAccProvider.Meta().(*AliyunClient).rdsconn
if err := conn.WaitForAccountPrivilege(rs.Primary.ID, accountName, dbName, rds.AccountPrivilege(privilege), 50); err != nil {
return fmt.Errorf("Failed to grant database %s privilege to account %s: %v", dbName, accountName, err)
}
return nil
}
}
func testAccCheckHasPublicConnection(n string) 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("No DB instance ID is set")
}
conn := testAccProvider.Meta().(*AliyunClient).rdsconn
if err := conn.WaitForPublicConnection(rs.Primary.ID, 50); err != nil {
return fmt.Errorf("Failed to allocate public connection: %v", err)
}
return nil
}
}
func testAccCheckDBInstanceExists(n string, d *rds.DBInstanceAttribute) 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("No DB Instance ID is set")
}
client := testAccProvider.Meta().(*AliyunClient)
attr, err := client.DescribeDBInstanceById(rs.Primary.ID)
log.Printf("[DEBUG] check instance %s attribute %#v", rs.Primary.ID, attr)
if err != nil {
return err
}
if attr == nil {
return fmt.Errorf("DB Instance not found")
}
*d = *attr
return nil
}
}
func testAccCheckBackupPolicyExists(n string, ps []map[string]interface{}) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Backup policy not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No DB Instance ID is set")
}
conn := testAccProvider.Meta().(*AliyunClient).rdsconn
args := rds.DescribeBackupPolicyArgs{
DBInstanceId: rs.Primary.ID,
}
resp, err := conn.DescribeBackupPolicy(&args)
log.Printf("[DEBUG] check instance %s backup policy %#v", rs.Primary.ID, resp)
if err != nil {
return err
}
var bs []rds.BackupPolicy
bs = append(bs, resp.BackupPolicy)
ps = flattenDBBackup(bs)
return nil
}
}
func testAccCheckKeyValueInMaps(ps []map[string]interface{}, propName, key, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, policy := range ps {
if policy[key].(string) != value {
return fmt.Errorf("DB %s attribute '%s' expected %#v, got %#v", propName, key, value, policy[key])
}
}
return nil
}
}
func testAccCheckDBInstanceDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*AliyunClient)
for _, rs := range s.RootModule().Resources {
if rs.Type != "alicloud_db_instance.foo" {
continue
}
ins, err := client.DescribeDBInstanceById(rs.Primary.ID)
if ins != nil {
return fmt.Errorf("Error DB Instance still exist")
}
// Verify the error is what we want
if err != nil {
// Verify the error is what we want
e, _ := err.(*common.Error)
if e.ErrorResponse.Code == InstanceNotfound {
continue
}
return err
}
}
return nil
}
const testAccDBInstanceConfig = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
}
`
const testAccDBInstance_vpc = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" {
name = "tf_test_foo"
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21"
availability_zone = "${data.alicloud_zones.default.zones.0.id}"
}
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
vswitch_id = "${alicloud_vswitch.foo.id}"
}
`
const testAccDBInstance_multiIZ = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
db_instance_net_type = "Intranet"
multi_az = true
}
`
const testAccDBInstance_prepaid_order = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Prepaid"
db_instance_net_type = "Intranet"
}
`
const testAccDBInstance_database = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
db_mappings = [
{
"db_name" = "foo"
"character_set_name" = "utf8"
"db_description" = "tf"
},{
"db_name" = "bar"
"character_set_name" = "utf8"
"db_description" = "tf"
}]
}
`
const testAccDBInstance_database_update = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
db_mappings = [
{
"db_name" = "foo"
"character_set_name" = "utf8"
"db_description" = "tf"
},{
"db_name" = "bar"
"character_set_name" = "utf8"
"db_description" = "tf"
},{
"db_name" = "zzz"
"character_set_name" = "utf8"
"db_description" = "tf"
}]
}
`
const testAccDBInstance_grantDatabasePrivilege2Account = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
master_user_name = "tester"
master_user_password = "Test12345"
db_mappings = [
{
"db_name" = "foo"
"character_set_name" = "utf8"
"db_description" = "tf"
},{
"db_name" = "bar"
"character_set_name" = "utf8"
"db_description" = "tf"
}]
}
`
const testAccDBInstance_allocatePublicConnection = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
master_user_name = "tester"
master_user_password = "Test12345"
allocate_public_connection = true
}
`
const testAccDBInstance_backup = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
preferred_backup_period = ["Wednesday","Thursday"]
preferred_backup_time = "00:00Z-01:00Z"
backup_retention_period = 9
}
`
const testAccDBInstance_securityIps = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
}
`
const testAccDBInstance_securityIpsConfig = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
instance_charge_type = "Postpaid"
db_instance_net_type = "Intranet"
security_ips = ["10.168.1.12", "100.69.7.112"]
}
`
const testAccDBInstance_class = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.t1.small"
db_instance_storage = "10"
db_instance_net_type = "Intranet"
}
`
const testAccDBInstance_classUpgrade = `
resource "alicloud_db_instance" "foo" {
engine = "MySQL"
engine_version = "5.6"
db_instance_class = "rds.mysql.s1.small"
db_instance_storage = "10"
db_instance_net_type = "Intranet"
}
`

View File

@ -151,4 +151,5 @@ resource "alicloud_security_group" "group" {
name = "terraform-test-group" name = "terraform-test-group"
description = "New security group" description = "New security group"
} }
` `

View File

@ -136,9 +136,13 @@ func testAccCheckDiskDestroy(s *terraform.State) error {
} }
const testAccDiskConfig = ` const testAccDiskConfig = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
}
resource "alicloud_disk" "foo" { resource "alicloud_disk" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
name = "New-disk" name = "New-disk"
description = "Hello ecs disk." description = "Hello ecs disk."
category = "cloud_efficiency" category = "cloud_efficiency"
@ -146,10 +150,15 @@ resource "alicloud_disk" "foo" {
} }
` `
const testAccDiskConfigWithTags = ` const testAccDiskConfigWithTags = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
}
resource "alicloud_disk" "bar" { resource "alicloud_disk" "bar" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
size = "10" category = "cloud_efficiency"
size = "20"
tags { tags {
Name = "TerraformTest" Name = "TerraformTest"
} }

View File

@ -108,6 +108,10 @@ func testAccCheckEIPAssociationDestroy(s *terraform.State) error {
} }
const testAccEIPAssociationConfig = ` const testAccEIPAssociationConfig = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "main" { resource "alicloud_vpc" "main" {
cidr_block = "10.1.0.0/21" cidr_block = "10.1.0.0/21"
} }
@ -115,19 +119,23 @@ resource "alicloud_vpc" "main" {
resource "alicloud_vswitch" "main" { resource "alicloud_vswitch" "main" {
vpc_id = "${alicloud_vpc.main.id}" vpc_id = "${alicloud_vpc.main.id}"
cidr_block = "10.1.1.0/24" cidr_block = "10.1.1.0/24"
availability_zone = "cn-beijing-a" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
depends_on = [ depends_on = [
"alicloud_vpc.main"] "alicloud_vpc.main"]
} }
resource "alicloud_instance" "instance" { resource "alicloud_instance" "instance" {
image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" # cn-beijing
instance_type = "ecs.s1.small"
availability_zone = "cn-beijing-a"
security_groups = ["${alicloud_security_group.group.id}"]
vswitch_id = "${alicloud_vswitch.main.id}" vswitch_id = "${alicloud_vswitch.main.id}"
instance_name = "hello" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
io_optimized = "none"
# series II
instance_type = "ecs.n1.medium"
io_optimized = "optimized"
system_disk_category = "cloud_efficiency"
security_groups = ["${alicloud_security_group.group.id}"]
instance_name = "test_foo"
tags { tags {
Name = "TerraformTest-instance" Name = "TerraformTest-instance"

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"encoding/base64" "encoding/base64"
"encoding/json"
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -21,8 +22,9 @@ func resourceAliyunInstance() *schema.Resource {
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"availability_zone": &schema.Schema{ "availability_zone": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true,
}, },
"image_id": &schema.Schema{ "image_id": &schema.Schema{
@ -60,11 +62,6 @@ func resourceAliyunInstance() *schema.Resource {
ValidateFunc: validateInstanceDescription, ValidateFunc: validateInstanceDescription,
}, },
"instance_network_type": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"internet_charge_type": &schema.Schema{ "internet_charge_type": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -104,11 +101,19 @@ func resourceAliyunInstance() *schema.Resource {
Default: "cloud", Default: "cloud",
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
ValidateFunc: validateAllowedStringValue([]string{
string(ecs.DiskCategoryCloud),
string(ecs.DiskCategoryCloudSSD),
string(ecs.DiskCategoryCloudEfficiency),
string(ecs.DiskCategoryEphemeralSSD),
}),
}, },
"system_disk_size": &schema.Schema{ "system_disk_size": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Computed: true, Computed: true,
ForceNew: true,
ValidateFunc: validateIntegerInRange(40, 500),
}, },
//subnet_id and vswitch_id both exists, cause compatible old version, and aws habit. //subnet_id and vswitch_id both exists, cause compatible old version, and aws habit.
@ -145,7 +150,6 @@ func resourceAliyunInstance() *schema.Resource {
"private_ip": &schema.Schema{ "private_ip": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true,
Computed: true, Computed: true,
}, },
@ -168,6 +172,11 @@ func resourceAliyunInstance() *schema.Resource {
func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) error { func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AliyunClient).ecsconn conn := meta.(*AliyunClient).ecsconn
// create postpaid instance by runInstances API
if v := d.Get("instance_charge_type").(string); v != string(common.PrePaid) {
return resourceAliyunRunInstance(d, meta)
}
args, err := buildAliyunInstanceArgs(d, meta) args, err := buildAliyunInstanceArgs(d, meta)
if err != nil { if err != nil {
return err return err
@ -181,7 +190,8 @@ func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) erro
d.SetId(instanceID) d.SetId(instanceID)
d.Set("password", d.Get("password")) d.Set("password", d.Get("password"))
d.Set("system_disk_category", d.Get("system_disk_category")) //d.Set("system_disk_category", d.Get("system_disk_category"))
//d.Set("system_disk_size", d.Get("system_disk_size"))
if d.Get("allocate_public_ip").(bool) { if d.Get("allocate_public_ip").(bool) {
_, err := conn.AllocatePublicIpAddress(d.Id()) _, err := conn.AllocatePublicIpAddress(d.Id())
@ -207,11 +217,56 @@ func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) erro
return resourceAliyunInstanceUpdate(d, meta) return resourceAliyunInstanceUpdate(d, meta)
} }
func resourceAliyunRunInstance(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AliyunClient).ecsconn
newConn := meta.(*AliyunClient).ecsNewconn
args, err := buildAliyunInstanceArgs(d, meta)
if err != nil {
return err
}
runArgs, err := buildAliyunRunInstancesArgs(d, meta)
if err != nil {
return err
}
runArgs.CreateInstanceArgs = *args
// runInstances is support in version 2016-03-14
instanceIds, err := newConn.RunInstances(runArgs)
if err != nil {
return fmt.Errorf("Error creating Aliyun ecs instance: %#v", err)
}
d.SetId(instanceIds[0])
d.Set("password", d.Get("password"))
d.Set("system_disk_category", d.Get("system_disk_category"))
d.Set("system_disk_size", d.Get("system_disk_size"))
if d.Get("allocate_public_ip").(bool) {
_, err := conn.AllocatePublicIpAddress(d.Id())
if err != nil {
log.Printf("[DEBUG] AllocatePublicIpAddress for instance got error: %#v", err)
}
}
// after instance created, its status change from pending, starting to running
if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Running, defaultTimeout); err != nil {
log.Printf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Running, err)
}
return resourceAliyunInstanceUpdate(d, meta)
}
func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error { func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
conn := client.ecsconn conn := client.ecsconn
instance, err := client.QueryInstancesById(d.Id()) instance, err := client.QueryInstancesById(d.Id())
if err != nil { if err != nil {
if notFoundError(err) { if notFoundError(err) {
d.SetId("") d.SetId("")
@ -220,7 +275,15 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error DescribeInstanceAttribute: %#v", err) return fmt.Errorf("Error DescribeInstanceAttribute: %#v", err)
} }
log.Printf("[DEBUG] DescribeInstanceAttribute for instance: %#v", instance) disk, diskErr := client.QueryInstanceSystemDisk(d.Id())
if diskErr != nil {
if notFoundError(diskErr) {
d.SetId("")
return nil
}
return fmt.Errorf("Error DescribeSystemDisk: %#v", err)
}
d.Set("instance_name", instance.InstanceName) d.Set("instance_name", instance.InstanceName)
d.Set("description", instance.Description) d.Set("description", instance.Description)
@ -229,6 +292,8 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("host_name", instance.HostName) d.Set("host_name", instance.HostName)
d.Set("image_id", instance.ImageId) d.Set("image_id", instance.ImageId)
d.Set("instance_type", instance.InstanceType) d.Set("instance_type", instance.InstanceType)
d.Set("system_disk_category", disk.Category)
d.Set("system_disk_size", disk.Size)
// In Classic network, internet_charge_type is valid in any case, and its default value is 'PayByBanwidth'. // In Classic network, internet_charge_type is valid in any case, and its default value is 'PayByBanwidth'.
// In VPC network, internet_charge_type is valid when instance has public ip, and its default value is 'PayByBanwidth'. // In VPC network, internet_charge_type is valid when instance has public ip, and its default value is 'PayByBanwidth'.
@ -244,10 +309,6 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("io_optimized", "none") d.Set("io_optimized", "none")
} }
log.Printf("instance.InternetChargeType: %#v", instance.InternetChargeType)
d.Set("instance_network_type", instance.InstanceNetworkType)
if d.Get("subnet_id").(string) != "" || d.Get("vswitch_id").(string) != "" { if d.Get("subnet_id").(string) != "" || d.Get("vswitch_id").(string) != "" {
ipAddress := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] ipAddress := instance.VpcAttributes.PrivateIpAddress.IpAddress[0]
d.Set("private_ip", ipAddress) d.Set("private_ip", ipAddress)
@ -414,6 +475,30 @@ func resourceAliyunInstanceDelete(d *schema.ResourceData, meta interface{}) erro
return nil return nil
} }
func buildAliyunRunInstancesArgs(d *schema.ResourceData, meta interface{}) (*ecs.RunInstanceArgs, error) {
args := &ecs.RunInstanceArgs{
MaxAmount: DEFAULT_INSTANCE_COUNT,
MinAmount: DEFAULT_INSTANCE_COUNT,
}
bussStr, err := json.Marshal(DefaultBusinessInfo)
if err != nil {
log.Printf("Failed to translate bussiness info %#v from json to string", DefaultBusinessInfo)
}
args.BusinessInfo = string(bussStr)
subnetValue := d.Get("subnet_id").(string)
vswitchValue := d.Get("vswitch_id").(string)
//networkValue := d.Get("instance_network_type").(string)
// because runInstance is not compatible with createInstance, force NetworkType value to classic
if subnetValue == "" && vswitchValue == "" {
args.NetworkType = string(ClassicNet)
}
return args, nil
}
func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateInstanceArgs, error) { func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateInstanceArgs, error) {
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
@ -421,15 +506,18 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre
args := &ecs.CreateInstanceArgs{ args := &ecs.CreateInstanceArgs{
RegionId: getRegion(d, meta), RegionId: getRegion(d, meta),
InstanceType: d.Get("instance_type").(string), InstanceType: d.Get("instance_type").(string),
PrivateIpAddress: d.Get("private_ip").(string),
} }
imageID := d.Get("image_id").(string) imageID := d.Get("image_id").(string)
args.ImageId = imageID args.ImageId = imageID
zoneID := d.Get("availability_zone").(string) systemDiskCategory := ecs.DiskCategory(d.Get("system_disk_category").(string))
systemDiskSize := d.Get("system_disk_size").(int)
zoneID := d.Get("availability_zone").(string)
// check instanceType and systemDiskCategory, when zoneID is not empty
if zoneID != "" {
zone, err := client.DescribeZone(zoneID) zone, err := client.DescribeZone(zoneID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -439,8 +527,19 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre
return nil, err return nil, err
} }
if err := client.DiskAvailable(zone, systemDiskCategory); err != nil {
return nil, err
}
args.ZoneId = zoneID args.ZoneId = zoneID
}
args.SystemDisk = ecs.SystemDiskType{
Category: systemDiskCategory,
Size: systemDiskSize,
}
sgs, ok := d.GetOk("security_groups") sgs, ok := d.GetOk("security_groups")
if ok { if ok {
@ -451,17 +550,6 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre
if err == nil { if err == nil {
args.SecurityGroupId = sg0 args.SecurityGroupId = sg0
} }
}
systemDiskCategory := ecs.DiskCategory(d.Get("system_disk_category").(string))
if err := client.DiskAvailable(zone, systemDiskCategory); err != nil {
return nil, err
}
args.SystemDisk = ecs.SystemDiskType{
Category: systemDiskCategory,
} }
if v := d.Get("instance_name").(string); v != "" { if v := d.Get("instance_name").(string); v != "" {
@ -472,7 +560,7 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre
args.Description = v args.Description = v
} }
log.Printf("[DEBUG] internet_charge_type is %s", d.Get("internet_charge_type").(string)) log.Printf("[DEBUG] SystemDisk is %d", systemDiskSize)
if v := d.Get("internet_charge_type").(string); v != "" { if v := d.Get("internet_charge_type").(string); v != "" {
args.InternetChargeType = common.InternetChargeType(v) args.InternetChargeType = common.InternetChargeType(v)
} }
@ -490,7 +578,11 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre
} }
if v := d.Get("io_optimized").(string); v != "" { if v := d.Get("io_optimized").(string); v != "" {
args.IoOptimized = ecs.IoOptimized(v) if v == "optimized" {
args.IoOptimized = ecs.IoOptimized("true")
} else {
args.IoOptimized = ecs.IoOptimized("false")
}
} }
vswitchValue := d.Get("subnet_id").(string) vswitchValue := d.Get("subnet_id").(string)

View File

@ -56,6 +56,7 @@ func TestAccAlicloudInstance_basic(t *testing.T) {
"alicloud_instance.foo", "alicloud_instance.foo",
"internet_charge_type", "internet_charge_type",
"PayByBandwidth"), "PayByBandwidth"),
testAccCheckSystemDiskSize("alicloud_instance.foo", 80),
), ),
}, },
@ -355,10 +356,6 @@ func TestAccAlicloudInstance_tags(t *testing.T) {
Config: testAccCheckInstanceConfigTagsUpdate, Config: testAccCheckInstanceConfigTagsUpdate,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("alicloud_instance.foo", &instance), testAccCheckInstanceExists("alicloud_instance.foo", &instance),
resource.TestCheckResourceAttr(
"alicloud_instance.foo",
"tags.foo",
""),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"alicloud_instance.foo", "alicloud_instance.foo",
"tags.bar", "tags.bar",
@ -418,8 +415,8 @@ func TestAccAlicloudInstance_privateIP(t *testing.T) {
testCheckPrivateIP := func() resource.TestCheckFunc { testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0]
if privateIP != "172.16.0.229" { if privateIP == "" {
return fmt.Errorf("bad private IP: %s", privateIP) return fmt.Errorf("can't get private IP")
} }
return nil return nil
@ -445,14 +442,14 @@ func TestAccAlicloudInstance_privateIP(t *testing.T) {
}) })
} }
func TestAccAlicloudInstance_associatePublicIPAndPrivateIP(t *testing.T) { func TestAccAlicloudInstance_associatePublicIP(t *testing.T) {
var instance ecs.InstanceAttributesType var instance ecs.InstanceAttributesType
testCheckPrivateIP := func() resource.TestCheckFunc { testCheckPrivateIP := func() resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0]
if privateIP != "172.16.0.229" { if privateIP == "" {
return fmt.Errorf("bad private IP: %s", privateIP) return fmt.Errorf("can't get private IP")
} }
return nil return nil
@ -468,7 +465,7 @@ func TestAccAlicloudInstance_associatePublicIPAndPrivateIP(t *testing.T) {
CheckDestroy: testAccCheckInstanceDestroy, CheckDestroy: testAccCheckInstanceDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccInstanceConfigAssociatePublicIPAndPrivateIP, Config: testAccInstanceConfigAssociatePublicIP,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists("alicloud_instance.foo", &instance), testAccCheckInstanceExists("alicloud_instance.foo", &instance),
testCheckPrivateIP(), testCheckPrivateIP(),
@ -597,6 +594,36 @@ func testAccCheckInstanceDestroyWithProvider(s *terraform.State, provider *schem
return nil return nil
} }
func testAccCheckSystemDiskSize(n string, size int) resource.TestCheckFunc {
return func(s *terraform.State) error {
providers := []*schema.Provider{testAccProvider}
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
for _, provider := range providers {
if provider.Meta() == nil {
continue
}
client := provider.Meta().(*AliyunClient)
systemDisk, err := client.QueryInstanceSystemDisk(rs.Primary.ID)
if err != nil {
log.Printf("[ERROR]get system disk size error: %#v", err)
return err
}
if systemDisk.Size != size {
return fmt.Errorf("system disk size not equal %d, the instance system size is %d",
size, systemDisk.Size)
}
}
return nil
}
}
const testAccInstanceConfig = ` const testAccInstanceConfig = `
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
name = "tf_test_foo" name = "tf_test_foo"
@ -609,11 +636,10 @@ resource "alicloud_security_group" "tf_test_bar" {
} }
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
system_disk_category = "cloud_ssd" system_disk_category = "cloud_ssd"
system_disk_size = 80
instance_type = "ecs.n1.small" instance_type = "ecs.n1.small"
internet_charge_type = "PayByBandwidth" internet_charge_type = "PayByBandwidth"
@ -628,6 +654,11 @@ resource "alicloud_instance" "foo" {
} }
` `
const testAccInstanceConfigVPC = ` const testAccInstanceConfigVPC = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -636,7 +667,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -647,7 +678,6 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
@ -666,6 +696,11 @@ resource "alicloud_instance" "foo" {
` `
const testAccInstanceConfigUserData = ` const testAccInstanceConfigUserData = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -674,7 +709,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -685,7 +720,6 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
# series II # series II
@ -727,7 +761,6 @@ resource "alicloud_security_group" "tf_test_bar" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
provider = "alicloud.beijing" provider = "alicloud.beijing"
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
internet_charge_type = "PayByBandwidth" internet_charge_type = "PayByBandwidth"
@ -742,7 +775,6 @@ resource "alicloud_instance" "foo" {
resource "alicloud_instance" "bar" { resource "alicloud_instance" "bar" {
# cn-shanghai # cn-shanghai
provider = "alicloud.shanghai" provider = "alicloud.shanghai"
availability_zone = "cn-shanghai-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
internet_charge_type = "PayByBandwidth" internet_charge_type = "PayByBandwidth"
@ -768,7 +800,6 @@ resource "alicloud_security_group" "tf_test_bar" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
instance_type = "ecs.s2.large" instance_type = "ecs.s2.large"
@ -776,6 +807,7 @@ resource "alicloud_instance" "foo" {
security_groups = ["${alicloud_security_group.tf_test_foo.id}", "${alicloud_security_group.tf_test_bar.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}", "${alicloud_security_group.tf_test_bar.id}"]
instance_name = "test_foo" instance_name = "test_foo"
io_optimized = "optimized" io_optimized = "optimized"
system_disk_category = "cloud_efficiency"
}` }`
const testAccInstanceConfig_multiSecurityGroup_add = ` const testAccInstanceConfig_multiSecurityGroup_add = `
@ -796,7 +828,6 @@ resource "alicloud_security_group" "tf_test_add_sg" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
instance_type = "ecs.s2.large" instance_type = "ecs.s2.large"
@ -805,6 +836,7 @@ resource "alicloud_instance" "foo" {
"${alicloud_security_group.tf_test_add_sg.id}"] "${alicloud_security_group.tf_test_add_sg.id}"]
instance_name = "test_foo" instance_name = "test_foo"
io_optimized = "optimized" io_optimized = "optimized"
system_disk_category = "cloud_efficiency"
} }
` `
@ -814,9 +846,30 @@ resource "alicloud_security_group" "tf_test_foo" {
description = "foo" description = "foo"
} }
resource "alicloud_security_group_rule" "http-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "ssh-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
instance_type = "ecs.s2.large" instance_type = "ecs.s2.large"
@ -824,6 +877,7 @@ resource "alicloud_instance" "foo" {
security_groups = ["${alicloud_security_group.tf_test_foo.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}"]
instance_name = "test_foo" instance_name = "test_foo"
io_optimized = "optimized" io_optimized = "optimized"
system_disk_category = "cloud_efficiency"
} }
` `
@ -836,18 +890,23 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
instance_type = "ecs.s2.large" instance_type = "ecs.s2.large"
internet_charge_type = "PayByBandwidth" internet_charge_type = "PayByBandwidth"
security_groups = ["${alicloud_security_group.tf_test_foo.*.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.*.id}"]
instance_name = "test_foo" instance_name = "test_foo"
io_optimized = "none" io_optimized = "optimized"
system_disk_category = "cloud_efficiency"
} }
` `
const testAccInstanceNetworkInstanceSecurityGroups = ` const testAccInstanceNetworkInstanceSecurityGroups = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -856,7 +915,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -867,7 +926,6 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
@ -892,7 +950,6 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
# series II # series II
@ -918,7 +975,6 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
# series II # series II
@ -941,9 +997,30 @@ resource "alicloud_security_group" "tf_test_foo" {
description = "foo" description = "foo"
} }
resource "alicloud_security_group_rule" "http-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "ssh-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
# series II # series II
@ -965,9 +1042,30 @@ resource "alicloud_security_group" "tf_test_foo" {
description = "foo" description = "foo"
} }
resource "alicloud_security_group_rule" "http-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "ssh-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = "${alicloud_security_group.tf_test_foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd"
# series II # series II
@ -984,6 +1082,11 @@ resource "alicloud_instance" "foo" {
` `
const testAccInstanceConfigPrivateIP = ` const testAccInstanceConfigPrivateIP = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -992,7 +1095,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/24" cidr_block = "172.16.0.0/24"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -1003,11 +1106,9 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
security_groups = ["${alicloud_security_group.tf_test_foo.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}"]
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"
private_ip = "172.16.0.229"
# series II # series II
instance_type = "ecs.n1.medium" instance_type = "ecs.n1.medium"
@ -1017,7 +1118,12 @@ resource "alicloud_instance" "foo" {
instance_name = "test_foo" instance_name = "test_foo"
} }
` `
const testAccInstanceConfigAssociatePublicIPAndPrivateIP = ` const testAccInstanceConfigAssociatePublicIP = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -1026,7 +1132,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/24" cidr_block = "172.16.0.0/24"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -1037,11 +1143,9 @@ resource "alicloud_security_group" "tf_test_foo" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
security_groups = ["${alicloud_security_group.tf_test_foo.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}"]
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"
private_ip = "172.16.0.229"
allocate_public_ip = "true" allocate_public_ip = "true"
internet_max_bandwidth_out = 5 internet_max_bandwidth_out = 5
internet_charge_type = "PayByBandwidth" internet_charge_type = "PayByBandwidth"
@ -1055,6 +1159,11 @@ resource "alicloud_instance" "foo" {
} }
` `
const testAccVpcInstanceWithSecurityRule = ` const testAccVpcInstanceWithSecurityRule = `
data "alicloud_zones" "default" {
"available_disk_category"= "cloud_efficiency"
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "10.1.0.0/21" cidr_block = "10.1.0.0/21"
@ -1063,7 +1172,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "10.1.1.0/24" cidr_block = "10.1.1.0/24"
availability_zone = "cn-beijing-c" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_security_group" "tf_test_foo" {
@ -1085,7 +1194,6 @@ resource "alicloud_security_group_rule" "ingress" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-c"
security_groups = ["${alicloud_security_group.tf_test_foo.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}"]
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"log" "log"
@ -71,7 +72,7 @@ func resourceAliyunNatGateway() *schema.Resource {
func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) error { func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AliyunClient).vpcconn conn := meta.(*AliyunClient).vpcconn
args := &CreateNatGatewayArgs{ args := &ecs.CreateNatGatewayArgs{
RegionId: getRegion(d, meta), RegionId: getRegion(d, meta),
VpcId: d.Get("vpc_id").(string), VpcId: d.Get("vpc_id").(string),
Spec: d.Get("spec").(string), Spec: d.Get("spec").(string),
@ -79,11 +80,11 @@ func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) er
bandwidthPackages := d.Get("bandwidth_packages").([]interface{}) bandwidthPackages := d.Get("bandwidth_packages").([]interface{})
bandwidthPackageTypes := []BandwidthPackageType{} bandwidthPackageTypes := []ecs.BandwidthPackageType{}
for _, e := range bandwidthPackages { for _, e := range bandwidthPackages {
pack := e.(map[string]interface{}) pack := e.(map[string]interface{})
bandwidthPackage := BandwidthPackageType{ bandwidthPackage := ecs.BandwidthPackageType{
IpCount: pack["ip_count"].(int), IpCount: pack["ip_count"].(int),
Bandwidth: pack["bandwidth"].(int), Bandwidth: pack["bandwidth"].(int),
} }
@ -106,8 +107,7 @@ func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) er
if v, ok := d.GetOk("description"); ok { if v, ok := d.GetOk("description"); ok {
args.Description = v.(string) args.Description = v.(string)
} }
resp, err := conn.CreateNatGateway(args)
resp, err := CreateNatGateway(conn, args)
if err != nil { if err != nil {
return fmt.Errorf("CreateNatGateway got error: %#v", err) return fmt.Errorf("CreateNatGateway got error: %#v", err)
} }
@ -142,6 +142,7 @@ func resourceAliyunNatGatewayRead(d *schema.ResourceData, meta interface{}) erro
func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
conn := client.vpcconn
natGateway, err := client.DescribeNatGateway(d.Id()) natGateway, err := client.DescribeNatGateway(d.Id())
if err != nil { if err != nil {
@ -150,7 +151,7 @@ func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) er
d.Partial(true) d.Partial(true)
attributeUpdate := false attributeUpdate := false
args := &ModifyNatGatewayAttributeArgs{ args := &ecs.ModifyNatGatewayAttributeArgs{
RegionId: natGateway.RegionId, RegionId: natGateway.RegionId,
NatGatewayId: natGateway.NatGatewayId, NatGatewayId: natGateway.NatGatewayId,
} }
@ -183,28 +184,28 @@ func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) er
} }
if attributeUpdate { if attributeUpdate {
if err := ModifyNatGatewayAttribute(client.vpcconn, args); err != nil { if err := conn.ModifyNatGatewayAttribute(args); err != nil {
return err return err
} }
} }
if d.HasChange("spec") { if d.HasChange("spec") {
d.SetPartial("spec") d.SetPartial("spec")
var spec NatGatewaySpec var spec ecs.NatGatewaySpec
if v, ok := d.GetOk("spec"); ok { if v, ok := d.GetOk("spec"); ok {
spec = NatGatewaySpec(v.(string)) spec = ecs.NatGatewaySpec(v.(string))
} else { } else {
// set default to small spec // set default to small spec
spec = NatGatewaySmallSpec spec = ecs.NatGatewaySmallSpec
} }
args := &ModifyNatGatewaySpecArgs{ args := &ecs.ModifyNatGatewaySpecArgs{
RegionId: natGateway.RegionId, RegionId: natGateway.RegionId,
NatGatewayId: natGateway.NatGatewayId, NatGatewayId: natGateway.NatGatewayId,
Spec: spec, Spec: spec,
} }
err := ModifyNatGatewaySpec(client.vpcconn, args) err := conn.ModifyNatGatewaySpec(args)
if err != nil { if err != nil {
return fmt.Errorf("%#v %#v", err, *args) return fmt.Errorf("%#v %#v", err, *args)
} }
@ -218,10 +219,11 @@ func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) er
func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) error { func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
conn := client.vpcconn
return resource.Retry(5*time.Minute, func() *resource.RetryError { return resource.Retry(5*time.Minute, func() *resource.RetryError {
packages, err := DescribeBandwidthPackages(client.vpcconn, &DescribeBandwidthPackagesArgs{ packages, err := conn.DescribeBandwidthPackages(&ecs.DescribeBandwidthPackagesArgs{
RegionId: getRegion(d, meta), RegionId: getRegion(d, meta),
NatGatewayId: d.Id(), NatGatewayId: d.Id(),
}) })
@ -232,7 +234,7 @@ func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) er
retry := false retry := false
for _, pack := range packages { for _, pack := range packages {
err = DeleteBandwidthPackage(client.vpcconn, &DeleteBandwidthPackageArgs{ err = conn.DeleteBandwidthPackage(&ecs.DeleteBandwidthPackageArgs{
RegionId: getRegion(d, meta), RegionId: getRegion(d, meta),
BandwidthPackageId: pack.BandwidthPackageId, BandwidthPackageId: pack.BandwidthPackageId,
}) })
@ -251,12 +253,12 @@ func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) er
return resource.RetryableError(fmt.Errorf("Bandwidth package in use - trying again while it is deleted.")) return resource.RetryableError(fmt.Errorf("Bandwidth package in use - trying again while it is deleted."))
} }
args := &DeleteNatGatewayArgs{ args := &ecs.DeleteNatGatewayArgs{
RegionId: client.Region, RegionId: client.Region,
NatGatewayId: d.Id(), NatGatewayId: d.Id(),
} }
err = DeleteNatGateway(client.vpcconn, args) err = conn.DeleteNatGateway(args)
if err != nil { if err != nil {
er, _ := err.(*common.Error) er, _ := err.(*common.Error)
if er.ErrorResponse.Code == DependencyViolationBandwidthPackages { if er.ErrorResponse.Code == DependencyViolationBandwidthPackages {
@ -264,11 +266,11 @@ func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) er
} }
} }
describeArgs := &DescribeNatGatewaysArgs{ describeArgs := &ecs.DescribeNatGatewaysArgs{
RegionId: client.Region, RegionId: client.Region,
NatGatewayId: d.Id(), NatGatewayId: d.Id(),
} }
gw, _, gwErr := DescribeNatGateways(client.vpcconn, describeArgs) gw, _, gwErr := conn.DescribeNatGateways(describeArgs)
if gwErr != nil { if gwErr != nil {
log.Printf("[ERROR] Describe NatGateways failed.") log.Printf("[ERROR] Describe NatGateways failed.")

View File

@ -3,13 +3,14 @@ package alicloud
import ( import (
"fmt" "fmt"
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"testing" "testing"
) )
func TestAccAlicloudNatGateway_basic(t *testing.T) { func TestAccAlicloudNatGateway_basic(t *testing.T) {
var nat NatGatewaySetType var nat ecs.NatGatewaySetType
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
if nat.BusinessStatus != "Normal" { if nat.BusinessStatus != "Normal" {
@ -55,7 +56,7 @@ func TestAccAlicloudNatGateway_basic(t *testing.T) {
} }
func TestAccAlicloudNatGateway_spec(t *testing.T) { func TestAccAlicloudNatGateway_spec(t *testing.T) {
var nat NatGatewaySetType var nat ecs.NatGatewaySetType
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { PreCheck: func() {
@ -95,7 +96,7 @@ func TestAccAlicloudNatGateway_spec(t *testing.T) {
} }
func testAccCheckNatGatewayExists(n string, nat *NatGatewaySetType) resource.TestCheckFunc { func testAccCheckNatGatewayExists(n string, nat *ecs.NatGatewaySetType) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -151,6 +152,10 @@ func testAccCheckNatGatewayDestroy(s *terraform.State) error {
} }
const testAccNatGatewayConfig = ` const testAccNatGatewayConfig = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -159,7 +164,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_nat_gateway" "foo" { resource "alicloud_nat_gateway" "foo" {
@ -169,11 +174,11 @@ resource "alicloud_nat_gateway" "foo" {
bandwidth_packages = [{ bandwidth_packages = [{
ip_count = 1 ip_count = 1
bandwidth = 5 bandwidth = 5
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}, { }, {
ip_count = 2 ip_count = 2
bandwidth = 10 bandwidth = 10
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}] }]
depends_on = [ depends_on = [
"alicloud_vswitch.foo"] "alicloud_vswitch.foo"]
@ -181,6 +186,10 @@ resource "alicloud_nat_gateway" "foo" {
` `
const testAccNatGatewayConfigSpec = ` const testAccNatGatewayConfigSpec = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -189,7 +198,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_nat_gateway" "foo" { resource "alicloud_nat_gateway" "foo" {
@ -199,11 +208,11 @@ resource "alicloud_nat_gateway" "foo" {
bandwidth_packages = [{ bandwidth_packages = [{
ip_count = 1 ip_count = 1
bandwidth = 5 bandwidth = 5
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}, { }, {
ip_count = 2 ip_count = 2
bandwidth = 10 bandwidth = 10
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}] }]
depends_on = [ depends_on = [
"alicloud_vswitch.foo"] "alicloud_vswitch.foo"]
@ -211,6 +220,10 @@ resource "alicloud_nat_gateway" "foo" {
` `
const testAccNatGatewayConfigSpecUpgrade = ` const testAccNatGatewayConfigSpecUpgrade = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -219,7 +232,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_nat_gateway" "foo" { resource "alicloud_nat_gateway" "foo" {
@ -229,11 +242,11 @@ resource "alicloud_nat_gateway" "foo" {
bandwidth_packages = [{ bandwidth_packages = [{
ip_count = 1 ip_count = 1
bandwidth = 5 bandwidth = 5
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}, { }, {
ip_count = 2 ip_count = 2
bandwidth = 10 bandwidth = 10
zone = "cn-beijing-b" zone = "${data.alicloud_zones.default.zones.0.id}"
}] }]
depends_on = [ depends_on = [
"alicloud_vswitch.foo"] "alicloud_vswitch.foo"]

View File

@ -6,7 +6,6 @@ import (
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"time" "time"
) )
@ -145,6 +144,7 @@ func resourceAliyunSecurityGroupDelete(d *schema.ResourceData, meta interface{})
return resource.RetryableError(fmt.Errorf("Security group in use - trying again while it is deleted.")) return resource.RetryableError(fmt.Errorf("Security group in use - trying again while it is deleted."))
}) })
} }
func buildAliyunSecurityGroupArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateSecurityGroupArgs, error) { func buildAliyunSecurityGroupArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateSecurityGroupArgs, error) {

View File

@ -34,6 +34,7 @@ func resourceAliyunSecurityGroupRule() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true,
ValidateFunc: validateSecurityRuleNicType, ValidateFunc: validateSecurityRuleNicType,
}, },
@ -67,7 +68,6 @@ func resourceAliyunSecurityGroupRule() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Default: "0.0.0.0/0",
}, },
"source_security_group_id": &schema.Schema{ "source_security_group_id": &schema.Schema{
@ -86,15 +86,17 @@ func resourceAliyunSecurityGroupRule() *schema.Resource {
} }
func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AliyunClient).ecsconn client := meta.(*AliyunClient)
conn := client.ecsconn
ruleType := d.Get("type").(string) direction := d.Get("type").(string)
sgId := d.Get("security_group_id").(string) sgId := d.Get("security_group_id").(string)
ptl := d.Get("ip_protocol").(string) ptl := d.Get("ip_protocol").(string)
port := d.Get("port_range").(string) port := d.Get("port_range").(string)
nicType := d.Get("nic_type").(string)
var autherr error var autherr error
switch GroupRuleDirection(ruleType) { switch GroupRuleDirection(direction) {
case GroupRuleIngress: case GroupRuleIngress:
args, err := buildAliyunSecurityIngressArgs(d, meta) args, err := buildAliyunSecurityIngressArgs(d, meta)
if err != nil { if err != nil {
@ -114,10 +116,11 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac
if autherr != nil { if autherr != nil {
return fmt.Errorf( return fmt.Errorf(
"Error authorizing security group rule type %s: %s", "Error authorizing security group rule type %s: %s",
ruleType, autherr) direction, autherr)
} }
d.SetId(sgId + ":" + ruleType + ":" + ptl + ":" + port) d.SetId(sgId + ":" + direction + ":" + ptl + ":" + port + ":" + nicType)
return resourceAliyunSecurityGroupRuleRead(d, meta) return resourceAliyunSecurityGroupRuleRead(d, meta)
} }
@ -125,10 +128,11 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
parts := strings.Split(d.Id(), ":") parts := strings.Split(d.Id(), ":")
sgId := parts[0] sgId := parts[0]
types := parts[1] direction := parts[1]
ip_protocol := parts[2] ip_protocol := parts[2]
port_range := parts[3] port_range := parts[3]
rule, err := client.DescribeSecurityGroupRule(sgId, types, ip_protocol, port_range) nic_type := parts[4]
rule, err := client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range)
if err != nil { if err != nil {
if notFoundError(err) { if notFoundError(err) {
@ -137,7 +141,7 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{
} }
return fmt.Errorf("Error SecurityGroup rule: %#v", err) return fmt.Errorf("Error SecurityGroup rule: %#v", err)
} }
log.Printf("[WARN]sg %s, type %s, protocol %s, port %s, rule %#v", sgId, types, ip_protocol, port_range, rule) log.Printf("[WARN]sg %s, type %s, protocol %s, port %s, rule %#v", sgId, direction, ip_protocol, port_range, rule)
d.Set("type", rule.Direction) d.Set("type", rule.Direction)
d.Set("ip_protocol", strings.ToLower(string(rule.IpProtocol))) d.Set("ip_protocol", strings.ToLower(string(rule.IpProtocol)))
d.Set("nic_type", rule.NicType) d.Set("nic_type", rule.NicType)
@ -146,7 +150,7 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{
d.Set("priority", rule.Priority) d.Set("priority", rule.Priority)
d.Set("security_group_id", sgId) d.Set("security_group_id", sgId)
//support source and desc by type //support source and desc by type
if GroupRuleDirection(types) == GroupRuleIngress { if GroupRuleDirection(direction) == GroupRuleIngress {
d.Set("cidr_ip", rule.SourceCidrIp) d.Set("cidr_ip", rule.SourceCidrIp)
d.Set("source_security_group_id", rule.SourceGroupId) d.Set("source_security_group_id", rule.SourceGroupId)
d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount) d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount)
@ -161,8 +165,10 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{
func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AliyunClient) client := meta.(*AliyunClient)
args, err := buildAliyunSecurityIngressArgs(d, meta) ruleType := d.Get("type").(string)
if GroupRuleDirection(ruleType) == GroupRuleIngress {
args, err := buildAliyunSecurityIngressArgs(d, meta)
if err != nil { if err != nil {
return err return err
} }
@ -170,8 +176,30 @@ func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interfac
AuthorizeSecurityGroupArgs: *args, AuthorizeSecurityGroupArgs: *args,
} }
return client.RevokeSecurityGroup(revokeArgs) return client.RevokeSecurityGroup(revokeArgs)
}
args, err := buildAliyunSecurityEgressArgs(d, meta)
if err != nil {
return err
}
revokeArgs := &ecs.RevokeSecurityGroupEgressArgs{
AuthorizeSecurityGroupEgressArgs: *args,
}
return client.RevokeSecurityGroupEgress(revokeArgs)
} }
func checkCidrAndSourceGroupId(cidrIp, sourceGroupId string) error {
if cidrIp == "" && sourceGroupId == "" {
return fmt.Errorf("Either cidr_ip or source_security_group_id is required.")
}
if cidrIp != "" && sourceGroupId != "" {
return fmt.Errorf("You should set only one value of cidr_ip or source_security_group_id.")
}
return nil
}
func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupArgs, error) { func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupArgs, error) {
conn := meta.(*AliyunClient).ecsconn conn := meta.(*AliyunClient).ecsconn
@ -199,12 +227,17 @@ func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (*
args.NicType = ecs.NicType(v) args.NicType = ecs.NicType(v)
} }
if v := d.Get("cidr_ip").(string); v != "" { cidrIp := d.Get("cidr_ip").(string)
args.SourceCidrIp = v sourceGroupId := d.Get("source_security_group_id").(string)
if err := checkCidrAndSourceGroupId(cidrIp, sourceGroupId); err != nil {
return nil, err
}
if cidrIp != "" {
args.SourceCidrIp = cidrIp
} }
if v := d.Get("source_security_group_id").(string); v != "" { if sourceGroupId != "" {
args.SourceGroupId = v args.SourceGroupId = sourceGroupId
} }
if v := d.Get("source_group_owner_account").(string); v != "" { if v := d.Get("source_group_owner_account").(string); v != "" {
@ -255,12 +288,17 @@ func buildAliyunSecurityEgressArgs(d *schema.ResourceData, meta interface{}) (*e
args.NicType = ecs.NicType(v) args.NicType = ecs.NicType(v)
} }
if v := d.Get("cidr_ip").(string); v != "" { cidrIp := d.Get("cidr_ip").(string)
args.DestCidrIp = v sourceGroupId := d.Get("source_security_group_id").(string)
if err := checkCidrAndSourceGroupId(cidrIp, sourceGroupId); err != nil {
return nil, err
}
if cidrIp != "" {
args.DestCidrIp = cidrIp
} }
if v := d.Get("source_security_group_id").(string); v != "" { if sourceGroupId != "" {
args.DestGroupId = v args.DestGroupId = sourceGroupId
} }
if v := d.Get("source_group_owner_account").(string); v != "" { if v := d.Get("source_group_owner_account").(string); v != "" {

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"log" "log"
"regexp"
"strings" "strings"
"testing" "testing"
) )
@ -81,6 +82,39 @@ func TestAccAlicloudSecurityGroupRule_Egress(t *testing.T) {
} }
func TestAccAlicloudSecurityGroupRule_EgressDefaultNicType(t *testing.T) {
var pt ecs.PermissionType
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_security_group_rule.egress",
Providers: testAccProviders,
CheckDestroy: testAccCheckSecurityGroupRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccSecurityGroupRuleEgress_emptyNicType,
Check: resource.ComposeTestCheckFunc(
testAccCheckSecurityGroupRuleExists(
"alicloud_security_group_rule.egress", &pt),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.egress",
"port_range",
"80/80"),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.egress",
"nic_type",
"internet"),
),
},
},
})
}
func TestAccAlicloudSecurityGroupRule_Vpc_Ingress(t *testing.T) { func TestAccAlicloudSecurityGroupRule_Vpc_Ingress(t *testing.T) {
var pt ecs.PermissionType var pt ecs.PermissionType
@ -114,6 +148,80 @@ func TestAccAlicloudSecurityGroupRule_Vpc_Ingress(t *testing.T) {
} }
func TestAccAlicloudSecurityGroupRule_MissParameterSourceCidrIp(t *testing.T) {
var pt ecs.PermissionType
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_security_group_rule.egress",
Providers: testAccProviders,
CheckDestroy: testAccCheckSecurityGroupRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccSecurityGroupRule_missingSourceCidrIp,
Check: resource.ComposeTestCheckFunc(
testAccCheckSecurityGroupRuleExists(
"alicloud_security_group_rule.egress", &pt),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.egress",
"port_range",
"80/80"),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.egress",
"nic_type",
"internet"),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.egress",
"ip_protocol",
"udp"),
),
},
},
})
}
func TestAccAlicloudSecurityGroupRule_SourceSecurityGroup(t *testing.T) {
var pt ecs.PermissionType
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
// module name
IDRefreshName: "alicloud_security_group_rule.ingress",
Providers: testAccProviders,
CheckDestroy: testAccCheckSecurityGroupRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccSecurityGroupRuleSourceSecurityGroup,
Check: resource.ComposeTestCheckFunc(
testAccCheckSecurityGroupRuleExists(
"alicloud_security_group_rule.ingress", &pt),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.ingress",
"port_range",
"3306/3306"),
resource.TestMatchResourceAttr(
"alicloud_security_group_rule.ingress",
"source_security_group_id",
regexp.MustCompile("^sg-[a-zA-Z0-9_]+")),
resource.TestCheckResourceAttr(
"alicloud_security_group_rule.ingress",
"cidr_ip",
""),
),
},
},
})
}
func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resource.TestCheckFunc { func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -128,7 +236,8 @@ func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resour
client := testAccProvider.Meta().(*AliyunClient) client := testAccProvider.Meta().(*AliyunClient)
log.Printf("[WARN]get sg rule %s", rs.Primary.ID) log.Printf("[WARN]get sg rule %s", rs.Primary.ID)
parts := strings.Split(rs.Primary.ID, ":") parts := strings.Split(rs.Primary.ID, ":")
rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[2], parts[3]) // securityGroupId, direction, nicType, ipProtocol, portRange
rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[4], parts[2], parts[3])
if err != nil { if err != nil {
return err return err
@ -152,7 +261,7 @@ func testAccCheckSecurityGroupRuleDestroy(s *terraform.State) error {
} }
parts := strings.Split(rs.Primary.ID, ":") parts := strings.Split(rs.Primary.ID, ":")
rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[2], parts[3]) rule, err := client.DescribeSecurityGroupRule(parts[0], parts[1], parts[4], parts[2], parts[3])
if rule != nil { if rule != nil {
return fmt.Errorf("Error SecurityGroup Rule still exist") return fmt.Errorf("Error SecurityGroup Rule still exist")
@ -210,6 +319,23 @@ resource "alicloud_security_group_rule" "egress" {
` `
const testAccSecurityGroupRuleEgress_emptyNicType = `
resource "alicloud_security_group" "foo" {
name = "sg_foo"
}
resource "alicloud_security_group_rule" "egress" {
type = "egress"
ip_protocol = "udp"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.foo.id}"
cidr_ip = "10.159.6.18/12"
}
`
const testAccSecurityGroupRuleVpcIngress = ` const testAccSecurityGroupRuleVpcIngress = `
resource "alicloud_security_group" "foo" { resource "alicloud_security_group" "foo" {
vpc_id = "${alicloud_vpc.vpc.id}" vpc_id = "${alicloud_vpc.vpc.id}"
@ -231,6 +357,22 @@ resource "alicloud_security_group_rule" "ingress" {
cidr_ip = "10.159.6.18/12" cidr_ip = "10.159.6.18/12"
} }
`
const testAccSecurityGroupRule_missingSourceCidrIp = `
resource "alicloud_security_group" "foo" {
name = "sg_foo"
}
resource "alicloud_security_group_rule" "egress" {
security_group_id = "${alicloud_security_group.foo.id}"
type = "egress"
cidr_ip= "0.0.0.0/0"
policy = "accept"
ip_protocol= "udp"
port_range= "80/80"
priority= 1
}
` `
const testAccSecurityGroupRuleMultiIngress = ` const testAccSecurityGroupRuleMultiIngress = `
@ -260,4 +402,27 @@ resource "alicloud_security_group_rule" "ingress2" {
cidr_ip = "127.0.1.18/16" cidr_ip = "127.0.1.18/16"
} }
`
const testAccSecurityGroupRuleSourceSecurityGroup = `
resource "alicloud_security_group" "foo" {
name = "sg_foo"
}
resource "alicloud_security_group" "bar" {
name = "sg_bar"
}
resource "alicloud_security_group_rule" "ingress" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "3306/3306"
priority = 50
security_group_id = "${alicloud_security_group.bar.id}"
source_security_group_id = "${alicloud_security_group.foo.id}"
}
` `

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"errors"
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/slb" "github.com/denverdino/aliyungo/slb"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
@ -83,40 +84,124 @@ func resourceAliyunSlb() *schema.Resource {
ValidateFunc: validateSlbListenerBandwidth, ValidateFunc: validateSlbListenerBandwidth,
Required: true, Required: true,
}, },
//http
"scheduler": &schema.Schema{ "scheduler": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validateSlbListenerScheduler, ValidateFunc: validateSlbListenerScheduler,
Optional: true, Optional: true,
Default: "wrr", Default: slb.WRRScheduler,
}, },
//http & https
"sticky_session": &schema.Schema{ "sticky_session": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validateSlbListenerStickySession, ValidateFunc: validateAllowedStringValue([]string{
string(slb.OnFlag),
string(slb.OffFlag)}),
Optional: true, Optional: true,
Default: slb.OffFlag,
}, },
//http & https
"sticky_session_type": &schema.Schema{ "sticky_session_type": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validateSlbListenerStickySessionType, ValidateFunc: validateAllowedStringValue([]string{
string(slb.InsertStickySessionType),
string(slb.ServerStickySessionType)}),
Optional: true, Optional: true,
}, },
//http & https
"cookie_timeout": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateSlbListenerCookieTimeout,
Optional: true,
},
//http & https
"cookie": &schema.Schema{ "cookie": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
ValidateFunc: validateSlbListenerCookie, ValidateFunc: validateSlbListenerCookie,
Optional: true, Optional: true,
}, },
"PersistenceTimeout": &schema.Schema{ //tcp & udp
"persistence_timeout": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
ValidateFunc: validateSlbListenerPersistenceTimeout, ValidateFunc: validateSlbListenerPersistenceTimeout,
Optional: true, Optional: true,
Default: 0, Default: 0,
}, },
//http & https
"health_check": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{
string(slb.OnFlag),
string(slb.OffFlag)}),
Optional: true,
Default: slb.OffFlag,
},
//tcp
"health_check_type": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedStringValue([]string{
string(slb.TCPHealthCheckType),
string(slb.HTTPHealthCheckType)}),
Optional: true,
Default: slb.TCPHealthCheckType,
},
//http & https & tcp
"health_check_domain": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateSlbListenerHealthCheckDomain,
Optional: true,
},
//http & https & tcp
"health_check_uri": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateSlbListenerHealthCheckUri,
Optional: true,
},
"health_check_connect_port": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateSlbListenerHealthCheckConnectPort,
Optional: true,
},
"healthy_threshold": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateIntegerInRange(1, 10),
Optional: true,
},
"unhealthy_threshold": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateIntegerInRange(1, 10),
Optional: true,
},
"health_check_timeout": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateIntegerInRange(1, 50),
Optional: true,
},
"health_check_interval": &schema.Schema{
Type: schema.TypeInt,
ValidateFunc: validateIntegerInRange(1, 5),
Optional: true,
},
//http & https & tcp
"health_check_http_code": &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateAllowedSplitStringValue([]string{
string(slb.HTTP_2XX),
string(slb.HTTP_3XX),
string(slb.HTTP_4XX),
string(slb.HTTP_5XX)}, ","),
Optional: true,
},
//https //https
"ssl_certificate_id": &schema.Schema{ "ssl_certificate_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
}, },
//https
//"ca_certificate_id": &schema.Schema{
// Type: schema.TypeString,
// Optional: true,
//},
}, },
}, },
Set: resourceAliyunSlbListenerHash, Set: resourceAliyunSlbListenerHash,
@ -349,44 +434,53 @@ func resourceAliyunSlbListenerHash(v interface{}) int {
} }
func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) error { func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) error {
errTypeJudge := func(err error) error {
if err != nil {
if listenerType, ok := err.(*ListenerErr); ok {
if listenerType.ErrType == HealthCheckErrType {
return fmt.Errorf("When the HealthCheck is %s, then related HealthCheck parameter "+
"must have.", slb.OnFlag)
} else if listenerType.ErrType == StickySessionErrType {
return fmt.Errorf("When the StickySession is %s, then StickySessionType parameter "+
"must have.", slb.OnFlag)
} else if listenerType.ErrType == CookieTimeOutErrType {
return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+
"then CookieTimeout parameter must have.", slb.OnFlag, slb.InsertStickySessionType)
} else if listenerType.ErrType == CookieErrType {
return fmt.Errorf("When the StickySession is %s and StickySessionType is %s, "+
"then Cookie parameter must have.", slb.OnFlag, slb.ServerStickySessionType)
}
return fmt.Errorf("slb listener check errtype not found.")
}
}
return nil
}
if listener.Protocol == strings.ToLower("tcp") { if listener.Protocol == strings.ToLower("tcp") {
args := &slb.CreateLoadBalancerTCPListenerArgs{
LoadBalancerId: loadBalancerId, args := getTcpListenerArgs(loadBalancerId, listener)
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort, if err := conn.CreateLoadBalancerTCPListener(&args); err != nil {
Bandwidth: listener.Bandwidth,
}
if err := conn.CreateLoadBalancerTCPListener(args); err != nil {
return err return err
} }
} else if listener.Protocol == strings.ToLower("http") {
args, argsErr := getHttpListenerArgs(loadBalancerId, listener)
if paramErr := errTypeJudge(argsErr); paramErr != nil {
return paramErr
} }
if listener.Protocol == strings.ToLower("http") { if err := conn.CreateLoadBalancerHTTPListener(&args); err != nil {
args := &slb.CreateLoadBalancerHTTPListenerArgs{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
StickySession: slb.OffFlag,
HealthCheck: slb.OffFlag,
}
if err := conn.CreateLoadBalancerHTTPListener(args); err != nil {
return err return err
} }
} else if listener.Protocol == strings.ToLower("https") {
listenerType, err := getHttpListenerType(loadBalancerId, listener)
if paramErr := errTypeJudge(err); paramErr != nil {
return paramErr
} }
if listener.Protocol == strings.ToLower("https") {
args := &slb.CreateLoadBalancerHTTPSListenerArgs{ args := &slb.CreateLoadBalancerHTTPSListenerArgs{
HTTPListenerType: listenerType,
HTTPListenerType: slb.HTTPListenerType{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
StickySession: slb.OffFlag,
HealthCheck: slb.OffFlag,
},
} }
if listener.SSLCertificateId == "" { if listener.SSLCertificateId == "" {
return fmt.Errorf("Server Certificated Id cann't be null") return fmt.Errorf("Server Certificated Id cann't be null")
@ -397,17 +491,10 @@ func createListener(conn *slb.Client, loadBalancerId string, listener *Listener)
if err := conn.CreateLoadBalancerHTTPSListener(args); err != nil { if err := conn.CreateLoadBalancerHTTPSListener(args); err != nil {
return err return err
} }
} } else if listener.Protocol == strings.ToLower("udp") {
args := getUdpListenerArgs(loadBalancerId, listener)
if listener.Protocol == strings.ToLower("udp") { if err := conn.CreateLoadBalancerUDPListener(&args); err != nil {
args := &slb.CreateLoadBalancerUDPListenerArgs{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
}
if err := conn.CreateLoadBalancerUDPListener(args); err != nil {
return err return err
} }
} }
@ -418,3 +505,102 @@ func createListener(conn *slb.Client, loadBalancerId string, listener *Listener)
return nil return nil
} }
func getTcpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerTCPListenerArgs {
args := slb.CreateLoadBalancerTCPListenerArgs{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
Scheduler: listener.Scheduler,
PersistenceTimeout: listener.PersistenceTimeout,
HealthCheckType: listener.HealthCheckType,
HealthCheckDomain: listener.HealthCheckDomain,
HealthCheckURI: listener.HealthCheckURI,
HealthCheckConnectPort: listener.HealthCheckConnectPort,
HealthyThreshold: listener.HealthyThreshold,
UnhealthyThreshold: listener.UnhealthyThreshold,
HealthCheckConnectTimeout: listener.HealthCheckTimeout,
HealthCheckInterval: listener.HealthCheckInterval,
HealthCheckHttpCode: listener.HealthCheckHttpCode,
}
return args
}
func getUdpListenerArgs(loadBalancerId string, listener *Listener) slb.CreateLoadBalancerUDPListenerArgs {
args := slb.CreateLoadBalancerUDPListenerArgs{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
PersistenceTimeout: listener.PersistenceTimeout,
HealthCheckConnectTimeout: listener.HealthCheckTimeout,
HealthCheckInterval: listener.HealthCheckInterval,
}
return args
}
func getHttpListenerType(loadBalancerId string, listener *Listener) (listenType slb.HTTPListenerType, err error) {
if listener.HealthCheck == slb.OnFlag {
if listener.HealthCheckURI == "" || listener.HealthCheckDomain == "" || listener.HealthCheckConnectPort == 0 ||
listener.HealthyThreshold == 0 || listener.UnhealthyThreshold == 0 || listener.HealthCheckTimeout == 0 ||
listener.HealthCheckHttpCode == "" || listener.HealthCheckInterval == 0 {
errMsg := errors.New("err: HealthCheck empty.")
return listenType, &ListenerErr{HealthCheckErrType, errMsg}
}
}
if listener.StickySession == slb.OnFlag {
if listener.StickySessionType == "" {
errMsg := errors.New("err: stickySession empty.")
return listenType, &ListenerErr{StickySessionErrType, errMsg}
}
if listener.StickySessionType == slb.InsertStickySessionType {
if listener.CookieTimeout == 0 {
errMsg := errors.New("err: cookieTimeout empty.")
return listenType, &ListenerErr{CookieTimeOutErrType, errMsg}
}
} else if listener.StickySessionType == slb.ServerStickySessionType {
if listener.Cookie == "" {
errMsg := errors.New("err: cookie empty.")
return listenType, &ListenerErr{CookieErrType, errMsg}
}
}
}
httpListenertType := slb.HTTPListenerType{
LoadBalancerId: loadBalancerId,
ListenerPort: listener.LoadBalancerPort,
BackendServerPort: listener.InstancePort,
Bandwidth: listener.Bandwidth,
Scheduler: listener.Scheduler,
HealthCheck: listener.HealthCheck,
StickySession: listener.StickySession,
StickySessionType: listener.StickySessionType,
CookieTimeout: listener.CookieTimeout,
Cookie: listener.Cookie,
HealthCheckDomain: listener.HealthCheckDomain,
HealthCheckURI: listener.HealthCheckURI,
HealthCheckConnectPort: listener.HealthCheckConnectPort,
HealthyThreshold: listener.HealthyThreshold,
UnhealthyThreshold: listener.UnhealthyThreshold,
HealthCheckTimeout: listener.HealthCheckTimeout,
HealthCheckInterval: listener.HealthCheckInterval,
HealthCheckHttpCode: listener.HealthCheckHttpCode,
}
return httpListenertType, err
}
func getHttpListenerArgs(loadBalancerId string, listener *Listener) (listenType slb.CreateLoadBalancerHTTPListenerArgs, err error) {
httpListenerType, err := getHttpListenerType(loadBalancerId, listener)
if err != nil {
return listenType, err
}
httpArgs := slb.CreateLoadBalancerHTTPListenerArgs(httpListenerType)
return httpArgs, err
}

View File

@ -79,9 +79,30 @@ resource "alicloud_security_group" "foo" {
description = "foo" description = "foo"
} }
resource "alicloud_security_group_rule" "http-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "ssh-in" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "internet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = "${alicloud_security_group.foo.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-b"
image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd"
# series II # series II

View File

@ -85,7 +85,7 @@ func TestAccAlicloudSlb_listener(t *testing.T) {
testListener := func() resource.TestCheckFunc { testListener := func() resource.TestCheckFunc {
return func(*terraform.State) error { return func(*terraform.State) error {
listenerPorts := slb.ListenerPorts.ListenerPort[0] listenerPorts := slb.ListenerPorts.ListenerPort[0]
if listenerPorts != 161 { if listenerPorts != 2001 {
return fmt.Errorf("bad loadbalancer listener: %#v", listenerPorts) return fmt.Errorf("bad loadbalancer listener: %#v", listenerPorts)
} }
@ -260,21 +260,49 @@ resource "alicloud_slb" "listener" {
"lb_port" = "21" "lb_port" = "21"
"lb_protocol" = "tcp" "lb_protocol" = "tcp"
"bandwidth" = 1 "bandwidth" = 1
"persistence_timeout" = 500
"health_check_type" = "http"
},{ },{
"instance_port" = "8000" "instance_port" = "8000"
"lb_port" = "80" "lb_port" = "80"
"lb_protocol" = "http" "lb_protocol" = "http"
"sticky_session" = "on"
"sticky_session_type" = "insert"
"cookie_timeout" = 800
"bandwidth" = 1 "bandwidth" = 1
},{ },{
"instance_port" = "1611" "instance_port" = "8001"
"lb_port" = "161" "lb_port" = "81"
"lb_protocol" = "http"
"sticky_session" = "on"
"sticky_session_type" = "server"
"cookie" = "testslblistenercookie"
"cookie_timeout" = 1800
"health_check" = "on"
"health_check_domain" = "$_ip"
"health_check_uri" = "/console"
"health_check_connect_port" = 20
"healthy_threshold" = 8
"unhealthy_threshold" = 8
"health_check_timeout" = 8
"health_check_interval" = 4
"health_check_http_code" = "http_2xx"
"bandwidth" = 1
},{
"instance_port" = "2001"
"lb_port" = "2001"
"lb_protocol" = "udp" "lb_protocol" = "udp"
"bandwidth" = 1 "bandwidth" = 1
"persistence_timeout" = 700
}] }]
} }
` `
const testAccSlb4Vpc = ` const testAccSlb4Vpc = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -283,7 +311,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_slb" "vpc" { resource "alicloud_slb" "vpc" {

View File

@ -124,6 +124,10 @@ func testAccCheckRouteEntryDestroy(s *terraform.State) error {
} }
const testAccRouteEntryConfig = ` const testAccRouteEntryConfig = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "10.1.0.0/21" cidr_block = "10.1.0.0/21"
@ -132,7 +136,7 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "10.1.1.0/24" cidr_block = "10.1.1.0/24"
availability_zone = "cn-beijing-c" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
resource "alicloud_route_entry" "foo" { resource "alicloud_route_entry" "foo" {
@ -162,7 +166,6 @@ resource "alicloud_security_group_rule" "ingress" {
resource "alicloud_instance" "foo" { resource "alicloud_instance" "foo" {
# cn-beijing # cn-beijing
availability_zone = "cn-beijing-c"
security_groups = ["${alicloud_security_group.tf_test_foo.id}"] security_groups = ["${alicloud_security_group.tf_test_foo.id}"]
vswitch_id = "${alicloud_vswitch.foo.id}" vswitch_id = "${alicloud_vswitch.foo.id}"

View File

@ -92,6 +92,10 @@ func testAccCheckVswitchDestroy(s *terraform.State) error {
} }
const testAccVswitchConfig = ` const testAccVswitchConfig = `
data "alicloud_zones" "default" {
"available_resource_creation"= "VSwitch"
}
resource "alicloud_vpc" "foo" { resource "alicloud_vpc" "foo" {
name = "tf_test_foo" name = "tf_test_foo"
cidr_block = "172.16.0.0/12" cidr_block = "172.16.0.0/12"
@ -100,6 +104,6 @@ resource "alicloud_vpc" "foo" {
resource "alicloud_vswitch" "foo" { resource "alicloud_vswitch" "foo" {
vpc_id = "${alicloud_vpc.foo.id}" vpc_id = "${alicloud_vpc.foo.id}"
cidr_block = "172.16.0.0/21" cidr_block = "172.16.0.0/21"
availability_zone = "cn-beijing-b" availability_zone = "${data.alicloud_zones.default.zones.0.id}"
} }
` `

View File

@ -84,6 +84,24 @@ func (client *AliyunClient) DescribeZone(zoneID string) (*ecs.ZoneType, error) {
return zone, nil return zone, nil
} }
// return multiIZ list of current region
func (client *AliyunClient) DescribeMultiIZByRegion() (izs []string, err error) {
resp, err := client.rdsconn.DescribeRegions()
if err != nil {
return nil, fmt.Errorf("error to list regions not found")
}
regions := resp.Regions.RDSRegion
zoneIds := []string{}
for _, r := range regions {
if r.RegionId == string(client.Region) && strings.Contains(r.ZoneId, MULTI_IZ_SYMBOL) {
zoneIds = append(zoneIds, r.ZoneId)
}
}
return zoneIds, nil
}
func (client *AliyunClient) QueryInstancesByIds(ids []string) (instances []ecs.InstanceAttributesType, err error) { func (client *AliyunClient) QueryInstancesByIds(ids []string) (instances []ecs.InstanceAttributesType, err error) {
idsStr, jerr := json.Marshal(ids) idsStr, jerr := json.Marshal(ids)
if jerr != nil { if jerr != nil {
@ -119,6 +137,23 @@ func (client *AliyunClient) QueryInstancesById(id string) (instance *ecs.Instanc
return &instances[0], nil return &instances[0], nil
} }
func (client *AliyunClient) QueryInstanceSystemDisk(id string) (disk *ecs.DiskItemType, err error) {
args := ecs.DescribeDisksArgs{
RegionId: client.Region,
InstanceId: string(id),
DiskType: ecs.DiskTypeAllSystem,
}
disks, _, err := client.ecsconn.DescribeDisks(&args)
if err != nil {
return nil, err
}
if len(disks) == 0 {
return nil, common.GetClientErrorFromString(SystemDiskNotFound)
}
return &disks[0], nil
}
// ResourceAvailable check resource available for zone // ResourceAvailable check resource available for zone
func (client *AliyunClient) ResourceAvailable(zone *ecs.ZoneType, resourceType ecs.ResourceType) error { func (client *AliyunClient) ResourceAvailable(zone *ecs.ZoneType, resourceType ecs.ResourceType) error {
available := false available := false
@ -186,15 +221,26 @@ func (client *AliyunClient) DescribeSecurity(securityGroupId string) (*ecs.Descr
return client.ecsconn.DescribeSecurityGroupAttribute(args) return client.ecsconn.DescribeSecurityGroupAttribute(args)
} }
func (client *AliyunClient) DescribeSecurityGroupRule(securityGroupId, types, ip_protocol, port_range string) (*ecs.PermissionType, error) { func (client *AliyunClient) DescribeSecurityByAttr(securityGroupId, direction, nicType string) (*ecs.DescribeSecurityGroupAttributeResponse, error) {
sg, err := client.DescribeSecurity(securityGroupId) args := &ecs.DescribeSecurityGroupAttributeArgs{
RegionId: client.Region,
SecurityGroupId: securityGroupId,
Direction: direction,
NicType: ecs.NicType(nicType),
}
return client.ecsconn.DescribeSecurityGroupAttribute(args)
}
func (client *AliyunClient) DescribeSecurityGroupRule(securityGroupId, direction, nicType, ipProtocol, portRange string) (*ecs.PermissionType, error) {
sg, err := client.DescribeSecurityByAttr(securityGroupId, direction, nicType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, p := range sg.Permissions.Permission { for _, p := range sg.Permissions.Permission {
if strings.ToLower(string(p.IpProtocol)) == ip_protocol && p.PortRange == port_range { if strings.ToLower(string(p.IpProtocol)) == ipProtocol && p.PortRange == portRange {
return &p, nil return &p, nil
} }
} }
@ -203,6 +249,11 @@ func (client *AliyunClient) DescribeSecurityGroupRule(securityGroupId, types, ip
} }
func (client *AliyunClient) RevokeSecurityGroup(args *ecs.RevokeSecurityGroupArgs) error { func (client *AliyunClient) RevokeSecurityGroup(args *ecs.RevokeSecurityGroupArgs) error {
//todo: handle the specal err //when the rule is not exist, api will return success(200)
return client.ecsconn.RevokeSecurityGroup(args) return client.ecsconn.RevokeSecurityGroup(args)
} }
func (client *AliyunClient) RevokeSecurityGroupEgress(args *ecs.RevokeSecurityGroupEgressArgs) error {
//when the rule is not exist, api will return success(200)
return client.ecsconn.RevokeSecurityGroupEgress(args)
}

View File

@ -0,0 +1,278 @@
package alicloud
import (
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/rds"
"strings"
)
// when getInstance is empty, then throw InstanceNotfound error
func (client *AliyunClient) DescribeDBInstanceById(id string) (instance *rds.DBInstanceAttribute, err error) {
arrtArgs := rds.DescribeDBInstancesArgs{
DBInstanceId: id,
}
resp, err := client.rdsconn.DescribeDBInstanceAttribute(&arrtArgs)
if err != nil {
return nil, err
}
attr := resp.Items.DBInstanceAttribute
if len(attr) <= 0 {
return nil, common.GetClientErrorFromString(InstanceNotfound)
}
return &attr[0], nil
}
func (client *AliyunClient) CreateAccountByInfo(instanceId, username, pwd string) error {
conn := client.rdsconn
args := rds.CreateAccountArgs{
DBInstanceId: instanceId,
AccountName: username,
AccountPassword: pwd,
}
if _, err := conn.CreateAccount(&args); err != nil {
return err
}
if err := conn.WaitForAccount(instanceId, username, rds.Available, 200); err != nil {
return err
}
return nil
}
func (client *AliyunClient) CreateDatabaseByInfo(instanceId, dbName, charset, desp string) error {
conn := client.rdsconn
args := rds.CreateDatabaseArgs{
DBInstanceId: instanceId,
DBName: dbName,
CharacterSetName: charset,
DBDescription: desp,
}
_, err := conn.CreateDatabase(&args)
return err
}
func (client *AliyunClient) DescribeDatabaseByName(instanceId, dbName string) (ds []rds.Database, err error) {
conn := client.rdsconn
args := rds.DescribeDatabasesArgs{
DBInstanceId: instanceId,
DBName: dbName,
}
resp, err := conn.DescribeDatabases(&args)
if err != nil {
return nil, err
}
return resp.Databases.Database, nil
}
func (client *AliyunClient) GrantDBPrivilege2Account(instanceId, username, dbName string) error {
conn := client.rdsconn
pargs := rds.GrantAccountPrivilegeArgs{
DBInstanceId: instanceId,
AccountName: username,
DBName: dbName,
AccountPrivilege: rds.ReadWrite,
}
if _, err := conn.GrantAccountPrivilege(&pargs); err != nil {
return err
}
if err := conn.WaitForAccountPrivilege(instanceId, username, dbName, rds.ReadWrite, 200); err != nil {
return err
}
return nil
}
func (client *AliyunClient) AllocateDBPublicConnection(instanceId, port string) error {
conn := client.rdsconn
args := rds.AllocateInstancePublicConnectionArgs{
DBInstanceId: instanceId,
ConnectionStringPrefix: instanceId + "o",
Port: port,
}
if _, err := conn.AllocateInstancePublicConnection(&args); err != nil {
return err
}
if err := conn.WaitForPublicConnection(instanceId, 600); err != nil {
return err
}
return nil
}
func (client *AliyunClient) ConfigDBBackup(instanceId, backupTime, backupPeriod string, retentionPeriod int) error {
bargs := rds.BackupPolicy{
PreferredBackupTime: backupTime,
PreferredBackupPeriod: backupPeriod,
BackupRetentionPeriod: retentionPeriod,
}
args := rds.ModifyBackupPolicyArgs{
DBInstanceId: instanceId,
BackupPolicy: bargs,
}
if _, err := client.rdsconn.ModifyBackupPolicy(&args); err != nil {
return err
}
if err := client.rdsconn.WaitForInstance(instanceId, rds.Running, 600); err != nil {
return err
}
return nil
}
func (client *AliyunClient) ModifyDBSecurityIps(instanceId, ips string) error {
sargs := rds.DBInstanceIPArray{
SecurityIps: ips,
}
args := rds.ModifySecurityIpsArgs{
DBInstanceId: instanceId,
DBInstanceIPArray: sargs,
}
if _, err := client.rdsconn.ModifySecurityIps(&args); err != nil {
return err
}
if err := client.rdsconn.WaitForInstance(instanceId, rds.Running, 600); err != nil {
return err
}
return nil
}
func (client *AliyunClient) DescribeDBSecurityIps(instanceId string) (ips []rds.DBInstanceIPList, err error) {
args := rds.DescribeDBInstanceIPsArgs{
DBInstanceId: instanceId,
}
resp, err := client.rdsconn.DescribeDBInstanceIPs(&args)
if err != nil {
return nil, err
}
return resp.Items.DBInstanceIPArray, nil
}
func (client *AliyunClient) GetSecurityIps(instanceId string) ([]string, error) {
arr, err := client.DescribeDBSecurityIps(instanceId)
if err != nil {
return nil, err
}
ips := ""
for i, ip := range arr {
if i == 0 {
ips += ip.SecurityIPList
} else {
ips += COMMA_SEPARATED + ip.SecurityIPList
}
}
return strings.Split(ips, COMMA_SEPARATED), nil
}
func (client *AliyunClient) ModifyDBClassStorage(instanceId, class, storage string) error {
conn := client.rdsconn
args := rds.ModifyDBInstanceSpecArgs{
DBInstanceId: instanceId,
PayType: rds.Postpaid,
DBInstanceClass: class,
DBInstanceStorage: storage,
}
if _, err := conn.ModifyDBInstanceSpec(&args); err != nil {
return err
}
if err := conn.WaitForInstance(instanceId, rds.Running, 600); err != nil {
return err
}
return nil
}
// turn period to TimeType
func TransformPeriod2Time(period int, chargeType string) (ut int, tt common.TimeType) {
if chargeType == string(rds.Postpaid) {
return 1, common.Day
}
if period >= 1 && period <= 9 {
return period, common.Month
}
if period == 12 {
return 1, common.Year
}
if period == 24 {
return 2, common.Year
}
return 0, common.Day
}
// turn TimeType to Period
func TransformTime2Period(ut int, tt common.TimeType) (period int) {
if tt == common.Year {
return 12 * ut
}
return ut
}
// Flattens an array of databases into a []map[string]interface{}
func flattenDatabaseMappings(list []rds.Database) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
l := map[string]interface{}{
"db_name": i.DBName,
"character_set_name": i.CharacterSetName,
"db_description": i.DBDescription,
}
result = append(result, l)
}
return result
}
func flattenDBBackup(list []rds.BackupPolicy) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
l := map[string]interface{}{
"preferred_backup_period": i.PreferredBackupPeriod,
"preferred_backup_time": i.PreferredBackupTime,
"backup_retention_period": i.LogBackupRetentionPeriod,
}
result = append(result, l)
}
return result
}
func flattenDBSecurityIPs(list []rds.DBInstanceIPList) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
l := map[string]interface{}{
"security_ips": i.SecurityIPList,
}
result = append(result, l)
}
return result
}
// Flattens an array of databases connection into a []map[string]interface{}
func flattenDBConnections(list []rds.DBInstanceNetInfo) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
l := map[string]interface{}{
"connection_string": i.ConnectionString,
"ip_type": i.IPType,
"ip_address": i.IPAddress,
}
result = append(result, l)
}
return result
}

View File

@ -24,14 +24,14 @@ func (client *AliyunClient) DescribeEipAddress(allocationId string) (*ecs.EipAdd
return &eips[0], nil return &eips[0], nil
} }
func (client *AliyunClient) DescribeNatGateway(natGatewayId string) (*NatGatewaySetType, error) { func (client *AliyunClient) DescribeNatGateway(natGatewayId string) (*ecs.NatGatewaySetType, error) {
args := &DescribeNatGatewaysArgs{ args := &ecs.DescribeNatGatewaysArgs{
RegionId: client.Region, RegionId: client.Region,
NatGatewayId: natGatewayId, NatGatewayId: natGatewayId,
} }
natGateways, _, err := DescribeNatGateways(client.ecsconn, args) natGateways, _, err := client.vpcconn.DescribeNatGateways(args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,3 +132,23 @@ func (client *AliyunClient) QueryRouteEntry(routeTableId, cidrBlock, nextHopType
} }
return nil, nil return nil, nil
} }
func (client *AliyunClient) GetVpcIdByVSwitchId(vswitchId string) (vpcId string, err error) {
vs, _, err := client.ecsconn.DescribeVpcs(&ecs.DescribeVpcsArgs{
RegionId: client.Region,
})
if err != nil {
return "", err
}
for _, v := range vs {
for _, sw := range v.VSwitchIds.VSwitchId {
if sw == vswitchId {
return v.VpcId, nil
}
}
}
return "", &common.Error{ErrorResponse: common.ErrorResponse{Message: Notfound}}
}

View File

@ -2,17 +2,27 @@ package alicloud
import ( import (
"fmt" "fmt"
"regexp" "net"
"strconv"
"strings" "strings"
"github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs" "github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/terraform/helper/validation" "github.com/denverdino/aliyungo/slb"
"github.com/hashicorp/terraform/helper/schema"
"regexp"
) )
// common // common
func validateInstancePort(v interface{}, k string) (ws []string, errors []error) { func validateInstancePort(v interface{}, k string) (ws []string, errors []error) {
return validation.IntBetween(1, 65535)(v, k) value := v.(int)
if value < 1 || value > 65535 {
errors = append(errors, fmt.Errorf(
"%q must be a valid instance port between 1 and 65535",
k))
return
}
return
} }
func validateInstanceProtocol(v interface{}, k string) (ws []string, errors []error) { func validateInstanceProtocol(v interface{}, k string) (ws []string, errors []error) {
@ -28,11 +38,12 @@ func validateInstanceProtocol(v interface{}, k string) (ws []string, errors []er
// ecs // ecs
func validateDiskCategory(v interface{}, k string) (ws []string, errors []error) { func validateDiskCategory(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ category := ecs.DiskCategory(v.(string))
string(ecs.DiskCategoryCloud), if category != ecs.DiskCategoryCloud && category != ecs.DiskCategoryCloudEfficiency && category != ecs.DiskCategoryCloudSSD {
string(ecs.DiskCategoryCloudEfficiency), errors = append(errors, fmt.Errorf("%s must be one of %s %s %s", k, ecs.DiskCategoryCloud, ecs.DiskCategoryCloudEfficiency, ecs.DiskCategoryCloudSSD))
string(ecs.DiskCategoryCloudSSD), }
}, false)(v, k)
return
} }
func validateInstanceName(v interface{}, k string) (ws []string, errors []error) { func validateInstanceName(v interface{}, k string) (ws []string, errors []error) {
@ -49,7 +60,12 @@ func validateInstanceName(v interface{}, k string) (ws []string, errors []error)
} }
func validateInstanceDescription(v interface{}, k string) (ws []string, errors []error) { func validateInstanceDescription(v interface{}, k string) (ws []string, errors []error) {
return validation.StringLenBetween(2, 256)(v, k) value := v.(string)
if len(value) < 2 || len(value) > 256 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 256 characters", k))
}
return
} }
func validateDiskName(v interface{}, k string) (ws []string, errors []error) { func validateDiskName(v interface{}, k string) (ws []string, errors []error) {
@ -71,7 +87,12 @@ func validateDiskName(v interface{}, k string) (ws []string, errors []error) {
} }
func validateDiskDescription(v interface{}, k string) (ws []string, errors []error) { func validateDiskDescription(v interface{}, k string) (ws []string, errors []error) {
return validation.StringLenBetween(2, 128)(v, k) value := v.(string)
if len(value) < 2 || len(value) > 256 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 256 characters", k))
}
return
} }
//security group //security group
@ -89,114 +110,225 @@ func validateSecurityGroupName(v interface{}, k string) (ws []string, errors []e
} }
func validateSecurityGroupDescription(v interface{}, k string) (ws []string, errors []error) { func validateSecurityGroupDescription(v interface{}, k string) (ws []string, errors []error) {
return validation.StringLenBetween(2, 256)(v, k) value := v.(string)
if len(value) < 2 || len(value) > 256 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 256 characters", k))
}
return
} }
func validateSecurityRuleType(v interface{}, k string) (ws []string, errors []error) { func validateSecurityRuleType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ rt := GroupRuleDirection(v.(string))
string(GroupRuleIngress), if rt != GroupRuleIngress && rt != GroupRuleEgress {
string(GroupRuleEgress), errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRuleIngress, GroupRuleEgress))
}, false)(v, k) }
return
} }
func validateSecurityRuleIpProtocol(v interface{}, k string) (ws []string, errors []error) { func validateSecurityRuleIpProtocol(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ pt := GroupRuleIpProtocol(v.(string))
string(GroupRuleTcp), if pt != GroupRuleTcp && pt != GroupRuleUdp && pt != GroupRuleIcmp && pt != GroupRuleGre && pt != GroupRuleAll {
string(GroupRuleUdp), errors = append(errors, fmt.Errorf("%s must be one of %s %s %s %s %s", k,
string(GroupRuleIcmp), GroupRuleTcp, GroupRuleUdp, GroupRuleIcmp, GroupRuleGre, GroupRuleAll))
string(GroupRuleGre), }
string(GroupRuleAll),
}, false)(v, k) return
} }
func validateSecurityRuleNicType(v interface{}, k string) (ws []string, errors []error) { func validateSecurityRuleNicType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ pt := GroupRuleNicType(v.(string))
string(GroupRuleInternet), if pt != GroupRuleInternet && pt != GroupRuleIntranet {
string(GroupRuleIntranet), errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRuleInternet, GroupRuleIntranet))
}, false)(v, k) }
return
} }
func validateSecurityRulePolicy(v interface{}, k string) (ws []string, errors []error) { func validateSecurityRulePolicy(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ pt := GroupRulePolicy(v.(string))
string(GroupRulePolicyAccept), if pt != GroupRulePolicyAccept && pt != GroupRulePolicyDrop {
string(GroupRulePolicyDrop), errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRulePolicyAccept, GroupRulePolicyDrop))
}, false)(v, k) }
return
} }
func validateSecurityPriority(v interface{}, k string) (ws []string, errors []error) { func validateSecurityPriority(v interface{}, k string) (ws []string, errors []error) {
return validation.IntBetween(1, 100)(v, k) value := v.(int)
if value < 1 || value > 100 {
errors = append(errors, fmt.Errorf(
"%q must be a valid authorization policy priority between 1 and 100",
k))
return
}
return
} }
// validateCIDRNetworkAddress ensures that the string value is a valid CIDR that // validateCIDRNetworkAddress ensures that the string value is a valid CIDR that
// represents a network address - it adds an error otherwise // represents a network address - it adds an error otherwise
func validateCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) { func validateCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) {
return validation.CIDRNetwork(0, 32)(v, k) value := v.(string)
_, ipnet, err := net.ParseCIDR(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR, got error parsing: %s", k, err))
return
}
if ipnet == nil || value != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid network CIDR, expected %q, got %q",
k, ipnet, value))
}
return
} }
func validateRouteEntryNextHopType(v interface{}, k string) (ws []string, errors []error) { func validateRouteEntryNextHopType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ nht := ecs.NextHopType(v.(string))
string(ecs.NextHopIntance), if nht != ecs.NextHopIntance && nht != ecs.NextHopTunnel {
string(ecs.NextHopTunnel), errors = append(errors, fmt.Errorf("%s must be one of %s %s", k,
}, false)(v, k) ecs.NextHopIntance, ecs.NextHopTunnel))
}
return
} }
func validateSwitchCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) { func validateSwitchCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) {
return validation.CIDRNetwork(16, 29)(v, k) value := v.(string)
_, ipnet, err := net.ParseCIDR(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR, got error parsing: %s", k, err))
return
}
if ipnet == nil || value != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid network CIDR, expected %q, got %q",
k, ipnet, value))
return
}
mark, _ := strconv.Atoi(strings.Split(ipnet.String(), "/")[1])
if mark < 16 || mark > 29 {
errors = append(errors, fmt.Errorf(
"%q must contain a network CIDR which mark between 16 and 29",
k))
}
return
} }
// validateIoOptimized ensures that the string value is a valid IoOptimized that // validateIoOptimized ensures that the string value is a valid IoOptimized that
// represents a IoOptimized - it adds an error otherwise // represents a IoOptimized - it adds an error otherwise
func validateIoOptimized(v interface{}, k string) (ws []string, errors []error) { func validateIoOptimized(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"", ioOptimized := ecs.IoOptimized(value)
string(ecs.IoOptimizedNone), if ioOptimized != ecs.IoOptimizedNone &&
string(ecs.IoOptimizedOptimized), ioOptimized != ecs.IoOptimizedOptimized {
}, false)(v, k) errors = append(errors, fmt.Errorf(
"%q must contain a valid IoOptimized, expected %s or %s, got %q",
k, ecs.IoOptimizedNone, ecs.IoOptimizedOptimized, ioOptimized))
}
}
return
} }
// validateInstanceNetworkType ensures that the string value is a classic or vpc // validateInstanceNetworkType ensures that the string value is a classic or vpc
func validateInstanceNetworkType(v interface{}, k string) (ws []string, errors []error) { func validateInstanceNetworkType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"", network := InstanceNetWork(value)
string(ClassicNet), if network != ClassicNet &&
string(VpcNet), network != VpcNet {
}, false)(v, k) errors = append(errors, fmt.Errorf(
"%q must contain a valid InstanceNetworkType, expected %s or %s, go %q",
k, ClassicNet, VpcNet, network))
}
}
return
} }
func validateInstanceChargeType(v interface{}, k string) (ws []string, errors []error) { func validateInstanceChargeType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"", chargeType := common.InstanceChargeType(value)
string(common.PrePaid), if chargeType != common.PrePaid &&
string(common.PostPaid), chargeType != common.PostPaid {
}, false)(v, k) errors = append(errors, fmt.Errorf(
"%q must contain a valid InstanceChargeType, expected %s or %s, got %q",
k, common.PrePaid, common.PostPaid, chargeType))
}
}
return
} }
func validateInternetChargeType(v interface{}, k string) (ws []string, errors []error) { func validateInternetChargeType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"", chargeType := common.InternetChargeType(value)
string(common.PayByBandwidth), if chargeType != common.PayByBandwidth &&
string(common.PayByTraffic), chargeType != common.PayByTraffic {
}, false)(v, k) errors = append(errors, fmt.Errorf(
"%q must contain a valid InstanceChargeType, expected %s or %s, got %q",
k, common.PayByBandwidth, common.PayByTraffic, chargeType))
}
}
return
} }
func validateInternetMaxBandWidthOut(v interface{}, k string) (ws []string, errors []error) { func validateInternetMaxBandWidthOut(v interface{}, k string) (ws []string, errors []error) {
return validation.IntBetween(1, 100)(v, k) value := v.(int)
if value < 1 || value > 100 {
errors = append(errors, fmt.Errorf(
"%q must be a valid internet bandwidth out between 1 and 1000",
k))
return
}
return
} }
// SLB // SLB
func validateSlbName(v interface{}, k string) (ws []string, errors []error) { func validateSlbName(v interface{}, k string) (ws []string, errors []error) {
return validation.StringLenBetween(0, 80)(v, k) if value := v.(string); value != "" {
if len(value) < 1 || len(value) > 80 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer name characters between 1 and 80",
k))
return
}
}
return
} }
func validateSlbInternetChargeType(v interface{}, k string) (ws []string, errors []error) { func validateSlbInternetChargeType(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"paybybandwidth", chargeType := common.InternetChargeType(value)
"paybytraffic",
}, false)(v, k) if chargeType != "paybybandwidth" &&
chargeType != "paybytraffic" {
errors = append(errors, fmt.Errorf(
"%q must contain a valid InstanceChargeType, expected %s or %s, got %q",
k, "paybybandwidth", "paybytraffic", value))
}
}
return
} }
func validateSlbBandwidth(v interface{}, k string) (ws []string, errors []error) { func validateSlbBandwidth(v interface{}, k string) (ws []string, errors []error) {
return validation.IntBetween(1, 1000)(v, k) value := v.(int)
if value < 1 || value > 1000 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer bandwidth between 1 and 1000",
k))
return
}
return
} }
func validateSlbListenerBandwidth(v interface{}, k string) (ws []string, errors []error) { func validateSlbListenerBandwidth(v interface{}, k string) (ws []string, errors []error) {
@ -211,23 +343,180 @@ func validateSlbListenerBandwidth(v interface{}, k string) (ws []string, errors
} }
func validateSlbListenerScheduler(v interface{}, k string) (ws []string, errors []error) { func validateSlbListenerScheduler(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{"wrr", "wlc"}, false)(v, k) if value := v.(string); value != "" {
} scheduler := slb.SchedulerType(value)
func validateSlbListenerStickySession(v interface{}, k string) (ws []string, errors []error) { if scheduler != "wrr" && scheduler != "wlc" {
return validation.StringInSlice([]string{"", "on", "off"}, false)(v, k) errors = append(errors, fmt.Errorf(
} "%q must contain a valid SchedulerType, expected %s or %s, got %q",
k, "wrr", "wlc", value))
}
}
func validateSlbListenerStickySessionType(v interface{}, k string) (ws []string, errors []error) { return
return validation.StringInSlice([]string{"", "insert", "server"}, false)(v, k)
} }
func validateSlbListenerCookie(v interface{}, k string) (ws []string, errors []error) { func validateSlbListenerCookie(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{"", "insert", "server"}, false)(v, k) if value := v.(string); value != "" {
if len(value) < 1 || len(value) > 200 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 200 characters", k))
}
}
return
}
func validateSlbListenerCookieTimeout(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < 0 || value > 86400 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer cookie timeout between 0 and 86400",
k))
return
}
return
} }
func validateSlbListenerPersistenceTimeout(v interface{}, k string) (ws []string, errors []error) { func validateSlbListenerPersistenceTimeout(v interface{}, k string) (ws []string, errors []error) {
return validation.IntBetween(0, 86400)(v, k) value := v.(int)
if value < 0 || value > 3600 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer persistence timeout between 0 and 86400",
k))
return
}
return
}
func validateSlbListenerHealthCheckDomain(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
//the len add "$_ip",so to max is 84
if len(value) < 1 || len(value) > 84 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 84 characters", k))
}
}
return
}
func validateSlbListenerHealthCheckUri(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
if len(value) < 1 || len(value) > 80 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k))
}
}
return
}
func validateSlbListenerHealthCheckConnectPort(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < 1 || value > 65535 {
if value != -520 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer health check connect port between 1 and 65535 or -520",
k))
return
}
}
return
}
func validateDBBackupPeriod(v interface{}, k string) (ws []string, errors []error) {
days := []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
value := v.(string)
exist := false
for _, d := range days {
if value == d {
exist = true
break
}
}
if !exist {
errors = append(errors, fmt.Errorf(
"%q must contain a valid backup period value should in array %#v, got %q",
k, days, value))
}
return
}
func validateAllowedStringValue(ss []string) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
existed := false
for _, s := range ss {
if s == value {
existed = true
break
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid string value should in array %#v, got %q",
k, ss, value))
}
return
}
}
func validateAllowedSplitStringValue(ss []string, splitStr string) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
existed := false
tsList := strings.Split(value, splitStr)
for _, ts := range tsList {
existed = false
for _, s := range ss {
if ts == s {
existed = true
break
}
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid string value should in %#v, got %q",
k, ss, value))
}
return
}
}
func validateAllowedIntValue(is []int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
existed := false
for _, i := range is {
if i == value {
existed = true
break
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid int value should in array %#v, got %q",
k, is, value))
}
return
}
}
func validateIntegerInRange(min, max int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < min {
errors = append(errors, fmt.Errorf(
"%q cannot be lower than %d: %d", k, min, value))
}
if value > max {
errors = append(errors, fmt.Errorf(
"%q cannot be higher than %d: %d", k, max, value))
}
return
}
} }
//data source validate func //data source validate func
@ -244,14 +533,19 @@ func validateNameRegex(v interface{}, k string) (ws []string, errors []error) {
} }
func validateImageOwners(v interface{}, k string) (ws []string, errors []error) { func validateImageOwners(v interface{}, k string) (ws []string, errors []error) {
return validation.StringInSlice([]string{ if value := v.(string); value != "" {
"", owners := ecs.ImageOwnerAlias(value)
string(ecs.ImageOwnerSystem), if owners != ecs.ImageOwnerSystem &&
string(ecs.ImageOwnerSelf), owners != ecs.ImageOwnerSelf &&
string(ecs.ImageOwnerOthers), owners != ecs.ImageOwnerOthers &&
string(ecs.ImageOwnerMarketplace), owners != ecs.ImageOwnerMarketplace &&
string(ecs.ImageOwnerDefault), owners != ecs.ImageOwnerDefault {
}, false)(v, k) errors = append(errors, fmt.Errorf(
"%q must contain a valid Image owner , expected %s, %s, %s, %s or %s, got %q",
k, ecs.ImageOwnerSystem, ecs.ImageOwnerSelf, ecs.ImageOwnerOthers, ecs.ImageOwnerMarketplace, ecs.ImageOwnerDefault, owners))
}
}
return
} }
func validateRegion(v interface{}, k string) (ws []string, errors []error) { func validateRegion(v interface{}, k string) (ws []string, errors []error) {

View File

@ -427,3 +427,76 @@ func TestValidateSlbListenerBandwidth(t *testing.T) {
} }
} }
} }
func TestValidateAllowedStringValue(t *testing.T) {
exceptValues := []string{"aliyun", "alicloud", "alibaba"}
validValues := []string{"aliyun"}
for _, v := range validValues {
_, errors := validateAllowedStringValue(exceptValues)(v, "allowvalue")
if len(errors) != 0 {
t.Fatalf("%q should be a valid value in %#v: %q", v, exceptValues, errors)
}
}
invalidValues := []string{"ali", "alidata", "terraform"}
for _, v := range invalidValues {
_, errors := validateAllowedStringValue(exceptValues)(v, "allowvalue")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid value", v)
}
}
}
func TestValidateAllowedStringSplitValue(t *testing.T) {
exceptValues := []string{"aliyun", "alicloud", "alibaba"}
validValues := "aliyun,alicloud"
_, errors := validateAllowedSplitStringValue(exceptValues, ",")(validValues, "allowvalue")
if len(errors) != 0 {
t.Fatalf("%q should be a valid value in %#v: %q", validValues, exceptValues, errors)
}
invalidValues := "ali,alidata"
_, invalidErr := validateAllowedSplitStringValue(exceptValues, ",")(invalidValues, "allowvalue")
if len(invalidErr) == 0 {
t.Fatalf("%q should be an invalid value", invalidValues)
}
}
func TestValidateAllowedIntValue(t *testing.T) {
exceptValues := []int{1, 3, 5, 6}
validValues := []int{1, 3, 5, 6}
for _, v := range validValues {
_, errors := validateAllowedIntValue(exceptValues)(v, "allowvalue")
if len(errors) != 0 {
t.Fatalf("%q should be a valid value in %#v: %q", v, exceptValues, errors)
}
}
invalidValues := []int{0, 7, 10}
for _, v := range invalidValues {
_, errors := validateAllowedIntValue(exceptValues)(v, "allowvalue")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid value", v)
}
}
}
func TestValidateIntegerInRange(t *testing.T) {
validIntegers := []int{-259, 0, 1, 5, 999}
min := -259
max := 999
for _, v := range validIntegers {
_, errors := validateIntegerInRange(min, max)(v, "name")
if len(errors) != 0 {
t.Fatalf("%q should be an integer in range (%d, %d): %q", v, min, max, errors)
}
}
invalidIntegers := []int{-260, -99999, 1000, 25678}
for _, v := range invalidIntegers {
_, errors := validateIntegerInRange(min, max)(v, "name")
if len(errors) == 0 {
t.Fatalf("%q should be an integer outside range (%d, %d)", v, min, max)
}
}
}

View File

@ -58,6 +58,10 @@ func dataSourceAwsCloudFormationStack() *schema.Resource {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
}, },
"iam_role_arn": {
Type: schema.TypeString,
Computed: true,
},
"tags": { "tags": {
Type: schema.TypeMap, Type: schema.TypeMap,
Computed: true, Computed: true,
@ -86,6 +90,7 @@ func dataSourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface
d.Set("description", stack.Description) d.Set("description", stack.Description)
d.Set("disable_rollback", stack.DisableRollback) d.Set("disable_rollback", stack.DisableRollback)
d.Set("timeout_in_minutes", stack.TimeoutInMinutes) d.Set("timeout_in_minutes", stack.TimeoutInMinutes)
d.Set("iam_role_arn", stack.RoleARN)
if len(stack.NotificationARNs) > 0 { if len(stack.NotificationARNs) > 0 {
d.Set("notification_arns", schema.NewSet(schema.HashString, flattenStringList(stack.NotificationARNs))) d.Set("notification_arns", schema.NewSet(schema.HashString, flattenStringList(stack.NotificationARNs)))

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
) )
@ -18,15 +19,15 @@ func timePtr(t time.Time) *time.Time {
func TestResourceSortByExpirationDate(t *testing.T) { func TestResourceSortByExpirationDate(t *testing.T) {
certs := []*iam.ServerCertificateMetadata{ certs := []*iam.ServerCertificateMetadata{
&iam.ServerCertificateMetadata{ {
ServerCertificateName: aws.String("oldest"), ServerCertificateName: aws.String("oldest"),
Expiration: timePtr(time.Now()), Expiration: timePtr(time.Now()),
}, },
&iam.ServerCertificateMetadata{ {
ServerCertificateName: aws.String("latest"), ServerCertificateName: aws.String("latest"),
Expiration: timePtr(time.Now().Add(3 * time.Hour)), Expiration: timePtr(time.Now().Add(3 * time.Hour)),
}, },
&iam.ServerCertificateMetadata{ {
ServerCertificateName: aws.String("in between"), ServerCertificateName: aws.String("in between"),
Expiration: timePtr(time.Now().Add(2 * time.Hour)), Expiration: timePtr(time.Now().Add(2 * time.Hour)),
}, },
@ -38,13 +39,18 @@ func TestResourceSortByExpirationDate(t *testing.T) {
} }
func TestAccAWSDataSourceIAMServerCertificate_basic(t *testing.T) { func TestAccAWSDataSourceIAMServerCertificate_basic(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy, CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccAwsDataIAMServerCertConfig, Config: testAccIAMServerCertConfig(rInt),
},
{
Config: testAccAwsDataIAMServerCertConfig(rInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("aws_iam_server_certificate.test_cert", "arn"), resource.TestCheckResourceAttrSet("aws_iam_server_certificate.test_cert", "arn"),
resource.TestCheckResourceAttrSet("data.aws_iam_server_certificate.test", "arn"), resource.TestCheckResourceAttrSet("data.aws_iam_server_certificate.test", "arn"),
@ -71,12 +77,16 @@ func TestAccAWSDataSourceIAMServerCertificate_matchNamePrefix(t *testing.T) {
}) })
} }
var testAccAwsDataIAMServerCertConfig = fmt.Sprintf(`%s func testAccAwsDataIAMServerCertConfig(rInt int) string {
return fmt.Sprintf(`
%s
data "aws_iam_server_certificate" "test" { data "aws_iam_server_certificate" "test" {
name = "${aws_iam_server_certificate.test_cert.name}" name = "${aws_iam_server_certificate.test_cert.name}"
latest = true latest = true
} }
`, testAccIAMServerCertConfig) `, testAccIAMServerCertConfig(rInt))
}
var testAccAwsDataIAMServerCertConfigMatchNamePrefix = ` var testAccAwsDataIAMServerCertConfigMatchNamePrefix = `
data "aws_iam_server_certificate" "test" { data "aws_iam_server_certificate" "test" {

View File

@ -11,7 +11,7 @@ import (
func TestAccDataSourceAwsRoute53Zone(t *testing.T) { func TestAccDataSourceAwsRoute53Zone(t *testing.T) {
rInt := acctest.RandInt() rInt := acctest.RandInt()
publicResourceName := "aws_route53_zon.test" publicResourceName := "aws_route53_zone.test"
publicDomain := fmt.Sprintf("terraformtestacchz-%d.com.", rInt) publicDomain := fmt.Sprintf("terraformtestacchz-%d.com.", rInt)
privateResourceName := "aws_route53_zone.test_private" privateResourceName := "aws_route53_zone.test_private"
privateDomain := fmt.Sprintf("test.acc-%d.", rInt) privateDomain := fmt.Sprintf("test.acc-%d.", rInt)

View File

@ -3,19 +3,20 @@ package aws
import ( import (
"testing" "testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
) )
func TestAccAWSCustomerGateway_importBasic(t *testing.T) { func TestAccAWSCustomerGateway_importBasic(t *testing.T) {
resourceName := "aws_customer_gateway.foo" resourceName := "aws_customer_gateway.foo"
randInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckCustomerGatewayDestroy, CheckDestroy: testAccCheckCustomerGatewayDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccCustomerGatewayConfig, Config: testAccCustomerGatewayConfig(randInt),
}, },
resource.TestStep{ resource.TestStep{

View File

@ -3,11 +3,13 @@ package aws
import ( import (
"testing" "testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
) )
func TestAccAWSEFSFileSystem_importBasic(t *testing.T) { func TestAccAWSEFSFileSystem_importBasic(t *testing.T) {
resourceName := "aws_efs_file_system.foo-with-tags" resourceName := "aws_efs_file_system.foo-with-tags"
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -15,7 +17,7 @@ func TestAccAWSEFSFileSystem_importBasic(t *testing.T) {
CheckDestroy: testAccCheckEfsFileSystemDestroy, CheckDestroy: testAccCheckEfsFileSystemDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccAWSEFSFileSystemConfigWithTags, Config: testAccAWSEFSFileSystemConfigWithTags(rInt),
}, },
resource.TestStep{ resource.TestStep{

View File

@ -0,0 +1,34 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSIAMServerCertificate_importBasic(t *testing.T) {
resourceName := "aws_iam_server_certificate.test_cert"
rInt := acctest.RandInt()
resourceId := fmt.Sprintf("terraform-test-cert-%d", rInt)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{
{
Config: testAccIAMServerCertConfig(rInt),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateId: resourceId,
ImportStateVerifyIgnore: []string{
"private_key"},
},
},
})
}

View File

@ -31,10 +31,12 @@ func resourceAwsAlbListenerRule() *schema.Resource {
"listener_arn": { "listener_arn": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true,
}, },
"priority": { "priority": {
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ForceNew: true,
ValidateFunc: validateAwsAlbListenerRulePriority, ValidateFunc: validateAwsAlbListenerRulePriority,
}, },
"action": { "action": {
@ -66,6 +68,7 @@ func resourceAwsAlbListenerRule() *schema.Resource {
}, },
"values": { "values": {
Type: schema.TypeList, Type: schema.TypeList,
MaxItems: 1,
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
Optional: true, Optional: true,
}, },
@ -183,10 +186,32 @@ func resourceAwsAlbListenerRuleRead(d *schema.ResourceData, meta interface{}) er
func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn elbconn := meta.(*AWSClient).elbv2conn
d.Partial(true)
if d.HasChange("priority") {
params := &elbv2.SetRulePrioritiesInput{
RulePriorities: []*elbv2.RulePriorityPair{
{
RuleArn: aws.String(d.Id()),
Priority: aws.Int64(int64(d.Get("priority").(int))),
},
},
}
_, err := elbconn.SetRulePriorities(params)
if err != nil {
return err
}
d.SetPartial("priority")
}
requestUpdate := false
params := &elbv2.ModifyRuleInput{ params := &elbv2.ModifyRuleInput{
RuleArn: aws.String(d.Id()), RuleArn: aws.String(d.Id()),
} }
if d.HasChange("action") {
actions := d.Get("action").([]interface{}) actions := d.Get("action").([]interface{})
params.Actions = make([]*elbv2.Action, len(actions)) params.Actions = make([]*elbv2.Action, len(actions))
for i, action := range actions { for i, action := range actions {
@ -196,7 +221,11 @@ func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{})
Type: aws.String(actionMap["type"].(string)), Type: aws.String(actionMap["type"].(string)),
} }
} }
requestUpdate = true
d.SetPartial("action")
}
if d.HasChange("condition") {
conditions := d.Get("condition").([]interface{}) conditions := d.Get("condition").([]interface{})
params.Conditions = make([]*elbv2.RuleCondition, len(conditions)) params.Conditions = make([]*elbv2.RuleCondition, len(conditions))
for i, condition := range conditions { for i, condition := range conditions {
@ -210,7 +239,11 @@ func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{})
params.Conditions[i].Values[j] = aws.String(value.(string)) params.Conditions[i].Values[j] = aws.String(value.(string))
} }
} }
requestUpdate = true
d.SetPartial("condition")
}
if requestUpdate {
resp, err := elbconn.ModifyRule(params) resp, err := elbconn.ModifyRule(params)
if err != nil { if err != nil {
return errwrap.Wrapf("Error modifying ALB Listener Rule: {{err}}", err) return errwrap.Wrapf("Error modifying ALB Listener Rule: {{err}}", err)
@ -219,6 +252,9 @@ func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{})
if len(resp.Rules) == 0 { if len(resp.Rules) == 0 {
return errors.New("Error modifying creating ALB Listener Rule: no rules returned in response") return errors.New("Error modifying creating ALB Listener Rule: no rules returned in response")
} }
}
d.Partial(false)
return resourceAwsAlbListenerRuleRead(d, meta) return resourceAwsAlbListenerRuleRead(d, meta)
} }

View File

@ -3,6 +3,7 @@ package aws
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
"testing" "testing"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
@ -43,6 +44,90 @@ func TestAccAWSALBListenerRule_basic(t *testing.T) {
}) })
} }
func TestAccAWSALBListenerRule_updateRulePriority(t *testing.T) {
var rule elbv2.Rule
albName := fmt.Sprintf("testrule-basic-%s", acctest.RandStringFromCharSet(13, acctest.CharSetAlphaNum))
targetGroupName := fmt.Sprintf("testtargetgroup-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb_listener_rule.static",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBListenerRuleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBListenerRuleConfig_basic(albName, targetGroupName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBListenerRuleExists("aws_alb_listener_rule.static", &rule),
resource.TestCheckResourceAttr("aws_alb_listener_rule.static", "priority", "100"),
),
},
{
Config: testAccAWSALBListenerRuleConfig_updateRulePriority(albName, targetGroupName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBListenerRuleExists("aws_alb_listener_rule.static", &rule),
resource.TestCheckResourceAttr("aws_alb_listener_rule.static", "priority", "101"),
),
},
},
})
}
func TestAccAWSALBListenerRule_changeListenerRuleArnForcesNew(t *testing.T) {
var before, after elbv2.Rule
albName := fmt.Sprintf("testrule-basic-%s", acctest.RandStringFromCharSet(13, acctest.CharSetAlphaNum))
targetGroupName := fmt.Sprintf("testtargetgroup-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb_listener_rule.static",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBListenerRuleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBListenerRuleConfig_basic(albName, targetGroupName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBListenerRuleExists("aws_alb_listener_rule.static", &before),
),
},
{
Config: testAccAWSALBListenerRuleConfig_changeRuleArn(albName, targetGroupName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBListenerRuleExists("aws_alb_listener_rule.static", &after),
testAccCheckAWSAlbListenerRuleRecreated(t, &before, &after),
),
},
},
})
}
func TestAccAWSALBListenerRule_multipleConditionThrowsError(t *testing.T) {
albName := fmt.Sprintf("testrule-basic-%s", acctest.RandStringFromCharSet(13, acctest.CharSetAlphaNum))
targetGroupName := fmt.Sprintf("testtargetgroup-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBListenerRuleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBListenerRuleConfig_multipleConditions(albName, targetGroupName),
ExpectError: regexp.MustCompile(`attribute supports 1 item maximum`),
},
},
})
}
func testAccCheckAWSAlbListenerRuleRecreated(t *testing.T,
before, after *elbv2.Rule) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *before.RuleArn == *after.RuleArn {
t.Fatalf("Expected change of Listener Rule ARNs, but both were %v", before.RuleArn)
}
return nil
}
}
func testAccCheckAWSALBListenerRuleExists(n string, res *elbv2.Rule) resource.TestCheckFunc { func testAccCheckAWSALBListenerRuleExists(n string, res *elbv2.Rule) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -104,6 +189,117 @@ func testAccCheckAWSALBListenerRuleDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccAWSALBListenerRuleConfig_multipleConditions(albName, targetGroupName string) string {
return fmt.Sprintf(`resource "aws_alb_listener_rule" "static" {
listener_arn = "${aws_alb_listener.front_end.arn}"
priority = 100
action {
type = "forward"
target_group_arn = "${aws_alb_target_group.test.arn}"
}
condition {
field = "path-pattern"
values = ["/static/*", "static"]
}
}
resource "aws_alb_listener" "front_end" {
load_balancer_arn = "${aws_alb.alb_test.id}"
protocol = "HTTP"
port = "80"
default_action {
target_group_arn = "${aws_alb_target_group.test.id}"
type = "forward"
}
}
resource "aws_alb" "alb_test" {
name = "%s"
internal = true
security_groups = ["${aws_security_group.alb_test.id}"]
subnets = ["${aws_subnet.alb_test.*.id}"]
idle_timeout = 30
enable_deletion_protection = false
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_alb_target_group" "test" {
name = "%s"
port = 8080
protocol = "HTTP"
vpc_id = "${aws_vpc.alb_test.id}"
health_check {
path = "/health"
interval = 60
port = 8081
protocol = "HTTP"
timeout = 3
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-299"
}
}
variable "subnets" {
default = ["10.0.1.0/24", "10.0.2.0/24"]
type = "list"
}
data "aws_availability_zones" "available" {}
resource "aws_vpc" "alb_test" {
cidr_block = "10.0.0.0/16"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_subnet" "alb_test" {
count = 2
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "${element(var.subnets, count.index)}"
map_public_ip_on_launch = true
availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_security_group" "alb_test" {
name = "allow_all_alb_test"
description = "Used for ALB Testing"
vpc_id = "${aws_vpc.alb_test.id}"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags {
TestName = "TestAccAWSALB_basic"
}
}`, albName, targetGroupName)
}
func testAccAWSALBListenerRuleConfig_basic(albName, targetGroupName string) string { func testAccAWSALBListenerRuleConfig_basic(albName, targetGroupName string) string {
return fmt.Sprintf(`resource "aws_alb_listener_rule" "static" { return fmt.Sprintf(`resource "aws_alb_listener_rule" "static" {
listener_arn = "${aws_alb_listener.front_end.arn}" listener_arn = "${aws_alb_listener.front_end.arn}"
@ -214,3 +410,238 @@ resource "aws_security_group" "alb_test" {
} }
}`, albName, targetGroupName) }`, albName, targetGroupName)
} }
func testAccAWSALBListenerRuleConfig_updateRulePriority(albName, targetGroupName string) string {
return fmt.Sprintf(`
resource "aws_alb_listener_rule" "static" {
listener_arn = "${aws_alb_listener.front_end.arn}"
priority = 101
action {
type = "forward"
target_group_arn = "${aws_alb_target_group.test.arn}"
}
condition {
field = "path-pattern"
values = ["/static/*"]
}
}
resource "aws_alb_listener" "front_end" {
load_balancer_arn = "${aws_alb.alb_test.id}"
protocol = "HTTP"
port = "80"
default_action {
target_group_arn = "${aws_alb_target_group.test.id}"
type = "forward"
}
}
resource "aws_alb" "alb_test" {
name = "%s"
internal = true
security_groups = ["${aws_security_group.alb_test.id}"]
subnets = ["${aws_subnet.alb_test.*.id}"]
idle_timeout = 30
enable_deletion_protection = false
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_alb_target_group" "test" {
name = "%s"
port = 8080
protocol = "HTTP"
vpc_id = "${aws_vpc.alb_test.id}"
health_check {
path = "/health"
interval = 60
port = 8081
protocol = "HTTP"
timeout = 3
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-299"
}
}
variable "subnets" {
default = ["10.0.1.0/24", "10.0.2.0/24"]
type = "list"
}
data "aws_availability_zones" "available" {}
resource "aws_vpc" "alb_test" {
cidr_block = "10.0.0.0/16"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_subnet" "alb_test" {
count = 2
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "${element(var.subnets, count.index)}"
map_public_ip_on_launch = true
availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_security_group" "alb_test" {
name = "allow_all_alb_test"
description = "Used for ALB Testing"
vpc_id = "${aws_vpc.alb_test.id}"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags {
TestName = "TestAccAWSALB_basic"
}
}`, albName, targetGroupName)
}
func testAccAWSALBListenerRuleConfig_changeRuleArn(albName, targetGroupName string) string {
return fmt.Sprintf(`
resource "aws_alb_listener_rule" "static" {
listener_arn = "${aws_alb_listener.front_end_ruleupdate.arn}"
priority = 101
action {
type = "forward"
target_group_arn = "${aws_alb_target_group.test.arn}"
}
condition {
field = "path-pattern"
values = ["/static/*"]
}
}
resource "aws_alb_listener" "front_end" {
load_balancer_arn = "${aws_alb.alb_test.id}"
protocol = "HTTP"
port = "80"
default_action {
target_group_arn = "${aws_alb_target_group.test.id}"
type = "forward"
}
}
resource "aws_alb_listener" "front_end_ruleupdate" {
load_balancer_arn = "${aws_alb.alb_test.id}"
protocol = "HTTP"
port = "8080"
default_action {
target_group_arn = "${aws_alb_target_group.test.id}"
type = "forward"
}
}
resource "aws_alb" "alb_test" {
name = "%s"
internal = true
security_groups = ["${aws_security_group.alb_test.id}"]
subnets = ["${aws_subnet.alb_test.*.id}"]
idle_timeout = 30
enable_deletion_protection = false
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_alb_target_group" "test" {
name = "%s"
port = 8080
protocol = "HTTP"
vpc_id = "${aws_vpc.alb_test.id}"
health_check {
path = "/health"
interval = 60
port = 8081
protocol = "HTTP"
timeout = 3
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-299"
}
}
variable "subnets" {
default = ["10.0.1.0/24", "10.0.2.0/24"]
type = "list"
}
data "aws_availability_zones" "available" {}
resource "aws_vpc" "alb_test" {
cidr_block = "10.0.0.0/16"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_subnet" "alb_test" {
count = 2
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "${element(var.subnets, count.index)}"
map_public_ip_on_launch = true
availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_security_group" "alb_test" {
name = "allow_all_alb_test"
description = "Used for ALB Testing"
vpc_id = "${aws_vpc.alb_test.id}"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags {
TestName = "TestAccAWSALB_basic"
}
}`, albName, targetGroupName)
}

View File

@ -22,12 +22,12 @@ func resourceAwsCloudFormationStack() *schema.Resource {
Delete: resourceAwsCloudFormationStackDelete, Delete: resourceAwsCloudFormationStackDelete,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"name": &schema.Schema{ "name": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
}, },
"template_body": &schema.Schema{ "template_body": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
@ -37,42 +37,42 @@ func resourceAwsCloudFormationStack() *schema.Resource {
return template return template
}, },
}, },
"template_url": &schema.Schema{ "template_url": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
}, },
"capabilities": &schema.Schema{ "capabilities": {
Type: schema.TypeSet, Type: schema.TypeSet,
Optional: true, Optional: true,
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString, Set: schema.HashString,
}, },
"disable_rollback": &schema.Schema{ "disable_rollback": {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"notification_arns": &schema.Schema{ "notification_arns": {
Type: schema.TypeSet, Type: schema.TypeSet,
Optional: true, Optional: true,
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString, Set: schema.HashString,
}, },
"on_failure": &schema.Schema{ "on_failure": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"parameters": &schema.Schema{ "parameters": {
Type: schema.TypeMap, Type: schema.TypeMap,
Optional: true, Optional: true,
Computed: true, Computed: true,
}, },
"outputs": &schema.Schema{ "outputs": {
Type: schema.TypeMap, Type: schema.TypeMap,
Computed: true, Computed: true,
}, },
"policy_body": &schema.Schema{ "policy_body": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
@ -82,20 +82,24 @@ func resourceAwsCloudFormationStack() *schema.Resource {
return json return json
}, },
}, },
"policy_url": &schema.Schema{ "policy_url": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
}, },
"timeout_in_minutes": &schema.Schema{ "timeout_in_minutes": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"tags": &schema.Schema{ "tags": {
Type: schema.TypeMap, Type: schema.TypeMap,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"iam_role_arn": {
Type: schema.TypeString,
Optional: true,
},
}, },
} }
} }
@ -153,6 +157,9 @@ func resourceAwsCloudFormationStackCreate(d *schema.ResourceData, meta interface
log.Printf("[DEBUG] CloudFormation timeout: %d", retryTimeout) log.Printf("[DEBUG] CloudFormation timeout: %d", retryTimeout)
} }
} }
if v, ok := d.GetOk("iam_role_arn"); ok {
input.RoleARN = aws.String(v.(string))
}
log.Printf("[DEBUG] Creating CloudFormation Stack: %s", input) log.Printf("[DEBUG] Creating CloudFormation Stack: %s", input)
resp, err := conn.CreateStack(&input) resp, err := conn.CreateStack(&input)
@ -297,6 +304,7 @@ func resourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{}
d.Set("name", stack.StackName) d.Set("name", stack.StackName)
d.Set("arn", stack.StackId) d.Set("arn", stack.StackId)
d.Set("iam_role_arn", stack.RoleARN)
if stack.TimeoutInMinutes != nil { if stack.TimeoutInMinutes != nil {
d.Set("timeout_in_minutes", int(*stack.TimeoutInMinutes)) d.Set("timeout_in_minutes", int(*stack.TimeoutInMinutes))
@ -385,6 +393,10 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface
input.StackPolicyURL = aws.String(d.Get("policy_url").(string)) input.StackPolicyURL = aws.String(d.Get("policy_url").(string))
} }
if d.HasChange("iam_role_arn") {
input.RoleARN = aws.String(d.Get("iam_role_arn").(string))
}
log.Printf("[DEBUG] Updating CloudFormation stack: %s", input) log.Printf("[DEBUG] Updating CloudFormation stack: %s", input)
stack, err := conn.UpdateStack(input) stack, err := conn.UpdateStack(input)
if err != nil { if err != nil {

View File

@ -20,7 +20,7 @@ func TestAccAWSCloudFormation_basic(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig, Config: testAccAWSCloudFormationConfig,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.network", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.network", &stack),
@ -38,7 +38,7 @@ func TestAccAWSCloudFormation_yaml(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_yaml, Config: testAccAWSCloudFormationConfig_yaml,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.yaml", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.yaml", &stack),
@ -56,7 +56,7 @@ func TestAccAWSCloudFormation_defaultParams(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_defaultParams, Config: testAccAWSCloudFormationConfig_defaultParams,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.asg-demo", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.asg-demo", &stack),
@ -75,7 +75,7 @@ func TestAccAWSCloudFormation_allAttributes(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_allAttributesWithBodies, Config: testAccAWSCloudFormationConfig_allAttributesWithBodies,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.full", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.full", &stack),
@ -93,7 +93,7 @@ func TestAccAWSCloudFormation_allAttributes(t *testing.T) {
resource.TestCheckResourceAttr("aws_cloudformation_stack.full", "timeout_in_minutes", "10"), resource.TestCheckResourceAttr("aws_cloudformation_stack.full", "timeout_in_minutes", "10"),
), ),
}, },
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_allAttributesWithBodies_modified, Config: testAccAWSCloudFormationConfig_allAttributesWithBodies_modified,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.full", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.full", &stack),
@ -124,13 +124,13 @@ func TestAccAWSCloudFormation_withParams(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_withParams, Config: testAccAWSCloudFormationConfig_withParams,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack),
), ),
}, },
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_withParams_modified, Config: testAccAWSCloudFormationConfig_withParams_modified,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack),
@ -149,13 +149,13 @@ func TestAccAWSCloudFormation_withUrl_withParams(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_templateUrl_withParams, Config: testAccAWSCloudFormationConfig_templateUrl_withParams,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack),
), ),
}, },
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_templateUrl_withParams_modified, Config: testAccAWSCloudFormationConfig_templateUrl_withParams_modified,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack),
@ -173,7 +173,7 @@ func TestAccAWSCloudFormation_withUrl_withParams_withYaml(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudFormationDestroy, CheckDestroy: testAccCheckAWSCloudFormationDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSCloudFormationConfig_templateUrl_withParams_withYaml, Config: testAccAWSCloudFormationConfig_templateUrl_withParams_withYaml,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params-and-yaml", &stack), testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params-and-yaml", &stack),

View File

@ -10,12 +10,14 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestAccAWSCustomerGateway_basic(t *testing.T) { func TestAccAWSCustomerGateway_basic(t *testing.T) {
var gateway ec2.CustomerGateway var gateway ec2.CustomerGateway
randInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_customer_gateway.foo", IDRefreshName: "aws_customer_gateway.foo",
@ -23,19 +25,19 @@ func TestAccAWSCustomerGateway_basic(t *testing.T) {
CheckDestroy: testAccCheckCustomerGatewayDestroy, CheckDestroy: testAccCheckCustomerGatewayDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccCustomerGatewayConfig, Config: testAccCustomerGatewayConfig(randInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway),
), ),
}, },
{ {
Config: testAccCustomerGatewayConfigUpdateTags, Config: testAccCustomerGatewayConfigUpdateTags(randInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway),
), ),
}, },
{ {
Config: testAccCustomerGatewayConfigForceReplace, Config: testAccCustomerGatewayConfigForceReplace(randInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway),
), ),
@ -46,6 +48,7 @@ func TestAccAWSCustomerGateway_basic(t *testing.T) {
func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) { func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) {
var gateway ec2.CustomerGateway var gateway ec2.CustomerGateway
randInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_customer_gateway.foo", IDRefreshName: "aws_customer_gateway.foo",
@ -53,13 +56,13 @@ func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) {
CheckDestroy: testAccCheckCustomerGatewayDestroy, CheckDestroy: testAccCheckCustomerGatewayDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccCustomerGatewayConfig, Config: testAccCustomerGatewayConfig(randInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway),
), ),
}, },
{ {
Config: testAccCustomerGatewayConfigIdentical, Config: testAccCustomerGatewayConfigIdentical(randInt),
ExpectError: regexp.MustCompile("An existing customer gateway"), ExpectError: regexp.MustCompile("An existing customer gateway"),
}, },
}, },
@ -68,13 +71,14 @@ func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) {
func TestAccAWSCustomerGateway_disappears(t *testing.T) { func TestAccAWSCustomerGateway_disappears(t *testing.T) {
var gateway ec2.CustomerGateway var gateway ec2.CustomerGateway
randInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckCustomerGatewayDestroy, CheckDestroy: testAccCheckCustomerGatewayDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccCustomerGatewayConfig, Config: testAccCustomerGatewayConfig(randInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway),
testAccAWSCustomerGatewayDisappears(&gateway), testAccAWSCustomerGatewayDisappears(&gateway),
@ -190,59 +194,67 @@ func testAccCheckCustomerGateway(gatewayResource string, cgw *ec2.CustomerGatewa
} }
} }
const testAccCustomerGatewayConfig = ` func testAccCustomerGatewayConfig(randInt int) string {
resource "aws_customer_gateway" "foo" { return fmt.Sprintf(`
resource "aws_customer_gateway" "foo" {
bgp_asn = 65000 bgp_asn = 65000
ip_address = "172.0.0.1" ip_address = "172.0.0.1"
type = "ipsec.1" type = "ipsec.1"
tags { tags {
Name = "foo-gateway" Name = "foo-gateway-%d"
} }
}
`
const testAccCustomerGatewayConfigIdentical = `
resource "aws_customer_gateway" "foo" {
bgp_asn = 65000
ip_address = "172.0.0.1"
type = "ipsec.1"
tags {
Name = "foo-gateway"
} }
`, randInt)
} }
resource "aws_customer_gateway" "identical" { func testAccCustomerGatewayConfigIdentical(randInt int) string {
return fmt.Sprintf(`
resource "aws_customer_gateway" "foo" {
bgp_asn = 65000 bgp_asn = 65000
ip_address = "172.0.0.1" ip_address = "172.0.0.1"
type = "ipsec.1" type = "ipsec.1"
tags { tags {
Name = "foo-gateway-identical" Name = "foo-gateway-%d"
} }
}
resource "aws_customer_gateway" "identical" {
bgp_asn = 65000
ip_address = "172.0.0.1"
type = "ipsec.1"
tags {
Name = "foo-gateway-identical-%d"
}
}
`, randInt, randInt)
} }
`
// Add the Another: "tag" tag. // Add the Another: "tag" tag.
const testAccCustomerGatewayConfigUpdateTags = ` func testAccCustomerGatewayConfigUpdateTags(randInt int) string {
resource "aws_customer_gateway" "foo" { return fmt.Sprintf(`
resource "aws_customer_gateway" "foo" {
bgp_asn = 65000 bgp_asn = 65000
ip_address = "172.0.0.1" ip_address = "172.0.0.1"
type = "ipsec.1" type = "ipsec.1"
tags { tags {
Name = "foo-gateway" Name = "foo-gateway-%d"
Another = "tag" Another = "tag-%d"
} }
}
`, randInt, randInt)
} }
`
// Change the ip_address. // Change the ip_address.
const testAccCustomerGatewayConfigForceReplace = ` func testAccCustomerGatewayConfigForceReplace(randInt int) string {
resource "aws_customer_gateway" "foo" { return fmt.Sprintf(`
resource "aws_customer_gateway" "foo" {
bgp_asn = 65000 bgp_asn = 65000
ip_address = "172.10.10.1" ip_address = "172.10.10.1"
type = "ipsec.1" type = "ipsec.1"
tags { tags {
Name = "foo-gateway" Name = "foo-gateway-%d"
Another = "tag" Another = "tag-%d"
} }
}
`, randInt, randInt)
} }
`

View File

@ -9,11 +9,14 @@ import (
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
// ACL Network ACLs all contain an explicit deny-all rule that cannot be // ACL Network ACLs all contain explicit deny-all rules that cannot be
// destroyed or changed by users. This rule is numbered very high to be a // destroyed or changed by users. This rules are numbered very high to be a
// catch-all. // catch-all.
// See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl // See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl
const awsDefaultAclRuleNumber = 32767 const (
awsDefaultAclRuleNumberIpv4 = 32767
awsDefaultAclRuleNumberIpv6 = 32768
)
func resourceAwsDefaultNetworkAcl() *schema.Resource { func resourceAwsDefaultNetworkAcl() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
@ -258,7 +261,8 @@ func revokeAllNetworkACLEntries(netaclId string, meta interface{}) error {
for _, e := range networkAcl.Entries { for _, e := range networkAcl.Entries {
// Skip the default rules added by AWS. They can be neither // Skip the default rules added by AWS. They can be neither
// configured or deleted by users. See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl // configured or deleted by users. See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl
if *e.RuleNumber == awsDefaultAclRuleNumber { if *e.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
*e.RuleNumber == awsDefaultAclRuleNumberIpv6 {
continue continue
} }

View File

@ -36,8 +36,27 @@ func TestAccAWSDefaultNetworkAcl_basic(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_basic, Config: testAccAWSDefaultNetworkConfig_basic,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0, 2),
),
},
},
})
}
func TestAccAWSDefaultNetworkAcl_basicIpv6Vpc(t *testing.T) {
var networkAcl ec2.NetworkAcl
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDefaultNetworkAclDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_basicIpv6Vpc,
Check: resource.ComposeTestCheckFunc(
testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0, 4),
), ),
}, },
}, },
@ -58,8 +77,8 @@ func TestAccAWSDefaultNetworkAcl_deny_ingress(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_deny_ingress, Config: testAccAWSDefaultNetworkConfig_deny_ingress,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{defaultEgressAcl}, 0), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{defaultEgressAcl}, 0, 2),
), ),
}, },
}, },
@ -77,8 +96,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetRemoval(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_Subnets, Config: testAccAWSDefaultNetworkConfig_Subnets,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2),
), ),
}, },
@ -88,8 +107,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetRemoval(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_Subnets_remove, Config: testAccAWSDefaultNetworkConfig_Subnets_remove,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2),
), ),
ExpectNonEmptyPlan: true, ExpectNonEmptyPlan: true,
}, },
@ -108,8 +127,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetReassign(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_Subnets, Config: testAccAWSDefaultNetworkConfig_Subnets,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2),
), ),
}, },
@ -128,8 +147,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetReassign(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSDefaultNetworkConfig_Subnets_move, Config: testAccAWSDefaultNetworkConfig_Subnets_move,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl),
testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0), testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0, 2),
), ),
}, },
}, },
@ -141,14 +160,14 @@ func testAccCheckAWSDefaultNetworkAclDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckAWSDefaultACLAttributes(acl *ec2.NetworkAcl, rules []*ec2.NetworkAclEntry, subnetCount int) resource.TestCheckFunc { func testAccCheckAWSDefaultACLAttributes(acl *ec2.NetworkAcl, rules []*ec2.NetworkAclEntry, subnetCount int, hiddenRuleCount int) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
aclEntriesCount := len(acl.Entries) aclEntriesCount := len(acl.Entries)
ruleCount := len(rules) ruleCount := len(rules)
// Default ACL has 2 hidden rules we can't do anything about // Default ACL has hidden rules we can't do anything about
ruleCount = ruleCount + 2 ruleCount = ruleCount + hiddenRuleCount
if ruleCount != aclEntriesCount { if ruleCount != aclEntriesCount {
return fmt.Errorf("Expected (%d) Rules, got (%d)", ruleCount, aclEntriesCount) return fmt.Errorf("Expected (%d) Rules, got (%d)", ruleCount, aclEntriesCount)
@ -162,7 +181,7 @@ func testAccCheckAWSDefaultACLAttributes(acl *ec2.NetworkAcl, rules []*ec2.Netwo
} }
} }
func testAccGetWSDefaultNetworkAcl(n string, networkAcl *ec2.NetworkAcl) resource.TestCheckFunc { func testAccGetAWSDefaultNetworkAcl(n string, networkAcl *ec2.NetworkAcl) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
if !ok { if !ok {
@ -426,3 +445,26 @@ resource "aws_default_network_acl" "default" {
} }
} }
` `
const testAccAWSDefaultNetworkConfig_basicIpv6Vpc = `
provider "aws" {
region = "us-east-2"
}
resource "aws_vpc" "tftestvpc" {
cidr_block = "10.1.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "TestAccAWSDefaultNetworkAcl_basicIpv6Vpc"
}
}
resource "aws_default_network_acl" "default" {
default_network_acl_id = "${aws_vpc.tftestvpc.default_network_acl_id}"
tags {
Name = "TestAccAWSDefaultNetworkAcl_basicIpv6Vpc"
}
}
`

View File

@ -169,7 +169,7 @@ resource "aws_dms_replication_instance" "dms_replication_instance" {
func dmsReplicationInstanceConfigUpdate(randId string) string { func dmsReplicationInstanceConfigUpdate(randId string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" { resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role" name = "dms-vpc-role-%[1]s"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
} }

View File

@ -102,7 +102,7 @@ func dmsReplicationSubnetGroupDestroy(s *terraform.State) error {
func dmsReplicationSubnetGroupConfig(randId string) string { func dmsReplicationSubnetGroupConfig(randId string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" { resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role" name = "dms-vpc-role-%[1]s"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
} }

View File

@ -102,7 +102,7 @@ func dmsReplicationTaskDestroy(s *terraform.State) error {
func dmsReplicationTaskConfig(randId string) string { func dmsReplicationTaskConfig(randId string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" { resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role" name = "dms-vpc-role-%[1]s"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
} }

View File

@ -82,6 +82,7 @@ func TestResourceAWSEFSFileSystem_hasEmptyFileSystems(t *testing.T) {
} }
func TestAccAWSEFSFileSystem_basic(t *testing.T) { func TestAccAWSEFSFileSystem_basic(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -104,7 +105,7 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) {
), ),
}, },
{ {
Config: testAccAWSEFSFileSystemConfigWithTags, Config: testAccAWSEFSFileSystemConfigWithTags(rInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckEfsFileSystem( testAccCheckEfsFileSystem(
"aws_efs_file_system.foo-with-tags", "aws_efs_file_system.foo-with-tags",
@ -116,7 +117,7 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) {
testAccCheckEfsFileSystemTags( testAccCheckEfsFileSystemTags(
"aws_efs_file_system.foo-with-tags", "aws_efs_file_system.foo-with-tags",
map[string]string{ map[string]string{
"Name": "foo-efs", "Name": fmt.Sprintf("foo-efs-%d", rInt),
"Another": "tag", "Another": "tag",
}, },
), ),
@ -143,13 +144,14 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) {
} }
func TestAccAWSEFSFileSystem_pagedTags(t *testing.T) { func TestAccAWSEFSFileSystem_pagedTags(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckEfsFileSystemDestroy, CheckDestroy: testAccCheckEfsFileSystemDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccAWSEFSFileSystemConfigPagedTags, Config: testAccAWSEFSFileSystemConfigPagedTags(rInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_efs_file_system.foo", "aws_efs_file_system.foo",
@ -312,11 +314,12 @@ resource "aws_efs_file_system" "foo" {
} }
` `
const testAccAWSEFSFileSystemConfigPagedTags = ` func testAccAWSEFSFileSystemConfigPagedTags(rInt int) string {
resource "aws_efs_file_system" "foo" { return fmt.Sprintf(`
resource "aws_efs_file_system" "foo" {
creation_token = "radeksimko" creation_token = "radeksimko"
tags { tags {
Name = "foo-efs" Name = "foo-efs-%d"
Another = "tag" Another = "tag"
Test = "yes" Test = "yes"
User = "root" User = "root"
@ -328,18 +331,21 @@ resource "aws_efs_file_system" "foo" {
PerfMode = "max" PerfMode = "max"
Region = "us-west-2" Region = "us-west-2"
} }
}
`, rInt)
} }
`
const testAccAWSEFSFileSystemConfigWithTags = ` func testAccAWSEFSFileSystemConfigWithTags(rInt int) string {
resource "aws_efs_file_system" "foo-with-tags" { return fmt.Sprintf(`
resource "aws_efs_file_system" "foo-with-tags" {
creation_token = "yada_yada" creation_token = "yada_yada"
tags { tags {
Name = "foo-efs" Name = "foo-efs-%d"
Another = "tag" Another = "tag"
} }
}
`, rInt)
} }
`
const testAccAWSEFSFileSystemConfigWithPerformanceMode = ` const testAccAWSEFSFileSystemConfigWithPerformanceMode = `
resource "aws_efs_file_system" "foo-with-performance-mode" { resource "aws_efs_file_system" "foo-with-performance-mode" {

View File

@ -661,7 +661,7 @@ resource "aws_elastic_beanstalk_environment" "tfenvtest" {
func testAccBeanstalkWorkerEnvConfig(rInt int) string { func testAccBeanstalkWorkerEnvConfig(rInt int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_iam_instance_profile" "tftest" { resource "aws_iam_instance_profile" "tftest" {
name = "tftest_profile" name = "tftest_profile-%d"
roles = ["${aws_iam_role.tftest.name}"] roles = ["${aws_iam_role.tftest.name}"]
} }
@ -693,7 +693,7 @@ func testAccBeanstalkWorkerEnvConfig(rInt int) string {
name = "IamInstanceProfile" name = "IamInstanceProfile"
value = "${aws_iam_instance_profile.tftest.name}" value = "${aws_iam_instance_profile.tftest.name}"
} }
}`, rInt, rInt) }`, rInt, rInt, rInt)
} }
func testAccBeanstalkEnvCnamePrefixConfig(randString string, rInt int) string { func testAccBeanstalkEnvCnamePrefixConfig(randString string, rInt int) string {
@ -937,24 +937,24 @@ resource "aws_s3_bucket_object" "default" {
} }
resource "aws_elastic_beanstalk_application" "default" { resource "aws_elastic_beanstalk_application" "default" {
name = "tf-test-name" name = "tf-test-name-%d"
description = "tf-test-desc" description = "tf-test-desc"
} }
resource "aws_elastic_beanstalk_application_version" "default" { resource "aws_elastic_beanstalk_application_version" "default" {
application = "tf-test-name" application = "tf-test-name-%d"
name = "tf-test-version-label" name = "tf-test-version-label"
bucket = "${aws_s3_bucket.default.id}" bucket = "${aws_s3_bucket.default.id}"
key = "${aws_s3_bucket_object.default.id}" key = "${aws_s3_bucket_object.default.id}"
} }
resource "aws_elastic_beanstalk_environment" "default" { resource "aws_elastic_beanstalk_environment" "default" {
name = "tf-test-name" name = "tf-test-name-%d"
application = "${aws_elastic_beanstalk_application.default.name}" application = "${aws_elastic_beanstalk_application.default.name}"
version_label = "${aws_elastic_beanstalk_application_version.default.name}" version_label = "${aws_elastic_beanstalk_application_version.default.name}"
solution_stack_name = "64bit Amazon Linux running Python" solution_stack_name = "64bit Amazon Linux running Python"
} }
`, randInt) `, randInt, randInt, randInt, randInt)
} }
func testAccBeanstalkEnvApplicationVersionConfigUpdate(randInt int) string { func testAccBeanstalkEnvApplicationVersionConfigUpdate(randInt int) string {
@ -970,22 +970,22 @@ resource "aws_s3_bucket_object" "default" {
} }
resource "aws_elastic_beanstalk_application" "default" { resource "aws_elastic_beanstalk_application" "default" {
name = "tf-test-name" name = "tf-test-name-%d"
description = "tf-test-desc" description = "tf-test-desc"
} }
resource "aws_elastic_beanstalk_application_version" "default" { resource "aws_elastic_beanstalk_application_version" "default" {
application = "tf-test-name" application = "tf-test-name-%d"
name = "tf-test-version-label-v2" name = "tf-test-version-label-v2"
bucket = "${aws_s3_bucket.default.id}" bucket = "${aws_s3_bucket.default.id}"
key = "${aws_s3_bucket_object.default.id}" key = "${aws_s3_bucket_object.default.id}"
} }
resource "aws_elastic_beanstalk_environment" "default" { resource "aws_elastic_beanstalk_environment" "default" {
name = "tf-test-name" name = "tf-test-name-%d"
application = "${aws_elastic_beanstalk_application.default.name}" application = "${aws_elastic_beanstalk_application.default.name}"
version_label = "${aws_elastic_beanstalk_application_version.default.name}" version_label = "${aws_elastic_beanstalk_application_version.default.name}"
solution_stack_name = "64bit Amazon Linux running Python" solution_stack_name = "64bit Amazon Linux running Python"
} }
`, randInt) `, randInt, randInt, randInt, randInt)
} }

View File

@ -87,9 +87,19 @@ func resourceAwsIamInstanceProfile() *schema.Resource {
"roles": { "roles": {
Type: schema.TypeSet, Type: schema.TypeSet,
Required: true, Optional: true,
Computed: true,
ConflictsWith: []string{"role"},
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString, Set: schema.HashString,
Deprecated: "Use `role` instead. Only a single role can be passed to an IAM Instance Profile",
},
"role": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ConflictsWith: []string{"roles"},
}, },
}, },
} }
@ -107,6 +117,13 @@ func resourceAwsIamInstanceProfileCreate(d *schema.ResourceData, meta interface{
name = resource.UniqueId() name = resource.UniqueId()
} }
_, hasRoles := d.GetOk("roles")
_, hasRole := d.GetOk("role")
if hasRole == false && hasRoles == false {
return fmt.Errorf("Either `roles` or `role` must be specified when creating an IAM Instance Profile")
}
request := &iam.CreateInstanceProfileInput{ request := &iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(name), InstanceProfileName: aws.String(name),
Path: aws.String(d.Get("path").(string)), Path: aws.String(d.Get("path").(string)),
@ -132,7 +149,7 @@ func resourceAwsIamInstanceProfileCreate(d *schema.ResourceData, meta interface{
return fmt.Errorf("Timed out while waiting for instance profile %s: %s", name, err) return fmt.Errorf("Timed out while waiting for instance profile %s: %s", name, err)
} }
return instanceProfileSetRoles(d, iamconn) return resourceAwsIamInstanceProfileUpdate(d, meta)
} }
func instanceProfileAddRole(iamconn *iam.IAM, profileName, roleName string) error { func instanceProfileAddRole(iamconn *iam.IAM, profileName, roleName string) error {
@ -205,11 +222,35 @@ func instanceProfileRemoveAllRoles(d *schema.ResourceData, iamconn *iam.IAM) err
func resourceAwsIamInstanceProfileUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsIamInstanceProfileUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn iamconn := meta.(*AWSClient).iamconn
if !d.HasChange("roles") { d.Partial(true)
return nil
if d.HasChange("role") {
oldRole, newRole := d.GetChange("role")
if oldRole.(string) != "" {
err := instanceProfileRemoveRole(iamconn, d.Id(), oldRole.(string))
if err != nil {
return fmt.Errorf("Error adding role %s to IAM instance profile %s: %s", oldRole.(string), d.Id(), err)
}
} }
if newRole.(string) != "" {
err := instanceProfileAddRole(iamconn, d.Id(), newRole.(string))
if err != nil {
return fmt.Errorf("Error adding role %s to IAM instance profile %s: %s", newRole.(string), d.Id(), err)
}
}
d.SetPartial("role")
}
if d.HasChange("roles") {
return instanceProfileSetRoles(d, iamconn) return instanceProfileSetRoles(d, iamconn)
}
d.Partial(false)
return nil
} }
func resourceAwsIamInstanceProfileRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsIamInstanceProfileRead(d *schema.ResourceData, meta interface{}) error {
@ -262,6 +303,10 @@ func instanceProfileReadResult(d *schema.ResourceData, result *iam.InstanceProfi
} }
d.Set("unique_id", result.InstanceProfileId) d.Set("unique_id", result.InstanceProfileId)
if result.Roles != nil && len(result.Roles) > 0 {
d.Set("role", result.Roles[0].RoleName) //there will only be 1 role returned
}
roles := &schema.Set{F: schema.HashString} roles := &schema.Set{F: schema.HashString}
for _, role := range result.Roles { for _, role := range result.Roles {
roles.Add(*role.RoleName) roles.Add(*role.RoleName)

View File

@ -2,6 +2,7 @@ package aws
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"testing" "testing"
@ -37,6 +38,8 @@ func TestAccAWSIAMInstanceProfile_importBasic(t *testing.T) {
} }
func TestAccAWSIAMInstanceProfile_basic(t *testing.T) { func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
var conf iam.GetInstanceProfileOutput
rName := acctest.RandString(5) rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -44,6 +47,41 @@ func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccAwsIamInstanceProfileConfig(rName), Config: testAccAwsIamInstanceProfileConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists("aws_iam_instance_profile.test", &conf),
),
},
},
})
}
func TestAccAWSIAMInstanceProfile_withRoleNotRoles(t *testing.T) {
var conf iam.GetInstanceProfileOutput
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAWSInstanceProfileWithRoleSpecified(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists("aws_iam_instance_profile.test", &conf),
),
},
},
})
}
func TestAccAWSIAMInstanceProfile_missingRoleThrowsError(t *testing.T) {
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAwsIamInstanceProfileConfigMissingRole(rName),
ExpectError: regexp.MustCompile("Either `roles` or `role` must be specified when creating an IAM Instance Profile"),
}, },
}, },
}) })
@ -157,6 +195,13 @@ resource "aws_iam_instance_profile" "test" {
}`, rName) }`, rName)
} }
func testAccAwsIamInstanceProfileConfigMissingRole(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_instance_profile" "test" {
name = "test-%s"
}`, rName)
}
func testAccAWSInstanceProfilePrefixNameConfig(rName string) string { func testAccAWSInstanceProfilePrefixNameConfig(rName string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "aws_iam_role" "test" { resource "aws_iam_role" "test" {
@ -169,3 +214,16 @@ resource "aws_iam_instance_profile" "test" {
roles = ["${aws_iam_role.test.name}"] roles = ["${aws_iam_role.test.name}"]
}`, rName) }`, rName)
} }
func testAccAWSInstanceProfileWithRoleSpecified(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = "test-%s"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
}
resource "aws_iam_instance_profile" "test" {
name_prefix = "test-"
role = "${aws_iam_role.test.name}"
}`, rName)
}

View File

@ -20,37 +20,41 @@ func resourceAwsIAMServerCertificate() *schema.Resource {
Create: resourceAwsIAMServerCertificateCreate, Create: resourceAwsIAMServerCertificateCreate,
Read: resourceAwsIAMServerCertificateRead, Read: resourceAwsIAMServerCertificateRead,
Delete: resourceAwsIAMServerCertificateDelete, Delete: resourceAwsIAMServerCertificateDelete,
Importer: &schema.ResourceImporter{
State: resourceAwsIAMServerCertificateImport,
},
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"certificate_body": &schema.Schema{ "certificate_body": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
StateFunc: normalizeCert, StateFunc: normalizeCert,
}, },
"certificate_chain": &schema.Schema{ "certificate_chain": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
StateFunc: normalizeCert, StateFunc: normalizeCert,
}, },
"path": &schema.Schema{ "path": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Default: "/", Default: "/",
ForceNew: true, ForceNew: true,
}, },
"private_key": &schema.Schema{ "private_key": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
StateFunc: normalizeCert, StateFunc: normalizeCert,
Sensitive: true,
}, },
"name": &schema.Schema{ "name": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
@ -66,7 +70,7 @@ func resourceAwsIAMServerCertificate() *schema.Resource {
}, },
}, },
"name_prefix": &schema.Schema{ "name_prefix": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -80,7 +84,7 @@ func resourceAwsIAMServerCertificate() *schema.Resource {
}, },
}, },
"arn": &schema.Schema{ "arn": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
@ -148,6 +152,8 @@ func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{
return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err) return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err)
} }
d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId)
// these values should always be present, and have a default if not set in // these values should always be present, and have a default if not set in
// configuration, and so safe to reference with nil checks // configuration, and so safe to reference with nil checks
d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody)) d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody))
@ -196,6 +202,13 @@ func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interfac
return nil return nil
} }
func resourceAwsIAMServerCertificateImport(
d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("name", d.Id())
// private_key can't be fetched from any API call
return []*schema.ResourceData{d}, nil
}
func normalizeCert(cert interface{}) string { func normalizeCert(cert interface{}) string {
if cert == nil || cert == (*string)(nil) { if cert == nil || cert == (*string)(nil) {
return "" return ""

View File

@ -2,10 +2,8 @@ package aws
import ( import (
"fmt" "fmt"
"math/rand"
"strings" "strings"
"testing" "testing"
"time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam"
@ -16,14 +14,15 @@ import (
func TestAccAWSIAMServerCertificate_basic(t *testing.T) { func TestAccAWSIAMServerCertificate_basic(t *testing.T) {
var cert iam.ServerCertificate var cert iam.ServerCertificate
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy, CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccIAMServerCertConfig, Config: testAccIAMServerCertConfig(rInt),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert),
testAccCheckAWSServerCertAttributes(&cert), testAccCheckAWSServerCertAttributes(&cert),
@ -41,7 +40,7 @@ func TestAccAWSIAMServerCertificate_name_prefix(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy, CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccIAMServerCertConfig_random, Config: testAccIAMServerCertConfig_random,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert),
@ -74,7 +73,7 @@ func TestAccAWSIAMServerCertificate_disappears(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy, CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccIAMServerCertConfig_random, Config: testAccIAMServerCertConfig_random,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert),
@ -97,7 +96,7 @@ func TestAccAWSIAMServerCertificate_file(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckIAMServerCertificateDestroy, CheckDestroy: testAccCheckIAMServerCertificateDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-unix-line-endings"), Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-unix-line-endings"),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert),
@ -105,7 +104,7 @@ func TestAccAWSIAMServerCertificate_file(t *testing.T) {
), ),
}, },
resource.TestStep{ {
Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-windows-line-endings"), Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-windows-line-endings"),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert),
@ -202,7 +201,8 @@ CqDUFjhydXxYRsxXBBrEiLOE5BdtJR1sH/QHxIJe23C9iHI2nS1NbLziNEApLwC4
GnSud83VUo9G9w== GnSud83VUo9G9w==
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
var testAccIAMServerCertConfig = fmt.Sprintf(` func testAccIAMServerCertConfig(rInt int) string {
return fmt.Sprintf(`
resource "aws_iam_server_certificate" "test_cert" { resource "aws_iam_server_certificate" "test_cert" {
name = "terraform-test-cert-%d" name = "terraform-test-cert-%d"
certificate_body = <<EOF certificate_body = <<EOF
@ -257,7 +257,8 @@ dg+Sd4Wjm89UQoUUoiIcstY7FPbqfBtYKfh4RYHAHV2BwDFqzZCM
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
EOF EOF
} }
`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) `, rInt)
}
var testAccIAMServerCertConfig_random = ` var testAccIAMServerCertConfig_random = `
resource "aws_iam_server_certificate" "test_cert" { resource "aws_iam_server_certificate" "test_cert" {

View File

@ -201,7 +201,8 @@ func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
for _, e := range networkAcl.Entries { for _, e := range networkAcl.Entries {
// Skip the default rules added by AWS. They can be neither // Skip the default rules added by AWS. They can be neither
// configured or deleted by users. // configured or deleted by users.
if *e.RuleNumber == awsDefaultAclRuleNumber { if *e.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
*e.RuleNumber == awsDefaultAclRuleNumberIpv6 {
continue continue
} }
@ -358,7 +359,8 @@ func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2
// neither modified nor destroyed. They have a custom rule // neither modified nor destroyed. They have a custom rule
// number that is out of bounds for any other rule. If we // number that is out of bounds for any other rule. If we
// encounter it, just continue. There's no work to be done. // encounter it, just continue. There's no work to be done.
if *remove.RuleNumber == awsDefaultAclRuleNumber { if *remove.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
*remove.RuleNumber == awsDefaultAclRuleNumberIpv6 {
continue continue
} }

View File

@ -41,6 +41,12 @@ func resourceAwsNetworkAclRule() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if old == "all" && new == "-1" || old == "-1" && new == "all" {
return true
}
return false
},
}, },
"rule_action": { "rule_action": {
Type: schema.TypeString, Type: schema.TypeString,

View File

@ -66,6 +66,25 @@ func TestAccAWSNetworkAclRule_ipv6(t *testing.T) {
}) })
} }
func TestAccAWSNetworkAclRule_allProtocol(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSNetworkAclRuleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSNetworkAclRuleAllProtocolConfig,
ExpectNonEmptyPlan: false,
},
{
Config: testAccAWSNetworkAclRuleAllProtocolConfigNoRealUpdate,
ExpectNonEmptyPlan: false,
},
},
})
}
func TestResourceAWSNetworkAclRule_validateICMPArgumentValue(t *testing.T) { func TestResourceAWSNetworkAclRule_validateICMPArgumentValue(t *testing.T) {
type testCases struct { type testCases struct {
Value string Value string
@ -251,6 +270,44 @@ resource "aws_network_acl_rule" "baz" {
} }
` `
const testAccAWSNetworkAclRuleAllProtocolConfigNoRealUpdate = `
resource "aws_vpc" "foo" {
cidr_block = "10.3.0.0/16"
}
resource "aws_network_acl" "bar" {
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_network_acl_rule" "baz" {
network_acl_id = "${aws_network_acl.bar.id}"
rule_number = 150
egress = false
protocol = "all"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 22
to_port = 22
}
`
const testAccAWSNetworkAclRuleAllProtocolConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.3.0.0/16"
}
resource "aws_network_acl" "bar" {
vpc_id = "${aws_vpc.foo.id}"
}
resource "aws_network_acl_rule" "baz" {
network_acl_id = "${aws_network_acl.bar.id}"
rule_number = 150
egress = false
protocol = "-1"
rule_action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 22
to_port = 22
}
`
const testAccAWSNetworkAclRuleIpv6Config = ` const testAccAWSNetworkAclRuleIpv6Config = `
resource "aws_vpc" "foo" { resource "aws_vpc" "foo" {
cidr_block = "10.3.0.0/16" cidr_block = "10.3.0.0/16"

View File

@ -242,6 +242,8 @@ func TestAccAWSNetworkAcl_ipv6Rules(t *testing.T) {
Config: testAccAWSNetworkAclIpv6Config, Config: testAccAWSNetworkAclIpv6Config,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl), testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.#", "1"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.1976110835.protocol", "6"), "aws_network_acl.foos", "ingress.1976110835.protocol", "6"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
@ -260,6 +262,29 @@ func TestAccAWSNetworkAcl_ipv6Rules(t *testing.T) {
}) })
} }
func TestAccAWSNetworkAcl_ipv6VpcRules(t *testing.T) {
var networkAcl ec2.NetworkAcl
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_network_acl.foos",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSNetworkAclDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSNetworkAclIpv6VpcConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSNetworkAclExists("aws_network_acl.foos", &networkAcl),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.#", "1"),
resource.TestCheckResourceAttr(
"aws_network_acl.foos", "ingress.1296304962.ipv6_cidr_block", "2600:1f16:d1e:9a00::/56"),
),
},
},
})
}
func TestAccAWSNetworkAcl_espProtocol(t *testing.T) { func TestAccAWSNetworkAcl_espProtocol(t *testing.T) {
var networkAcl ec2.NetworkAcl var networkAcl ec2.NetworkAcl
@ -436,6 +461,33 @@ resource "aws_network_acl" "foos" {
} }
` `
const testAccAWSNetworkAclIpv6VpcConfig = `
provider "aws" {
region = "us-east-2"
}
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
Name = "TestAccAWSNetworkAcl_ipv6VpcRules"
}
}
resource "aws_network_acl" "foos" {
vpc_id = "${aws_vpc.foo.id}"
ingress = {
protocol = "tcp"
rule_no = 1
action = "allow"
ipv6_cidr_block = "2600:1f16:d1e:9a00::/56"
from_port = 0
to_port = 22
}
}
`
const testAccAWSNetworkAclIngressConfig = ` const testAccAWSNetworkAclIngressConfig = `
resource "aws_vpc" "foo" { resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16" cidr_block = "10.1.0.0/16"

View File

@ -565,6 +565,10 @@ func resourceAwsOpsworksInstanceRead(d *schema.ResourceData, meta interface{}) e
for _, v := range instance.LayerIds { for _, v := range instance.LayerIds {
layerIds = append(layerIds, *v) layerIds = append(layerIds, *v)
} }
layerIds, err = sortListBasedonTFFile(layerIds, d, "layer_ids")
if err != nil {
return fmt.Errorf("[DEBUG] Error sorting layer_ids attribute: %#v", err)
}
if err := d.Set("layer_ids", layerIds); err != nil { if err := d.Set("layer_ids", layerIds); err != nil {
return fmt.Errorf("[DEBUG] Error setting layer_ids attribute: %#v, error: %#v", layerIds, err) return fmt.Errorf("[DEBUG] Error setting layer_ids attribute: %#v, error: %#v", layerIds, err)
} }
@ -820,7 +824,6 @@ func resourceAwsOpsworksInstanceUpdate(d *schema.ResourceData, meta interface{})
if v, ok := d.GetOk("layer_ids"); ok { if v, ok := d.GetOk("layer_ids"); ok {
req.LayerIds = expandStringList(v.([]interface{})) req.LayerIds = expandStringList(v.([]interface{}))
} }
if v, ok := d.GetOk("os"); ok { if v, ok := d.GetOk("os"); ok {

View File

@ -3,14 +3,17 @@ package aws
import ( import (
"fmt" "fmt"
"log" "log"
"os"
"strings" "strings"
"time" "time"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/opsworks" "github.com/aws/aws-sdk-go/service/opsworks"
) )
@ -183,6 +186,11 @@ func resourceAwsOpsworksStack() *schema.Resource {
Computed: true, Computed: true,
Optional: true, Optional: true,
}, },
"stack_endpoint": {
Type: schema.TypeString,
Computed: true,
},
}, },
} }
} }
@ -254,6 +262,13 @@ func resourceAwsOpsworksSetStackCustomCookbooksSource(d *schema.ResourceData, v
func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn client := meta.(*AWSClient).opsworksconn
var conErr error
if v := d.Get("stack_endpoint").(string); v != "" {
client, conErr = opsworksConnForRegion(v, meta)
if conErr != nil {
return conErr
}
}
req := &opsworks.DescribeStacksInput{ req := &opsworks.DescribeStacksInput{
StackIds: []*string{ StackIds: []*string{
@ -263,16 +278,53 @@ func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) erro
log.Printf("[DEBUG] Reading OpsWorks stack: %s", d.Id()) log.Printf("[DEBUG] Reading OpsWorks stack: %s", d.Id())
resp, err := client.DescribeStacks(req) // notFound represents the number of times we've called DescribeStacks looking
if err != nil { // for this Stack. If it's not found in the the default region we're in, we
if awserr, ok := err.(awserr.Error); ok { // check us-east-1 in the event this stack was created with Terraform before
// version 0.9
// See https://github.com/hashicorp/terraform/issues/12842
var notFound int
var resp *opsworks.DescribeStacksOutput
var dErr error
for {
resp, dErr = client.DescribeStacks(req)
if dErr != nil {
if awserr, ok := dErr.(awserr.Error); ok {
if awserr.Code() == "ResourceNotFoundException" { if awserr.Code() == "ResourceNotFoundException" {
if notFound < 1 {
// If we haven't already, try us-east-1, legacy connection
notFound++
var connErr error
client, connErr = opsworksConnForRegion("us-east-1", meta)
if connErr != nil {
return connErr
}
// start again from the top of the FOR loop, but with a client
// configured to talk to us-east-1
continue
}
// We've tried both the original and us-east-1 endpoint, and the stack
// is still not found
log.Printf("[DEBUG] OpsWorks stack (%s) not found", d.Id()) log.Printf("[DEBUG] OpsWorks stack (%s) not found", d.Id())
d.SetId("") d.SetId("")
return nil return nil
} }
// not ResoureNotFoundException, fall through to returning error
} }
return err return dErr
}
// If the stack was found, set the stack_endpoint
if client.Config.Region != nil && *client.Config.Region != "" {
log.Printf("[DEBUG] Setting stack_endpoint for (%s) to (%s)", d.Id(), *client.Config.Region)
if err := d.Set("stack_endpoint", *client.Config.Region); err != nil {
log.Printf("[WARN] Error setting stack_endpoint: %s", err)
}
}
log.Printf("[DEBUG] Breaking stack endpoint search, found stack for (%s)", d.Id())
// Break the FOR loop
break
} }
stack := resp.Stacks[0] stack := resp.Stacks[0]
@ -309,6 +361,40 @@ func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) erro
return nil return nil
} }
// opsworksConn will return a connection for the stack_endpoint in the
// configuration. Stacks can only be accessed or managed within the endpoint
// in which they are created, so we allow users to specify an original endpoint
// for Stacks created before multiple endpoints were offered (Terraform v0.9.0).
// See:
// - https://github.com/hashicorp/terraform/pull/12688
// - https://github.com/hashicorp/terraform/issues/12842
func opsworksConnForRegion(region string, meta interface{}) (*opsworks.OpsWorks, error) {
originalConn := meta.(*AWSClient).opsworksconn
// Regions are the same, no need to reconfigure
if originalConn.Config.Region != nil && *originalConn.Config.Region == region {
return originalConn, nil
}
// Set up base session
sess, err := session.NewSession(&originalConn.Config)
if err != nil {
return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
}
sess.Handlers.Build.PushBackNamed(addTerraformVersionToUserAgent)
if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" {
sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure)
}
newSession := sess.Copy(&aws.Config{Region: aws.String(region)})
newOpsworksconn := opsworks.New(newSession)
log.Printf("[DEBUG] Returning new OpsWorks client")
return newOpsworksconn, nil
}
func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn client := meta.(*AWSClient).opsworksconn
@ -396,6 +482,13 @@ func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) er
func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) error { func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn client := meta.(*AWSClient).opsworksconn
var conErr error
if v := d.Get("stack_endpoint").(string); v != "" {
client, conErr = opsworksConnForRegion(v, meta)
if conErr != nil {
return conErr
}
}
err := resourceAwsOpsworksStackValidate(d) err := resourceAwsOpsworksStackValidate(d)
if err != nil { if err != nil {
@ -456,6 +549,13 @@ func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) er
func resourceAwsOpsworksStackDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsOpsworksStackDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient).opsworksconn client := meta.(*AWSClient).opsworksconn
var conErr error
if v := d.Get("stack_endpoint").(string); v != "" {
client, conErr = opsworksConnForRegion(v, meta)
if conErr != nil {
return conErr
}
}
req := &opsworks.DeleteStackInput{ req := &opsworks.DeleteStackInput{
StackId: aws.String(d.Id()), StackId: aws.String(d.Id()),

View File

@ -74,6 +74,205 @@ func TestAccAWSOpsworksStackVpc(t *testing.T) {
}) })
} }
// Tests the addition of regional endpoints and supporting the classic link used
// to create Stack's prior to v0.9.0.
// See https://github.com/hashicorp/terraform/issues/12842
func TestAccAWSOpsWorksStack_classic_endpoints(t *testing.T) {
stackName := fmt.Sprintf("tf-opsworks-acc-%d", acctest.RandInt())
rInt := acctest.RandInt()
var opsstack opsworks.Stack
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsOpsworksStackDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAwsOpsWorksStack_classic_endpoint(stackName, rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSOpsworksStackExists(
"aws_opsworks_stack.main", false, &opsstack),
),
},
// Ensure that changing to us-west-2 region results in no plan
resource.TestStep{
Config: testAccAwsOpsWorksStack_regional_endpoint(stackName, rInt),
PlanOnly: true,
},
},
})
}
func testAccAwsOpsWorksStack_classic_endpoint(rName string, rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-east-1"
}
resource "aws_opsworks_stack" "main" {
name = "%s"
region = "us-west-2"
service_role_arn = "${aws_iam_role.opsworks_service.arn}"
default_instance_profile_arn = "${aws_iam_instance_profile.opsworks_instance.arn}"
configuration_manager_version = "12"
default_availability_zone = "us-west-2b"
}
resource "aws_iam_role" "opsworks_service" {
name = "tf_opsworks_service_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "opsworks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_role_policy" "opsworks_service" {
name = "tf_opsworks_service_%d"
role = "${aws_iam_role.opsworks_service.id}"
policy = <<EOT
{
"Statement": [
{
"Action": [
"ec2:*",
"iam:PassRole",
"cloudwatch:GetMetricStatistics",
"elasticloadbalancing:*",
"rds:*"
],
"Effect": "Allow",
"Resource": ["*"]
}
]
}
EOT
}
resource "aws_iam_role" "opsworks_instance" {
name = "tf_opsworks_instance_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_instance_profile" "opsworks_instance" {
name = "%s_profile"
roles = ["${aws_iam_role.opsworks_instance.name}"]
}`, rName, rInt, rInt, rInt, rName)
}
func testAccAwsOpsWorksStack_regional_endpoint(rName string, rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-west-2"
}
resource "aws_opsworks_stack" "main" {
name = "%s"
region = "us-west-2"
service_role_arn = "${aws_iam_role.opsworks_service.arn}"
default_instance_profile_arn = "${aws_iam_instance_profile.opsworks_instance.arn}"
configuration_manager_version = "12"
default_availability_zone = "us-west-2b"
}
resource "aws_iam_role" "opsworks_service" {
name = "tf_opsworks_service_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "opsworks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_role_policy" "opsworks_service" {
name = "tf_opsworks_service_%d"
role = "${aws_iam_role.opsworks_service.id}"
policy = <<EOT
{
"Statement": [
{
"Action": [
"ec2:*",
"iam:PassRole",
"cloudwatch:GetMetricStatistics",
"elasticloadbalancing:*",
"rds:*"
],
"Effect": "Allow",
"Resource": ["*"]
}
]
}
EOT
}
resource "aws_iam_role" "opsworks_instance" {
name = "tf_opsworks_instance_%d"
assume_role_policy = <<EOT
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOT
}
resource "aws_iam_instance_profile" "opsworks_instance" {
name = "%s_profile"
roles = ["${aws_iam_role.opsworks_instance.name}"]
}`, rName, rInt, rInt, rInt, rName)
}
//////////////////////////// ////////////////////////////
//// Checkers and Utilities //// Checkers and Utilities
//////////////////////////// ////////////////////////////

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/aws/aws-sdk-go/service/ses" "github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -42,7 +43,7 @@ func testAccCheckSESConfigurationSetDestroy(s *terraform.State) error {
found := false found := false
for _, element := range response.ConfigurationSets { for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" { if *element.Name == fmt.Sprintf("some-configuration-set-%d", escRandomInteger) {
found = true found = true
} }
} }
@ -77,7 +78,7 @@ func testAccCheckAwsSESConfigurationSetExists(n string) resource.TestCheckFunc {
found := false found := false
for _, element := range response.ConfigurationSets { for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" { if *element.Name == fmt.Sprintf("some-configuration-set-%d", escRandomInteger) {
found = true found = true
} }
} }
@ -90,8 +91,9 @@ func testAccCheckAwsSESConfigurationSetExists(n string) resource.TestCheckFunc {
} }
} }
const testAccAWSSESConfigurationSetConfig = ` var escRandomInteger = acctest.RandInt()
var testAccAWSSESConfigurationSetConfig = fmt.Sprintf(`
resource "aws_ses_configuration_set" "test" { resource "aws_ses_configuration_set" "test" {
name = "some-configuration-set" name = "some-configuration-set-%d"
} }
` `, escRandomInteger)

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/aws/aws-sdk-go/service/ses" "github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -46,7 +47,7 @@ func testAccCheckSESEventDestinationDestroy(s *terraform.State) error {
found := false found := false
for _, element := range response.ConfigurationSets { for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" { if *element.Name == fmt.Sprintf("some-configuration-set-%d", edRandomInteger) {
found = true found = true
} }
} }
@ -81,7 +82,7 @@ func testAccCheckAwsSESEventDestinationExists(n string) resource.TestCheckFunc {
found := false found := false
for _, element := range response.ConfigurationSets { for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" { if *element.Name == fmt.Sprintf("some-configuration-set-%d", edRandomInteger) {
found = true found = true
} }
} }
@ -94,7 +95,8 @@ func testAccCheckAwsSESEventDestinationExists(n string) resource.TestCheckFunc {
} }
} }
const testAccAWSSESEventDestinationConfig = ` var edRandomInteger = acctest.RandInt()
var testAccAWSSESEventDestinationConfig = fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" { resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-format" bucket = "tf-test-bucket-format"
acl = "private" acl = "private"
@ -155,7 +157,7 @@ data "aws_iam_policy_document" "fh_felivery_document" {
} }
resource "aws_ses_configuration_set" "test" { resource "aws_ses_configuration_set" "test" {
name = "some-configuration-set" name = "some-configuration-set-%d"
} }
resource "aws_ses_event_destination" "kinesis" { resource "aws_ses_event_destination" "kinesis" {
@ -182,4 +184,4 @@ resource "aws_ses_event_destination" "cloudwatch" {
value_source = "emailHeader" value_source = "emailHeader"
} }
} }
` `, edRandomInteger)

View File

@ -443,7 +443,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
addHeaderAction := map[string]interface{}{ addHeaderAction := map[string]interface{}{
"header_name": *element.AddHeaderAction.HeaderName, "header_name": *element.AddHeaderAction.HeaderName,
"header_value": *element.AddHeaderAction.HeaderValue, "header_value": *element.AddHeaderAction.HeaderValue,
"position": i, "position": i + 1,
} }
addHeaderActionList = append(addHeaderActionList, addHeaderAction) addHeaderActionList = append(addHeaderActionList, addHeaderAction)
} }
@ -453,7 +453,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
"message": *element.BounceAction.Message, "message": *element.BounceAction.Message,
"sender": *element.BounceAction.Sender, "sender": *element.BounceAction.Sender,
"smtp_reply_code": *element.BounceAction.SmtpReplyCode, "smtp_reply_code": *element.BounceAction.SmtpReplyCode,
"position": i, "position": i + 1,
} }
if element.BounceAction.StatusCode != nil { if element.BounceAction.StatusCode != nil {
@ -470,7 +470,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
if element.LambdaAction != nil { if element.LambdaAction != nil {
lambdaAction := map[string]interface{}{ lambdaAction := map[string]interface{}{
"function_arn": *element.LambdaAction.FunctionArn, "function_arn": *element.LambdaAction.FunctionArn,
"position": i, "position": i + 1,
} }
if element.LambdaAction.InvocationType != nil { if element.LambdaAction.InvocationType != nil {
@ -487,7 +487,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
if element.S3Action != nil { if element.S3Action != nil {
s3Action := map[string]interface{}{ s3Action := map[string]interface{}{
"bucket_name": *element.S3Action.BucketName, "bucket_name": *element.S3Action.BucketName,
"position": i, "position": i + 1,
} }
if element.S3Action.KmsKeyArn != nil { if element.S3Action.KmsKeyArn != nil {
@ -508,7 +508,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
if element.SNSAction != nil { if element.SNSAction != nil {
snsAction := map[string]interface{}{ snsAction := map[string]interface{}{
"topic_arn": *element.SNSAction.TopicArn, "topic_arn": *element.SNSAction.TopicArn,
"position": i, "position": i + 1,
} }
snsActionList = append(snsActionList, snsAction) snsActionList = append(snsActionList, snsAction)
@ -517,7 +517,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
if element.StopAction != nil { if element.StopAction != nil {
stopAction := map[string]interface{}{ stopAction := map[string]interface{}{
"scope": *element.StopAction.Scope, "scope": *element.StopAction.Scope,
"position": i, "position": i + 1,
} }
if element.StopAction.TopicArn != nil { if element.StopAction.TopicArn != nil {
@ -530,7 +530,7 @@ func resourceAwsSesReceiptRuleRead(d *schema.ResourceData, meta interface{}) err
if element.WorkmailAction != nil { if element.WorkmailAction != nil {
workmailAction := map[string]interface{}{ workmailAction := map[string]interface{}{
"organization_arn": *element.WorkmailAction.OrganizationArn, "organization_arn": *element.WorkmailAction.OrganizationArn,
"position": i, "position": i + 1,
} }
if element.WorkmailAction.TopicArn != nil { if element.WorkmailAction.TopicArn != nil {

View File

@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ses" "github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -111,7 +112,7 @@ func testAccCheckAwsSESReceiptRuleExists(n string) resource.TestCheckFunc {
params := &ses.DescribeReceiptRuleInput{ params := &ses.DescribeReceiptRuleInput{
RuleName: aws.String("basic"), RuleName: aws.String("basic"),
RuleSetName: aws.String("test-me"), RuleSetName: aws.String(fmt.Sprintf("test-me-%d", srrsRandomInt)),
} }
response, err := conn.DescribeReceiptRule(params) response, err := conn.DescribeReceiptRule(params)
@ -153,7 +154,7 @@ func testAccCheckAwsSESReceiptRuleOrder(n string) resource.TestCheckFunc {
conn := testAccProvider.Meta().(*AWSClient).sesConn conn := testAccProvider.Meta().(*AWSClient).sesConn
params := &ses.DescribeReceiptRuleSetInput{ params := &ses.DescribeReceiptRuleSetInput{
RuleSetName: aws.String("test-me"), RuleSetName: aws.String(fmt.Sprintf("test-me-%d", srrsRandomInt)),
} }
response, err := conn.DescribeReceiptRuleSet(params) response, err := conn.DescribeReceiptRuleSet(params)
@ -185,8 +186,8 @@ func testAccCheckAwsSESReceiptRuleActions(n string) resource.TestCheckFunc {
conn := testAccProvider.Meta().(*AWSClient).sesConn conn := testAccProvider.Meta().(*AWSClient).sesConn
params := &ses.DescribeReceiptRuleInput{ params := &ses.DescribeReceiptRuleInput{
RuleName: aws.String("actions"), RuleName: aws.String("actions4"),
RuleSetName: aws.String("test-me"), RuleSetName: aws.String(fmt.Sprintf("test-me-%d", srrsRandomInt)),
} }
response, err := conn.DescribeReceiptRule(params) response, err := conn.DescribeReceiptRule(params)
@ -227,9 +228,10 @@ func testAccCheckAwsSESReceiptRuleActions(n string) resource.TestCheckFunc {
} }
} }
const testAccAWSSESReceiptRuleBasicConfig = ` var srrsRandomInt = acctest.RandInt()
var testAccAWSSESReceiptRuleBasicConfig = fmt.Sprintf(`
resource "aws_ses_receipt_rule_set" "test" { resource "aws_ses_receipt_rule_set" "test" {
rule_set_name = "test-me" rule_set_name = "test-me-%d"
} }
resource "aws_ses_receipt_rule" "basic" { resource "aws_ses_receipt_rule" "basic" {
@ -240,11 +242,11 @@ resource "aws_ses_receipt_rule" "basic" {
scan_enabled = true scan_enabled = true
tls_policy = "Require" tls_policy = "Require"
} }
` `, srrsRandomInt)
const testAccAWSSESReceiptRuleOrderConfig = ` var testAccAWSSESReceiptRuleOrderConfig = fmt.Sprintf(`
resource "aws_ses_receipt_rule_set" "test" { resource "aws_ses_receipt_rule_set" "test" {
rule_set_name = "test-me" rule_set_name = "test-me-%d"
} }
resource "aws_ses_receipt_rule" "second" { resource "aws_ses_receipt_rule" "second" {
@ -257,19 +259,19 @@ resource "aws_ses_receipt_rule" "first" {
name = "first" name = "first"
rule_set_name = "${aws_ses_receipt_rule_set.test.rule_set_name}" rule_set_name = "${aws_ses_receipt_rule_set.test.rule_set_name}"
} }
` `, srrsRandomInt)
const testAccAWSSESReceiptRuleActionsConfig = ` var testAccAWSSESReceiptRuleActionsConfig = fmt.Sprintf(`
resource "aws_s3_bucket" "emails" { resource "aws_s3_bucket" "emails" {
bucket = "ses-terraform-emails" bucket = "ses-terraform-emails"
} }
resource "aws_ses_receipt_rule_set" "test" { resource "aws_ses_receipt_rule_set" "test" {
rule_set_name = "test-me" rule_set_name = "test-me-%d"
} }
resource "aws_ses_receipt_rule" "actions" { resource "aws_ses_receipt_rule" "actions" {
name = "actions" name = "actions4"
rule_set_name = "${aws_ses_receipt_rule_set.test.rule_set_name}" rule_set_name = "${aws_ses_receipt_rule_set.test.rule_set_name}"
add_header_action { add_header_action {
@ -289,4 +291,4 @@ resource "aws_ses_receipt_rule" "actions" {
position = 2 position = 2
} }
} }
` `, srrsRandomInt)

View File

@ -22,6 +22,9 @@ func resourceAwsVpc() *schema.Resource {
State: resourceAwsVpcInstanceImport, State: resourceAwsVpcInstanceImport,
}, },
SchemaVersion: 1,
MigrateState: resourceAwsVpcMigrateState,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"cidr_block": { "cidr_block": {
Type: schema.TypeString, Type: schema.TypeString,

View File

@ -0,0 +1,33 @@
package aws
import (
"fmt"
"log"
"github.com/hashicorp/terraform/terraform"
)
func resourceAwsVpcMigrateState(
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
switch v {
case 0:
log.Println("[INFO] Found AWS VPC State v0; migrating to v1")
return migrateVpcStateV0toV1(is)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
}
func migrateVpcStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) {
if is.Empty() || is.Attributes == nil {
log.Println("[DEBUG] Empty VPC State; nothing to migrate.")
return is, nil
}
log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes)
is.Attributes["assign_generated_ipv6_cidr_block"] = "false"
log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes)
return is, nil
}

View File

@ -0,0 +1,49 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestAWSVpcMigrateState(t *testing.T) {
cases := map[string]struct {
StateVersion int
ID string
Attributes map[string]string
Expected string
Meta interface{}
}{
"v0_1": {
StateVersion: 0,
ID: "some_id",
Attributes: map[string]string{
"assign_generated_ipv6_cidr_block": "true",
},
Expected: "false",
},
"v0_1_without_value": {
StateVersion: 0,
ID: "some_id",
Attributes: map[string]string{},
Expected: "false",
},
}
for tn, tc := range cases {
is := &terraform.InstanceState{
ID: tc.ID,
Attributes: tc.Attributes,
}
is, err := resourceAwsVpcMigrateState(
tc.StateVersion, is, tc.Meta)
if err != nil {
t.Fatalf("bad: %s, err: %#v", tn, err)
}
if is.Attributes["assign_generated_ipv6_cidr_block"] != tc.Expected {
t.Fatalf("bad VPC Migrate: %s\n\n expected: %s", is.Attributes["assign_generated_ipv6_cidr_block"], tc.Expected)
}
}
}

View File

@ -1490,6 +1490,22 @@ func sortInterfaceSlice(in []interface{}) []interface{} {
return b return b
} }
// This function sorts List A to look like a list found in the tf file.
func sortListBasedonTFFile(in []string, d *schema.ResourceData, listName string) ([]string, error) {
if attributeCount, ok := d.Get(listName + ".#").(int); ok {
for i := 0; i < attributeCount; i++ {
currAttributeId := d.Get(listName + "." + strconv.Itoa(i))
for j := 0; j < len(in); j++ {
if currAttributeId == in[j] {
in[i], in[j] = in[j], in[i]
}
}
}
return in, nil
}
return in, fmt.Errorf("Could not find list: %s", listName)
}
func flattenApiGatewayThrottleSettings(settings *apigateway.ThrottleSettings) []map[string]interface{} { func flattenApiGatewayThrottleSettings(settings *apigateway.ThrottleSettings) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1) result := make([]map[string]interface{}, 0, 1)

View File

@ -13,7 +13,7 @@ import (
func TestAccAzureRMTemplateDeployment_basic(t *testing.T) { func TestAccAzureRMTemplateDeployment_basic(t *testing.T) {
ri := acctest.RandInt() ri := acctest.RandInt()
config := fmt.Sprintf(testAccAzureRMTemplateDeployment_basicExample, ri, ri) config := fmt.Sprintf(testAccAzureRMTemplateDeployment_basicMultiple, ri, ri)
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -31,7 +31,7 @@ func TestAccAzureRMTemplateDeployment_basic(t *testing.T) {
func TestAccAzureRMTemplateDeployment_disappears(t *testing.T) { func TestAccAzureRMTemplateDeployment_disappears(t *testing.T) {
ri := acctest.RandInt() ri := acctest.RandInt()
config := fmt.Sprintf(testAccAzureRMTemplateDeployment_basicExample, ri, ri) config := fmt.Sprintf(testAccAzureRMTemplateDeployment_basicSingle, ri, ri, ri)
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -163,7 +163,47 @@ func testCheckAzureRMTemplateDeploymentDestroy(s *terraform.State) error {
return nil return nil
} }
var testAccAzureRMTemplateDeployment_basicExample = ` var testAccAzureRMTemplateDeployment_basicSingle = `
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "West US"
}
resource "azurerm_template_deployment" "test" {
name = "acctesttemplate-%d"
resource_group_name = "${azurerm_resource_group.test.name}"
template_body = <<DEPLOY
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"variables": {
"location": "[resourceGroup().location]",
"publicIPAddressType": "Dynamic",
"apiVersion": "2015-06-15",
"dnsLabelPrefix": "[concat('terraform-tdacctest', uniquestring(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "[variables('apiVersion')]",
"name": "acctestpip-%d",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
"dnsSettings": {
"domainNameLabel": "[variables('dnsLabelPrefix')]"
}
}
}
]
}
DEPLOY
deployment_mode = "Complete"
}
`
var testAccAzureRMTemplateDeployment_basicMultiple = `
resource "azurerm_resource_group" "test" { resource "azurerm_resource_group" "test" {
name = "acctestRG-%d" name = "acctestRG-%d"
location = "West US" location = "West US"
@ -196,7 +236,7 @@ var testAccAzureRMTemplateDeployment_basicExample = `
"publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]", "publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"apiVersion": "2015-06-15", "apiVersion": "2015-06-15",
"dnsLabelPrefix": "terraform-tdacctest" "dnsLabelPrefix": "[concat('terraform-tdacctest', uniquestring(resourceGroup().id))]"
}, },
"resources": [ "resources": [
{ {

View File

@ -25,6 +25,7 @@ const (
const ( const (
apiCheckTypeCAQL circonusCheckType = "caql" apiCheckTypeCAQL circonusCheckType = "caql"
apiCheckTypeConsul circonusCheckType = "consul"
apiCheckTypeICMPPing circonusCheckType = "ping_icmp" apiCheckTypeICMPPing circonusCheckType = "ping_icmp"
apiCheckTypeHTTP circonusCheckType = "http" apiCheckTypeHTTP circonusCheckType = "http"
apiCheckTypeJSON circonusCheckType = "json" apiCheckTypeJSON circonusCheckType = "json"
@ -108,15 +109,24 @@ func (c *circonusCheck) Fixup() error {
} }
func (c *circonusCheck) Validate() error { func (c *circonusCheck) Validate() error {
if len(c.Metrics) == 0 {
return fmt.Errorf("At least one %s must be specified", checkMetricAttr)
}
if c.Timeout > float32(c.Period) { if c.Timeout > float32(c.Period) {
return fmt.Errorf("Timeout (%f) can not exceed period (%d)", c.Timeout, c.Period) return fmt.Errorf("Timeout (%f) can not exceed period (%d)", c.Timeout, c.Period)
} }
// Check-type specific validation
switch apiCheckType(c.Type) { switch apiCheckType(c.Type) {
case apiCheckTypeCloudWatchAttr: case apiCheckTypeCloudWatchAttr:
if !(c.Period == 60 || c.Period == 300) { if !(c.Period == 60 || c.Period == 300) {
return fmt.Errorf("Period must be either 1m or 5m for a %s check", apiCheckTypeCloudWatchAttr) return fmt.Errorf("Period must be either 1m or 5m for a %s check", apiCheckTypeCloudWatchAttr)
} }
case apiCheckTypeConsulAttr:
if v, found := c.Config[config.URL]; !found || v == "" {
return fmt.Errorf("%s must have at least one check mode set: %s, %s, or %s must be set", checkConsulAttr, checkConsulServiceAttr, checkConsulNodeAttr, checkConsulStateAttr)
}
} }
return nil return nil

View File

@ -17,6 +17,19 @@ const (
providerAutoTagAttr = "auto_tag" providerAutoTagAttr = "auto_tag"
providerKeyAttr = "key" providerKeyAttr = "key"
apiConsulCheckBlacklist = "check_name_blacklist"
apiConsulDatacenterAttr = "dc"
apiConsulNodeBlacklist = "node_blacklist"
apiConsulServiceBlacklist = "service_blacklist"
apiConsulStaleAttr = "stale"
checkConsulTokenHeader = `X-Consul-Token`
checkConsulV1NodePrefix = "node"
checkConsulV1Prefix = "/v1/health"
checkConsulV1ServicePrefix = "service"
checkConsulV1StatePrefix = "state"
defaultCheckConsulHTTPAddr = "http://consul.service.consul"
defaultCheckConsulPort = "8500"
defaultCheckJSONMethod = "GET" defaultCheckJSONMethod = "GET"
defaultCheckJSONPort = "443" defaultCheckJSONPort = "443"
defaultCheckJSONVersion = "1.1" defaultCheckJSONVersion = "1.1"

View File

@ -33,21 +33,22 @@ const (
checkCAQLAttr = "caql" checkCAQLAttr = "caql"
checkCloudWatchAttr = "cloudwatch" checkCloudWatchAttr = "cloudwatch"
checkCollectorAttr = "collector" checkCollectorAttr = "collector"
checkConsulAttr = "consul"
checkHTTPAttr = "http" checkHTTPAttr = "http"
checkHTTPTrapAttr = "httptrap" checkHTTPTrapAttr = "httptrap"
checkICMPPingAttr = "icmp_ping" checkICMPPingAttr = "icmp_ping"
checkJSONAttr = "json" checkJSONAttr = "json"
checkMetricAttr = "metric"
checkMetricLimitAttr = "metric_limit" checkMetricLimitAttr = "metric_limit"
checkMySQLAttr = "mysql" checkMySQLAttr = "mysql"
checkNameAttr = "name" checkNameAttr = "name"
checkNotesAttr = "notes" checkNotesAttr = "notes"
checkPeriodAttr = "period" checkPeriodAttr = "period"
checkPostgreSQLAttr = "postgresql" checkPostgreSQLAttr = "postgresql"
checkMetricAttr = "metric"
checkStatsdAttr = "statsd" checkStatsdAttr = "statsd"
checkTCPAttr = "tcp"
checkTagsAttr = "tags" checkTagsAttr = "tags"
checkTargetAttr = "target" checkTargetAttr = "target"
checkTCPAttr = "tcp"
checkTimeoutAttr = "timeout" checkTimeoutAttr = "timeout"
checkTypeAttr = "type" checkTypeAttr = "type"
@ -75,6 +76,7 @@ const (
// Circonus API constants from their API endpoints // Circonus API constants from their API endpoints
apiCheckTypeCAQLAttr apiCheckType = "caql" apiCheckTypeCAQLAttr apiCheckType = "caql"
apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch" apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch"
apiCheckTypeConsulAttr apiCheckType = "consul"
apiCheckTypeHTTPAttr apiCheckType = "http" apiCheckTypeHTTPAttr apiCheckType = "http"
apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap" apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap"
apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp" apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp"
@ -90,6 +92,7 @@ var checkDescriptions = attrDescrs{
checkCAQLAttr: "CAQL check configuration", checkCAQLAttr: "CAQL check configuration",
checkCloudWatchAttr: "CloudWatch check configuration", checkCloudWatchAttr: "CloudWatch check configuration",
checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics", checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics",
checkConsulAttr: "Consul check configuration",
checkHTTPAttr: "HTTP check configuration", checkHTTPAttr: "HTTP check configuration",
checkHTTPTrapAttr: "HTTP Trap check configuration", checkHTTPTrapAttr: "HTTP Trap check configuration",
checkICMPPingAttr: "ICMP ping check configuration", checkICMPPingAttr: "ICMP ping check configuration",
@ -157,6 +160,7 @@ func resourceCheck() *schema.Resource {
}), }),
}, },
}, },
checkConsulAttr: schemaCheckConsul,
checkHTTPAttr: schemaCheckHTTP, checkHTTPAttr: schemaCheckHTTP,
checkHTTPTrapAttr: schemaCheckHTTPTrap, checkHTTPTrapAttr: schemaCheckHTTPTrap,
checkJSONAttr: schemaCheckJSON, checkJSONAttr: schemaCheckJSON,
@ -577,6 +581,7 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error {
checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{ checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{
checkCAQLAttr: checkConfigToAPICAQL, checkCAQLAttr: checkConfigToAPICAQL,
checkCloudWatchAttr: checkConfigToAPICloudWatch, checkCloudWatchAttr: checkConfigToAPICloudWatch,
checkConsulAttr: checkConfigToAPIConsul,
checkHTTPAttr: checkConfigToAPIHTTP, checkHTTPAttr: checkConfigToAPIHTTP,
checkHTTPTrapAttr: checkConfigToAPIHTTPTrap, checkHTTPTrapAttr: checkConfigToAPIHTTPTrap,
checkICMPPingAttr: checkConfigToAPIICMPPing, checkICMPPingAttr: checkConfigToAPIICMPPing,
@ -589,9 +594,18 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error {
for checkType, fn := range checkTypeParseMap { for checkType, fn := range checkTypeParseMap {
if listRaw, found := d.GetOk(checkType); found { if listRaw, found := d.GetOk(checkType); found {
if err := fn(c, listRaw.(*schema.Set).List()); err != nil { switch u := listRaw.(type) {
case []interface{}:
if err := fn(c, u); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err) return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err)
} }
case *schema.Set:
if err := fn(c, u.List()); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err)
}
default:
return fmt.Errorf("PROVIDER BUG: unsupported check type interface: %q", checkType)
}
} }
} }
@ -604,6 +618,7 @@ func parseCheckTypeConfig(c *circonusCheck, d *schema.ResourceData) error {
checkTypeConfigHandlers := map[apiCheckType]func(*circonusCheck, *schema.ResourceData) error{ checkTypeConfigHandlers := map[apiCheckType]func(*circonusCheck, *schema.ResourceData) error{
apiCheckTypeCAQLAttr: checkAPIToStateCAQL, apiCheckTypeCAQLAttr: checkAPIToStateCAQL,
apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch, apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch,
apiCheckTypeConsulAttr: checkAPIToStateConsul,
apiCheckTypeHTTPAttr: checkAPIToStateHTTP, apiCheckTypeHTTPAttr: checkAPIToStateHTTP,
apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap, apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap,
apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing, apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing,

View File

@ -0,0 +1,412 @@
package circonus
import (
"fmt"
"net"
"net/url"
"regexp"
"strings"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
// circonus_check.consul.* resource attribute names
checkConsulACLTokenAttr = "acl_token"
checkConsulAllowStaleAttr = "allow_stale"
checkConsulCAChainAttr = "ca_chain"
checkConsulCertFileAttr = "certificate_file"
checkConsulCheckNameBlacklistAttr = "check_blacklist"
checkConsulCiphersAttr = "ciphers"
checkConsulDatacenterAttr = "dc"
checkConsulHTTPAddrAttr = "http_addr"
checkConsulHeadersAttr = "headers"
checkConsulKeyFileAttr = "key_file"
checkConsulNodeAttr = "node"
checkConsulNodeBlacklistAttr = "node_blacklist"
checkConsulServiceAttr = "service"
checkConsulServiceNameBlacklistAttr = "service_blacklist"
checkConsulStateAttr = "state"
)
var checkConsulDescriptions = attrDescrs{
checkConsulACLTokenAttr: "A Consul ACL token",
checkConsulAllowStaleAttr: "Allow Consul to read from a non-leader system",
checkConsulCAChainAttr: "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
checkConsulCertFileAttr: "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
checkConsulCheckNameBlacklistAttr: "A blacklist of check names to exclude from metric results",
checkConsulCiphersAttr: "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
checkConsulDatacenterAttr: "The Consul datacenter to extract health information from",
checkConsulHeadersAttr: "Map of HTTP Headers to send along with HTTP Requests",
checkConsulHTTPAddrAttr: "The HTTP Address of a Consul agent to query",
checkConsulKeyFileAttr: "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
checkConsulNodeAttr: "Node Name or NodeID of a Consul agent",
checkConsulNodeBlacklistAttr: "A blacklist of node names or IDs to exclude from metric results",
checkConsulServiceAttr: "Name of the Consul service to check",
checkConsulServiceNameBlacklistAttr: "A blacklist of service names to exclude from metric results",
checkConsulStateAttr: "Check for Consul services in this particular state",
}
var consulHealthCheckRE = regexp.MustCompile(fmt.Sprintf(`^%s/(%s|%s|%s)/(.+)`, checkConsulV1Prefix, checkConsulV1NodePrefix, checkConsulV1ServicePrefix, checkConsulV1StatePrefix))
var schemaCheckConsul = &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkConsulDescriptions, map[schemaAttr]*schema.Schema{
checkConsulACLTokenAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulACLTokenAttr, `^[a-zA-Z0-9\-]+$`),
},
checkConsulAllowStaleAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
checkConsulCAChainAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCAChainAttr, `.+`),
},
checkConsulCertFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCertFileAttr, `.+`),
},
checkConsulCheckNameBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulCheckNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulCiphersAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCiphersAttr, `.+`),
},
checkConsulDatacenterAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCertFileAttr, `^[a-zA-Z0-9]+$`),
},
checkConsulHTTPAddrAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckConsulHTTPAddr,
ValidateFunc: validateHTTPURL(checkConsulHTTPAddrAttr, urlIsAbs|urlWithoutPath),
},
checkConsulHeadersAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateHTTPHeaders,
},
checkConsulKeyFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulKeyFileAttr, `.+`),
},
checkConsulNodeAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulNodeAttr, `^[a-zA-Z0-9_\-]+$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulServiceAttr,
checkConsulAttr + "." + checkConsulStateAttr,
},
},
checkConsulNodeBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulNodeBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulServiceAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulServiceAttr, `^[a-zA-Z0-9_\-]+$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulNodeAttr,
checkConsulAttr + "." + checkConsulStateAttr,
},
},
checkConsulServiceNameBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulServiceNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulStateAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulStateAttr, `^(any|passing|warning|critical)$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulNodeAttr,
checkConsulAttr + "." + checkConsulServiceAttr,
},
},
}),
},
}
// checkAPIToStateConsul reads the Config data out of circonusCheck.CheckBundle into
// the statefile.
func checkAPIToStateConsul(c *circonusCheck, d *schema.ResourceData) error {
consulConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, s := range c.Config {
swamp[k] = s
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok && s != "" {
consulConfig[string(attrName)] = s
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.CAChain, checkConsulCAChainAttr)
saveStringConfigToState(config.CertFile, checkConsulCertFileAttr)
saveStringConfigToState(config.Ciphers, checkConsulCiphersAttr)
// httpAddrURL is used to compose the http_addr value using multiple c.Config
// values.
var httpAddrURL url.URL
headers := make(map[string]interface{}, len(c.Config)+1) // +1 is for the ACLToken
headerPrefixLen := len(config.HeaderPrefix)
// Explicitly handle several config parameters in sequence: URL, then port,
// then everything else.
if v, found := c.Config[config.URL]; found {
u, err := url.Parse(v)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %q from config: {{err}}", config.URL), err)
}
queryArgs := u.Query()
if vals, found := queryArgs[apiConsulStaleAttr]; found && len(vals) > 0 {
consulConfig[string(checkConsulAllowStaleAttr)] = true
}
if dc := queryArgs.Get(apiConsulDatacenterAttr); dc != "" {
consulConfig[string(checkConsulDatacenterAttr)] = dc
}
httpAddrURL.Host = u.Host
httpAddrURL.Scheme = u.Scheme
md := consulHealthCheckRE.FindStringSubmatch(u.EscapedPath())
if md == nil {
return fmt.Errorf("config %q failed to match the health regexp", config.URL)
}
checkMode := md[1]
checkArg := md[2]
switch checkMode {
case checkConsulV1NodePrefix:
consulConfig[string(checkConsulNodeAttr)] = checkArg
case checkConsulV1ServicePrefix:
consulConfig[string(checkConsulServiceAttr)] = checkArg
case checkConsulV1StatePrefix:
consulConfig[string(checkConsulStateAttr)] = checkArg
default:
return fmt.Errorf("PROVIDER BUG: unsupported check mode %q from %q", checkMode, u.EscapedPath())
}
delete(swamp, config.URL)
}
if v, found := c.Config[config.Port]; found {
hostInfo := strings.SplitN(httpAddrURL.Host, ":", 2)
switch {
case len(hostInfo) == 1 && v != defaultCheckConsulPort, len(hostInfo) > 1:
httpAddrURL.Host = net.JoinHostPort(hostInfo[0], v)
}
delete(swamp, config.Port)
}
if v, found := c.Config[apiConsulCheckBlacklist]; found {
consulConfig[checkConsulCheckNameBlacklistAttr] = strings.Split(v, ",")
}
if v, found := c.Config[apiConsulNodeBlacklist]; found {
consulConfig[checkConsulNodeBlacklistAttr] = strings.Split(v, ",")
}
if v, found := c.Config[apiConsulServiceBlacklist]; found {
consulConfig[checkConsulServiceNameBlacklistAttr] = strings.Split(v, ",")
}
// NOTE(sean@): headers attribute processed last. See below.
consulConfig[string(checkConsulHTTPAddrAttr)] = httpAddrURL.String()
saveStringConfigToState(config.KeyFile, checkConsulKeyFileAttr)
// Process the headers last in order to provide an escape hatch capible of
// overriding any other derived value above.
for k, v := range c.Config {
if len(k) <= headerPrefixLen {
continue
}
// Handle all of the prefix variable headers, like `header_`
if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
key := k[headerPrefixLen:]
switch key {
case checkConsulTokenHeader:
consulConfig[checkConsulACLTokenAttr] = v
default:
headers[string(key)] = v
}
}
delete(swamp, k)
}
consulConfig[string(checkConsulHeadersAttr)] = headers
whitelistedConfigKeys := map[config.Key]struct{}{
config.Port: struct{}{},
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
config.URL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
return fmt.Errorf("PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkConsulAttr, []interface{}{consulConfig}); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkConsulAttr), err)
}
return nil
}
func checkConfigToAPIConsul(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeConsul)
// Iterate over all `consul` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
consulConfig := newInterfaceMap(mapRaw)
if v, found := consulConfig[checkConsulCAChainAttr]; found {
c.Config[config.CAChain] = v.(string)
}
if v, found := consulConfig[checkConsulCertFileAttr]; found {
c.Config[config.CertFile] = v.(string)
}
if v, found := consulConfig[checkConsulCheckNameBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulCheckBlacklist] = strings.Join(checks, ",")
}
if v, found := consulConfig[checkConsulCiphersAttr]; found {
c.Config[config.Ciphers] = v.(string)
}
if headers := consulConfig.CollectMap(checkConsulHeadersAttr); headers != nil {
for k, v := range headers {
h := config.HeaderPrefix + config.Key(k)
c.Config[h] = v
}
}
if v, found := consulConfig[checkConsulKeyFileAttr]; found {
c.Config[config.KeyFile] = v.(string)
}
{
// Extract all of the input attributes necessary to construct the
// Consul agent's URL.
httpAddr := consulConfig[checkConsulHTTPAddrAttr].(string)
checkURL, err := url.Parse(httpAddr)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse %s's attribute %q: {{err}}", checkConsulAttr, httpAddr), err)
}
hostInfo := strings.SplitN(checkURL.Host, ":", 2)
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
if len(hostInfo) > 1 {
c.Config[config.Port] = hostInfo[1]
}
if v, found := consulConfig[checkConsulNodeAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1NodePrefix, v.(string)}, "/")
}
if v, found := consulConfig[checkConsulServiceAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1ServicePrefix, v.(string)}, "/")
}
if v, found := consulConfig[checkConsulStateAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1StatePrefix, v.(string)}, "/")
}
q := checkURL.Query()
if v, found := consulConfig[checkConsulAllowStaleAttr]; found && v.(bool) {
q.Set(apiConsulStaleAttr, "")
}
if v, found := consulConfig[checkConsulDatacenterAttr]; found && v.(string) != "" {
q.Set(apiConsulDatacenterAttr, v.(string))
}
checkURL.RawQuery = q.Encode()
c.Config[config.URL] = checkURL.String()
}
if v, found := consulConfig[checkConsulNodeBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulNodeBlacklist] = strings.Join(checks, ",")
}
if v, found := consulConfig[checkConsulServiceNameBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulServiceBlacklist] = strings.Join(checks, ",")
}
}
return nil
}

View File

@ -0,0 +1,282 @@
package circonus
import (
"fmt"
"regexp"
"testing"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckConsul_node(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=state check - %s", acctest.RandString(5))
checkNode := fmt.Sprintf("my-node-name-or-node-id-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthNodeFmt, checkName, checkNode),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.dc", "dc2"),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul:8501"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node", checkNode),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.0", "a"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.1", "bad"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.2", "node"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
func TestAccCirconusCheckConsul_service(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=service check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthServiceFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service", "consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.0", "bad"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.1", "hombre"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.2", "service"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
func TestAccCirconusCheckConsul_state(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=state check - %s", acctest.RandString(5))
checkState := "critical"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthStateFmt, checkName, checkState),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.state", checkState),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.0", "worthless"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.1", "check"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
const testAccCirconusCheckConsulConfigV1HealthNodeFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
dc = "dc2"
http_addr = "http://consul.service.consul:8501"
node = "%s"
node_blacklist = ["a","bad","node"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`
const testAccCirconusCheckConsulConfigV1HealthServiceFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
service = "consul"
service_blacklist = ["bad","hombre","service"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`
const testAccCirconusCheckConsulConfigV1HealthStateFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
state = "%s"
check_blacklist = ["worthless","check"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`

View File

@ -372,6 +372,10 @@ func checkConfigToAPIHTTP(c *circonusCheck, l interfaceList) error {
if len(c.Target) == 0 { if len(c.Target) == 0 {
c.Target = hostInfo[0] c.Target = hostInfo[0]
} }
if len(hostInfo) > 1 && c.Config[config.Port] == "" {
c.Config[config.Port] = hostInfo[1]
}
} }
if v, found := httpConfig[checkHTTPVersionAttr]; found { if v, found := httpConfig[checkHTTPVersionAttr]; found {

View File

@ -355,6 +355,10 @@ func checkConfigToAPIJSON(c *circonusCheck, l interfaceList) error {
if len(c.Target) == 0 { if len(c.Target) == 0 {
c.Target = hostInfo[0] c.Target = hostInfo[0]
} }
if len(hostInfo) > 1 && c.Config[config.Port] == "" {
c.Config[config.Port] = hostInfo[1]
}
} }
if v, found := jsonConfig[checkJSONVersionAttr]; found { if v, found := jsonConfig[checkJSONVersionAttr]; found {

View File

@ -314,6 +314,7 @@ type urlParseFlags int
const ( const (
urlIsAbs urlParseFlags = 1 << iota urlIsAbs urlParseFlags = 1 << iota
urlOptional urlOptional
urlWithoutPath
urlWithoutPort urlWithoutPort
urlWithoutSchema urlWithoutSchema
) )
@ -345,6 +346,10 @@ func validateHTTPURL(attrName schemaAttr, checkFlags urlParseFlags) func(v inter
errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string))) errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string)))
} }
if checkFlags&urlWithoutPath != 0 && u.Path != "" {
errors = append(errors, fmt.Errorf("Path is present on URL %q (HINT: drop the %s)", v.(string), u.Path))
}
if checkFlags&urlWithoutPort != 0 { if checkFlags&urlWithoutPort != 0 {
hostParts := strings.SplitN(u.Host, ":", 2) hostParts := strings.SplitN(u.Host, ":", 2)
if len(hostParts) != 1 { if len(hostParts) != 1 {

View File

@ -52,7 +52,7 @@ func (c *Config) Client() (*consulapi.Client, error) {
} else { } else {
username = c.HttpAuth username = c.HttpAuth
} }
config.HttpAuth = &consulapi.HttpBasicAuth{username, password} config.HttpAuth = &consulapi.HttpBasicAuth{Username: username, Password: password}
} }
if c.Token != "" { if c.Token != "" {

View File

@ -3,11 +3,17 @@ package digitalocean
import ( import (
"testing" "testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
) )
func TestAccDigitalOceanSSHKey_importBasic(t *testing.T) { func TestAccDigitalOceanSSHKey_importBasic(t *testing.T) {
resourceName := "digitalocean_ssh_key.foobar" resourceName := "digitalocean_ssh_key.foobar"
rInt := acctest.RandInt()
publicKeyMaterial, _, err := acctest.RandSSHKeyPair("digitalocean@ssh-acceptance-test")
if err != nil {
t.Fatalf("Cannot generate test SSH key pair: %s", err)
}
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -15,7 +21,7 @@ func TestAccDigitalOceanSSHKey_importBasic(t *testing.T) {
CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy, CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccCheckDigitalOceanSSHKeyConfig_basic(testAccValidImportPublicKey), Config: testAccCheckDigitalOceanSSHKeyConfig_basic(rInt, publicKeyMaterial),
}, },
{ {

View File

@ -322,8 +322,9 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{})
return fmt.Errorf("invalid droplet id: %v", err) return fmt.Errorf("invalid droplet id: %v", err)
} }
if d.HasChange("size") { resize_disk := d.Get("resize_disk").(bool)
oldSize, newSize := d.GetChange("size") if d.HasChange("size") || d.HasChange("resize_disk") && resize_disk {
newSize := d.Get("size")
_, _, err = client.DropletActions.PowerOff(id) _, _, err = client.DropletActions.PowerOff(id)
if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") { if err != nil && !strings.Contains(err.Error(), "Droplet is already powered off") {
@ -339,13 +340,7 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{})
} }
// Resize the droplet // Resize the droplet
resize_disk := d.Get("resize_disk") action, _, err := client.DropletActions.Resize(id, newSize.(string), resize_disk)
switch {
case resize_disk == true:
_, _, err = client.DropletActions.Resize(id, newSize.(string), true)
case resize_disk == false:
_, _, err = client.DropletActions.Resize(id, newSize.(string), false)
}
if err != nil { if err != nil {
newErr := powerOnAndWait(d, meta) newErr := powerOnAndWait(d, meta)
if newErr != nil { if newErr != nil {
@ -356,11 +351,8 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{})
"Error resizing droplet (%s): %s", d.Id(), err) "Error resizing droplet (%s): %s", d.Id(), err)
} }
// Wait for the size to change // Wait for the resize action to complete.
_, err = WaitForDropletAttribute( if err := waitForAction(client, action); err != nil {
d, newSize.(string), []string{"", oldSize.(string)}, "size", meta)
if err != nil {
newErr := powerOnAndWait(d, meta) newErr := powerOnAndWait(d, meta)
if newErr != nil { if newErr != nil {
return fmt.Errorf( return fmt.Errorf(

View File

@ -144,6 +144,56 @@ func TestAccDigitalOceanDroplet_ResizeWithOutDisk(t *testing.T) {
}) })
} }
func TestAccDigitalOceanDroplet_ResizeOnlyDisk(t *testing.T) {
var droplet godo.Droplet
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
testAccCheckDigitalOceanDropletAttributes(&droplet),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "name", fmt.Sprintf("foo-%d", rInt)),
),
},
{
Config: testAccCheckDigitalOceanDropletConfig_resize_without_disk(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
testAccCheckDigitalOceanDropletResizeWithOutDisk(&droplet),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "name", fmt.Sprintf("foo-%d", rInt)),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "size", "1gb"),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "disk", "20"),
),
},
{
Config: testAccCheckDigitalOceanDropletConfig_resize_only_disk(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
testAccCheckDigitalOceanDropletResizeOnlyDisk(&droplet),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "name", fmt.Sprintf("foo-%d", rInt)),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "size", "1gb"),
resource.TestCheckResourceAttr(
"digitalocean_droplet.foobar", "disk", "30"),
),
},
},
})
}
func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) { func TestAccDigitalOceanDroplet_UpdateUserData(t *testing.T) {
var afterCreate, afterUpdate godo.Droplet var afterCreate, afterUpdate godo.Droplet
rInt := acctest.RandInt() rInt := acctest.RandInt()
@ -321,6 +371,21 @@ func testAccCheckDigitalOceanDropletResizeWithOutDisk(droplet *godo.Droplet) res
} }
} }
func testAccCheckDigitalOceanDropletResizeOnlyDisk(droplet *godo.Droplet) resource.TestCheckFunc {
return func(s *terraform.State) error {
if droplet.Size.Slug != "1gb" {
return fmt.Errorf("Bad size_slug: %s", droplet.SizeSlug)
}
if droplet.Disk != 30 {
return fmt.Errorf("Bad disk: %d", droplet.Disk)
}
return nil
}
}
func testAccCheckDigitalOceanDropletAttributes_PrivateNetworkingIpv6(droplet *godo.Droplet) resource.TestCheckFunc { func testAccCheckDigitalOceanDropletAttributes_PrivateNetworkingIpv6(droplet *godo.Droplet) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
@ -492,6 +557,19 @@ resource "digitalocean_droplet" "foobar" {
`, rInt) `, rInt)
} }
func testAccCheckDigitalOceanDropletConfig_resize_only_disk(rInt int) string {
return fmt.Sprintf(`
resource "digitalocean_droplet" "foobar" {
name = "foo-%d"
size = "1gb"
image = "centos-7-x64"
region = "nyc3"
user_data = "foobar"
resize_disk = true
}
`, rInt)
}
// IPV6 only in singapore // IPV6 only in singapore
func testAccCheckDigitalOceanDropletConfig_PrivateNetworkingIpv6(rInt int) string { func testAccCheckDigitalOceanDropletConfig_PrivateNetworkingIpv6(rInt int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
@ -505,3 +583,5 @@ resource "digitalocean_droplet" "foobar" {
} }
`, rInt) `, rInt)
} }
var testAccValidPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR`

View File

@ -3,16 +3,21 @@ package digitalocean
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"testing" "testing"
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestAccDigitalOceanSSHKey_Basic(t *testing.T) { func TestAccDigitalOceanSSHKey_Basic(t *testing.T) {
var key godo.Key var key godo.Key
rInt := acctest.RandInt()
publicKeyMaterial, _, err := acctest.RandSSHKeyPair("digitalocean@ssh-acceptance-test")
if err != nil {
t.Fatalf("Cannot generate test SSH key pair: %s", err)
}
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -20,14 +25,13 @@ func TestAccDigitalOceanSSHKey_Basic(t *testing.T) {
CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy, CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
{ {
Config: testAccCheckDigitalOceanSSHKeyConfig_basic(testAccValidPublicKey), Config: testAccCheckDigitalOceanSSHKeyConfig_basic(rInt, publicKeyMaterial),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanSSHKeyExists("digitalocean_ssh_key.foobar", &key), testAccCheckDigitalOceanSSHKeyExists("digitalocean_ssh_key.foobar", &key),
testAccCheckDigitalOceanSSHKeyAttributes(&key),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"digitalocean_ssh_key.foobar", "name", "foobar"), "digitalocean_ssh_key.foobar", "name", fmt.Sprintf("foobar-%d", rInt)),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"digitalocean_ssh_key.foobar", "public_key", strings.TrimSpace(testAccValidPublicKey)), "digitalocean_ssh_key.foobar", "public_key", publicKeyMaterial),
), ),
}, },
}, },
@ -58,17 +62,6 @@ func testAccCheckDigitalOceanSSHKeyDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckDigitalOceanSSHKeyAttributes(key *godo.Key) resource.TestCheckFunc {
return func(s *terraform.State) error {
if key.Name != "foobar" {
return fmt.Errorf("Bad name: %s", key.Name)
}
return nil
}
}
func testAccCheckDigitalOceanSSHKeyExists(n string, key *godo.Key) resource.TestCheckFunc { func testAccCheckDigitalOceanSSHKeyExists(n string, key *godo.Key) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -105,13 +98,10 @@ func testAccCheckDigitalOceanSSHKeyExists(n string, key *godo.Key) resource.Test
} }
} }
func testAccCheckDigitalOceanSSHKeyConfig_basic(key string) string { func testAccCheckDigitalOceanSSHKeyConfig_basic(rInt int, key string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "digitalocean_ssh_key" "foobar" { resource "digitalocean_ssh_key" "foobar" {
name = "foobar" name = "foobar-%d"
public_key = "%s" public_key = "%s"
}`, key) }`, rInt, key)
} }
var testAccValidPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR`
var testAccValidImportPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwelf/LV8TKOd6ZCcDwU9L8YRdVwfR2q8E+Bzamcxwb1U41vnfyvEZbzx0aeXimdHipOql0SG2tu9Z+bzekROVc13OP/gtGRlWwZ9RoKE8hFHanhi0K2tC6OWagsvmHpW/xptsYAo2k+eRJJo0iy/hLNG2c1v5rrjg6xwnSL3+a7bFM4xNDux5sNYCmxIBfIL+4rQ8XBlxsjMrGoev/uumZ0yc75JtBCOSZbdie936pvVmoAf4nhxNbe5lOxp+18zHhBbO2fjhux4xmf4hLM2gHsdBGqtnphzLh3d1+uMIpv7ZMTKN7pBw53xQxw7hhDYuNKc8FkQ8xK6IL5bu/Ar/`

View File

@ -1922,7 +1922,7 @@ func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} {
nb := map[string]interface{}{ nb := map[string]interface{}{
"name": b.Name, "name": b.Name,
"address": b.Address, "address": b.Address,
"auto_loadbalance": gofastly.CBool(b.AutoLoadbalance), "auto_loadbalance": b.AutoLoadbalance,
"between_bytes_timeout": int(b.BetweenBytesTimeout), "between_bytes_timeout": int(b.BetweenBytesTimeout),
"connect_timeout": int(b.ConnectTimeout), "connect_timeout": int(b.ConnectTimeout),
"error_threshold": int(b.ErrorThreshold), "error_threshold": int(b.ErrorThreshold),
@ -1930,7 +1930,7 @@ func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} {
"max_conn": int(b.MaxConn), "max_conn": int(b.MaxConn),
"port": int(b.Port), "port": int(b.Port),
"shield": b.Shield, "shield": b.Shield,
"ssl_check_cert": gofastly.CBool(b.SSLCheckCert), "ssl_check_cert": b.SSLCheckCert,
"ssl_hostname": b.SSLHostname, "ssl_hostname": b.SSLHostname,
"ssl_cert_hostname": b.SSLCertHostname, "ssl_cert_hostname": b.SSLCertHostname,
"ssl_sni_hostname": b.SSLSNIHostname, "ssl_sni_hostname": b.SSLSNIHostname,

View File

@ -84,14 +84,14 @@ func TestResourceFastlyFlattenBackend(t *testing.T) {
"name": "test.notexample.com", "name": "test.notexample.com",
"address": "www.notexample.com", "address": "www.notexample.com",
"port": 80, "port": 80,
"auto_loadbalance": gofastly.CBool(true), "auto_loadbalance": true,
"between_bytes_timeout": 10000, "between_bytes_timeout": 10000,
"connect_timeout": 1000, "connect_timeout": 1000,
"error_threshold": 0, "error_threshold": 0,
"first_byte_timeout": 15000, "first_byte_timeout": 15000,
"max_conn": 200, "max_conn": 200,
"request_condition": "", "request_condition": "",
"ssl_check_cert": gofastly.CBool(true), "ssl_check_cert": true,
"ssl_hostname": "", "ssl_hostname": "",
"ssl_cert_hostname": "", "ssl_cert_hostname": "",
"ssl_sni_hostname": "", "ssl_sni_hostname": "",

View File

@ -0,0 +1,135 @@
package kubernetes
import (
"encoding/json"
"reflect"
"sort"
"strings"
)
func diffStringMap(pathPrefix string, oldV, newV map[string]interface{}) PatchOperations {
ops := make([]PatchOperation, 0, 0)
pathPrefix = strings.TrimRight(pathPrefix, "/")
// This is suboptimal for adding whole new map from scratch
// or deleting the whole map, but it's actually intention.
// There may be some other map items managed outside of TF
// and we don't want to touch these.
for k, _ := range oldV {
if _, ok := newV[k]; ok {
continue
}
ops = append(ops, &RemoveOperation{Path: pathPrefix + "/" + k})
}
for k, v := range newV {
newValue := v.(string)
if oldValue, ok := oldV[k].(string); ok {
if oldValue == newValue {
continue
}
ops = append(ops, &ReplaceOperation{
Path: pathPrefix + "/" + k,
Value: newValue,
})
continue
}
ops = append(ops, &AddOperation{
Path: pathPrefix + "/" + k,
Value: newValue,
})
}
return ops
}
type PatchOperations []PatchOperation
func (po PatchOperations) MarshalJSON() ([]byte, error) {
var v []PatchOperation = po
return json.Marshal(v)
}
func (po PatchOperations) Equal(ops []PatchOperation) bool {
var v []PatchOperation = po
sort.Slice(v, sortByPathAsc(v))
sort.Slice(ops, sortByPathAsc(ops))
return reflect.DeepEqual(v, ops)
}
func sortByPathAsc(ops []PatchOperation) func(i, j int) bool {
return func(i, j int) bool {
return ops[i].GetPath() < ops[j].GetPath()
}
}
type PatchOperation interface {
MarshalJSON() ([]byte, error)
GetPath() string
}
type ReplaceOperation struct {
Path string `json:"path"`
Value interface{} `json:"value"`
Op string `json:"op"`
}
func (o *ReplaceOperation) GetPath() string {
return o.Path
}
func (o *ReplaceOperation) MarshalJSON() ([]byte, error) {
o.Op = "replace"
return json.Marshal(*o)
}
func (o *ReplaceOperation) String() string {
b, _ := o.MarshalJSON()
return string(b)
}
type AddOperation struct {
Path string `json:"path"`
Value interface{} `json:"value"`
Op string `json:"op"`
}
func (o *AddOperation) GetPath() string {
return o.Path
}
func (o *AddOperation) MarshalJSON() ([]byte, error) {
o.Op = "add"
return json.Marshal(*o)
}
func (o *AddOperation) String() string {
b, _ := o.MarshalJSON()
return string(b)
}
type RemoveOperation struct {
Path string `json:"path"`
Op string `json:"op"`
}
func (o *RemoveOperation) GetPath() string {
return o.Path
}
func (o *RemoveOperation) MarshalJSON() ([]byte, error) {
o.Op = "remove"
return json.Marshal(*o)
}
func (o *RemoveOperation) String() string {
b, _ := o.MarshalJSON()
return string(b)
}

View File

@ -0,0 +1,126 @@
package kubernetes
import (
"fmt"
"testing"
)
func TestDiffStringMap(t *testing.T) {
testCases := []struct {
Path string
Old map[string]interface{}
New map[string]interface{}
ExpectedOps PatchOperations
}{
{
Path: "/parent/",
Old: map[string]interface{}{
"one": "111",
"two": "222",
},
New: map[string]interface{}{
"one": "111",
"two": "222",
"three": "333",
},
ExpectedOps: []PatchOperation{
&AddOperation{
Path: "/parent/three",
Value: "333",
},
},
},
{
Path: "/parent/",
Old: map[string]interface{}{
"one": "111",
"two": "222",
},
New: map[string]interface{}{
"one": "111",
"two": "abcd",
},
ExpectedOps: []PatchOperation{
&ReplaceOperation{
Path: "/parent/two",
Value: "abcd",
},
},
},
{
Path: "/parent/",
Old: map[string]interface{}{
"one": "111",
"two": "222",
},
New: map[string]interface{}{
"two": "abcd",
"three": "333",
},
ExpectedOps: []PatchOperation{
&RemoveOperation{Path: "/parent/one"},
&ReplaceOperation{
Path: "/parent/two",
Value: "abcd",
},
&AddOperation{
Path: "/parent/three",
Value: "333",
},
},
},
{
Path: "/parent/",
Old: map[string]interface{}{
"one": "111",
"two": "222",
},
New: map[string]interface{}{
"two": "222",
},
ExpectedOps: []PatchOperation{
&RemoveOperation{Path: "/parent/one"},
},
},
{
Path: "/parent/",
Old: map[string]interface{}{
"one": "111",
"two": "222",
},
New: map[string]interface{}{},
ExpectedOps: []PatchOperation{
&RemoveOperation{Path: "/parent/one"},
&RemoveOperation{Path: "/parent/two"},
},
},
{
Path: "/parent/",
Old: map[string]interface{}{},
New: map[string]interface{}{
"one": "111",
"two": "222",
},
ExpectedOps: []PatchOperation{
&AddOperation{
Path: "/parent/one",
Value: "111",
},
&AddOperation{
Path: "/parent/two",
Value: "222",
},
},
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
ops := diffStringMap(tc.Path, tc.Old, tc.New)
if !tc.ExpectedOps.Equal(ops) {
t.Fatalf("Operations don't match.\nExpected: %v\nGiven: %v\n", tc.ExpectedOps, ops)
}
})
}
}

View File

@ -1,9 +1,11 @@
package kubernetes package kubernetes
import ( import (
"fmt"
"log" "log"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
pkgApi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
api "k8s.io/kubernetes/pkg/api/v1" api "k8s.io/kubernetes/pkg/api/v1"
kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
@ -73,19 +75,22 @@ func resourceKubernetesConfigMapRead(d *schema.ResourceData, meta interface{}) e
func resourceKubernetesConfigMapUpdate(d *schema.ResourceData, meta interface{}) error { func resourceKubernetesConfigMapUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset) conn := meta.(*kubernetes.Clientset)
metadata := expandMetadata(d.Get("metadata").([]interface{}))
namespace, name := idParts(d.Id()) namespace, name := idParts(d.Id())
// This is necessary in case the name is generated
metadata.Name = name
cfgMap := api.ConfigMap{ ops := patchMetadata("metadata.0.", "/metadata/", d)
ObjectMeta: metadata, if d.HasChange("data") {
Data: expandStringMap(d.Get("data").(map[string]interface{})), oldV, newV := d.GetChange("data")
diffOps := diffStringMap("/data/", oldV.(map[string]interface{}), newV.(map[string]interface{}))
ops = append(ops, diffOps...)
} }
log.Printf("[INFO] Updating config map: %#v", cfgMap) data, err := ops.MarshalJSON()
out, err := conn.CoreV1().ConfigMaps(namespace).Update(&cfgMap)
if err != nil { if err != nil {
return err return fmt.Errorf("Failed to marshal update operations: %s", err)
}
log.Printf("[INFO] Updating config map %q: %v", name, string(data))
out, err := conn.CoreV1().ConfigMaps(namespace).Patch(name, pkgApi.JSONPatchType, data)
if err != nil {
return fmt.Errorf("Failed to update Config Map: %s", err)
} }
log.Printf("[INFO] Submitted updated config map: %#v", out) log.Printf("[INFO] Submitted updated config map: %#v", out)
d.SetId(buildId(out.ObjectMeta)) d.SetId(buildId(out.ObjectMeta))

View File

@ -2,8 +2,10 @@ package kubernetes
import ( import (
"fmt" "fmt"
"net/url"
"strings" "strings"
"github.com/hashicorp/terraform/helper/schema"
api "k8s.io/kubernetes/pkg/api/v1" api "k8s.io/kubernetes/pkg/api/v1"
) )
@ -39,6 +41,21 @@ func expandMetadata(in []interface{}) api.ObjectMeta {
return meta return meta
} }
func patchMetadata(keyPrefix, pathPrefix string, d *schema.ResourceData) PatchOperations {
ops := make([]PatchOperation, 0, 0)
if d.HasChange(keyPrefix + "annotations") {
oldV, newV := d.GetChange(keyPrefix + "annotations")
diffOps := diffStringMap(pathPrefix+"annotations", oldV.(map[string]interface{}), newV.(map[string]interface{}))
ops = append(ops, diffOps...)
}
if d.HasChange(keyPrefix + "labels") {
oldV, newV := d.GetChange(keyPrefix + "labels")
diffOps := diffStringMap(pathPrefix+"labels", oldV.(map[string]interface{}), newV.(map[string]interface{}))
ops = append(ops, diffOps...)
}
return ops
}
func expandStringMap(m map[string]interface{}) map[string]string { func expandStringMap(m map[string]interface{}) map[string]string {
result := make(map[string]string) result := make(map[string]string)
for k, v := range m { for k, v := range m {
@ -49,7 +66,7 @@ func expandStringMap(m map[string]interface{}) map[string]string {
func flattenMetadata(meta api.ObjectMeta) []map[string]interface{} { func flattenMetadata(meta api.ObjectMeta) []map[string]interface{} {
m := make(map[string]interface{}) m := make(map[string]interface{})
m["annotations"] = meta.Annotations m["annotations"] = filterAnnotations(meta.Annotations)
m["generate_name"] = meta.GenerateName m["generate_name"] = meta.GenerateName
m["labels"] = meta.Labels m["labels"] = meta.Labels
m["name"] = meta.Name m["name"] = meta.Name
@ -64,3 +81,21 @@ func flattenMetadata(meta api.ObjectMeta) []map[string]interface{} {
return []map[string]interface{}{m} return []map[string]interface{}{m}
} }
func filterAnnotations(m map[string]string) map[string]string {
for k, _ := range m {
if isInternalAnnotationKey(k) {
delete(m, k)
}
}
return m
}
func isInternalAnnotationKey(annotationKey string) bool {
u, err := url.Parse("//" + annotationKey)
if err == nil && strings.HasSuffix(u.Hostname(), "kubernetes.io") {
return true
}
return false
}

View File

@ -0,0 +1,32 @@
package kubernetes
import (
"fmt"
"testing"
)
func TestIsInternalAnnotationKey(t *testing.T) {
testCases := []struct {
Key string
Expected bool
}{
{"", false},
{"anyKey", false},
{"any.hostname.io", false},
{"any.hostname.com/with/path", false},
{"any.kubernetes.io", true},
{"kubernetes.io", true},
{"pv.kubernetes.io/any/path", true},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
isInternal := isInternalAnnotationKey(tc.Key)
if tc.Expected && isInternal != tc.Expected {
t.Fatalf("Expected %q to be internal", tc.Key)
}
if !tc.Expected && isInternal != tc.Expected {
t.Fatalf("Expected %q not to be internal", tc.Key)
}
})
}
}

View File

@ -82,6 +82,7 @@ func resourceComputeInstanceV2() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: false, ForceNew: false,
Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead",
}, },
"user_data": &schema.Schema{ "user_data": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -153,6 +154,7 @@ func resourceComputeInstanceV2() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead",
}, },
"mac": &schema.Schema{ "mac": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -245,6 +247,7 @@ func resourceComputeInstanceV2() *schema.Resource {
"volume": &schema.Schema{ "volume": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeSet,
Optional: true, Optional: true,
Deprecated: "Use block_device or openstack_compute_volume_attach_v2 instead",
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"id": &schema.Schema{ "id": &schema.Schema{
@ -335,6 +338,10 @@ func resourceComputeInstanceV2() *schema.Resource {
Optional: true, Optional: true,
Default: false, Default: false,
}, },
"all_metadata": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
}, },
} }
} }
@ -554,7 +561,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
}) })
} }
d.Set("metadata", server.Metadata) d.Set("all_metadata", server.Metadata)
secGrpNames := []string{} secGrpNames := []string{}
for _, sg := range server.SecurityGroups { for _, sg := range server.SecurityGroups {

View File

@ -29,6 +29,8 @@ func TestAccComputeV2Instance_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2InstanceExists("openstack_compute_instance_v2.instance_1", &instance), testAccCheckComputeV2InstanceExists("openstack_compute_instance_v2.instance_1", &instance),
testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"),
resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "all_metadata.foo", "bar"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "availability_zone", "nova"), "openstack_compute_instance_v2.instance_1", "availability_zone", "nova"),
), ),
@ -607,6 +609,10 @@ func TestAccComputeV2Instance_metadataRemove(t *testing.T) {
testAccCheckComputeV2InstanceExists("openstack_compute_instance_v2.instance_1", &instance), testAccCheckComputeV2InstanceExists("openstack_compute_instance_v2.instance_1", &instance),
testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"),
testAccCheckComputeV2InstanceMetadata(&instance, "abc", "def"), testAccCheckComputeV2InstanceMetadata(&instance, "abc", "def"),
resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "all_metadata.foo", "bar"),
resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "all_metadata.abc", "def"),
), ),
}, },
resource.TestStep{ resource.TestStep{
@ -616,6 +622,10 @@ func TestAccComputeV2Instance_metadataRemove(t *testing.T) {
testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"),
testAccCheckComputeV2InstanceMetadata(&instance, "ghi", "jkl"), testAccCheckComputeV2InstanceMetadata(&instance, "ghi", "jkl"),
testAccCheckComputeV2InstanceNoMetadataKey(&instance, "abc"), testAccCheckComputeV2InstanceNoMetadataKey(&instance, "abc"),
resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "all_metadata.foo", "bar"),
resource.TestCheckResourceAttr(
"openstack_compute_instance_v2.instance_1", "all_metadata.ghi", "jkl"),
), ),
}, },
}, },

View File

@ -240,7 +240,7 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error {
} }
if d.HasChange("monitor_ids") { if d.HasChange("monitor_ids") {
oldMIDsRaw, newMIDsRaw := d.GetChange("security_groups") oldMIDsRaw, newMIDsRaw := d.GetChange("monitor_ids")
oldMIDsSet, newMIDsSet := oldMIDsRaw.(*schema.Set), newMIDsRaw.(*schema.Set) oldMIDsSet, newMIDsSet := oldMIDsRaw.(*schema.Set), newMIDsRaw.(*schema.Set)
monitorsToAdd := newMIDsSet.Difference(oldMIDsSet) monitorsToAdd := newMIDsSet.Difference(oldMIDsSet)
monitorsToRemove := oldMIDsSet.Difference(newMIDsSet) monitorsToRemove := oldMIDsSet.Difference(newMIDsSet)

View File

@ -104,6 +104,42 @@ func TestAccLBV1Pool_timeout(t *testing.T) {
}) })
} }
func TestAccLBV1Pool_updateMonitor(t *testing.T) {
var monitor_1 monitors.Monitor
var monitor_2 monitors.Monitor
var network networks.Network
var pool pools.Pool
var subnet subnets.Subnet
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLBV1PoolDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccLBV1Pool_updateMonitor_1,
Check: resource.ComposeTestCheckFunc(
testAccCheckNetworkingV2NetworkExists("openstack_networking_network_v2.network_1", &network),
testAccCheckNetworkingV2SubnetExists("openstack_networking_subnet_v2.subnet_1", &subnet),
testAccCheckLBV1PoolExists("openstack_lb_pool_v1.pool_1", &pool),
testAccCheckLBV1MonitorExists("openstack_lb_monitor_v1.monitor_1", &monitor_1),
testAccCheckLBV1MonitorExists("openstack_lb_monitor_v1.monitor_2", &monitor_2),
),
},
resource.TestStep{
Config: testAccLBV1Pool_updateMonitor_2,
Check: resource.ComposeTestCheckFunc(
testAccCheckNetworkingV2NetworkExists("openstack_networking_network_v2.network_1", &network),
testAccCheckNetworkingV2SubnetExists("openstack_networking_subnet_v2.subnet_1", &subnet),
testAccCheckLBV1PoolExists("openstack_lb_pool_v1.pool_1", &pool),
testAccCheckLBV1MonitorExists("openstack_lb_monitor_v1.monitor_1", &monitor_1),
testAccCheckLBV1MonitorExists("openstack_lb_monitor_v1.monitor_2", &monitor_2),
),
},
},
})
}
func testAccCheckLBV1PoolDestroy(s *terraform.State) error { func testAccCheckLBV1PoolDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
networkingClient, err := config.networkingV2Client(OS_REGION_NAME) networkingClient, err := config.networkingV2Client(OS_REGION_NAME)
@ -402,3 +438,77 @@ resource "openstack_lb_pool_v1" "pool_1" {
} }
} }
` `
const testAccLBV1Pool_updateMonitor_1 = `
resource "openstack_networking_network_v2" "network_1" {
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "subnet_1" {
cidr = "192.168.199.0/24"
ip_version = 4
network_id = "${openstack_networking_network_v2.network_1.id}"
}
resource "openstack_lb_monitor_v1" "monitor_1" {
type = "TCP"
delay = 30
timeout = 5
max_retries = 3
admin_state_up = "true"
}
resource "openstack_lb_monitor_v1" "monitor_2" {
type = "TCP"
delay = 30
timeout = 5
max_retries = 3
admin_state_up = "true"
}
resource "openstack_lb_pool_v1" "pool_1" {
name = "pool_1"
protocol = "TCP"
lb_method = "ROUND_ROBIN"
monitor_ids = ["${openstack_lb_monitor_v1.monitor_1.id}"]
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
}
`
const testAccLBV1Pool_updateMonitor_2 = `
resource "openstack_networking_network_v2" "network_1" {
name = "network_1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "subnet_1" {
cidr = "192.168.199.0/24"
ip_version = 4
network_id = "${openstack_networking_network_v2.network_1.id}"
}
resource "openstack_lb_monitor_v1" "monitor_1" {
type = "TCP"
delay = 30
timeout = 5
max_retries = 3
admin_state_up = "true"
}
resource "openstack_lb_monitor_v1" "monitor_2" {
type = "TCP"
delay = 30
timeout = 5
max_retries = 3
admin_state_up = "true"
}
resource "openstack_lb_pool_v1" "pool_1" {
name = "pool_1"
protocol = "TCP"
lb_method = "ROUND_ROBIN"
monitor_ids = ["${openstack_lb_monitor_v1.monitor_2.id}"]
subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}"
}
`

View File

@ -167,10 +167,33 @@ func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error {
} }
log.Printf("[DEBUG] Create Options: %#v", createOpts) log.Printf("[DEBUG] Create Options: %#v", createOpts)
pool, err := pools.Create(networkingClient, createOpts).Extract()
var pool *pools.Pool
err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
var err error
log.Printf("[DEBUG] Attempting to create LBaaSV2 pool")
pool, err = pools.Create(networkingClient, createOpts).Extract()
if err != nil {
switch errCode := err.(type) {
case gophercloud.ErrDefault500:
log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.")
return resource.RetryableError(err)
case gophercloud.ErrUnexpectedResponseCode:
if errCode.Actual == 409 {
log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.")
return resource.RetryableError(err)
}
default:
return resource.NonRetryableError(err)
}
}
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err) return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err)
} }
log.Printf("[INFO] pool ID: %s", pool.ID) log.Printf("[INFO] pool ID: %s", pool.ID)
log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID) log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID)

View File

@ -85,10 +85,9 @@ func resourceNetworkingPortV2() *schema.Resource {
Computed: true, Computed: true,
}, },
"fixed_ip": &schema.Schema{ "fixed_ip": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeList,
Optional: true, Optional: true,
ForceNew: false, ForceNew: false,
Computed: true,
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"subnet_id": &schema.Schema{ "subnet_id": &schema.Schema{
@ -98,7 +97,6 @@ func resourceNetworkingPortV2() *schema.Resource {
"ip_address": &schema.Schema{ "ip_address": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
}, },
}, },
}, },
@ -128,6 +126,11 @@ func resourceNetworkingPortV2() *schema.Resource {
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"all_fixed_ips": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
}, },
} }
} }
@ -202,15 +205,14 @@ func resourceNetworkingPortV2Read(d *schema.ResourceData, meta interface{}) erro
d.Set("security_group_ids", p.SecurityGroups) d.Set("security_group_ids", p.SecurityGroups)
d.Set("device_id", p.DeviceID) d.Set("device_id", p.DeviceID)
// Convert FixedIPs to list of map // Create a slice of all returned Fixed IPs.
var ips []map[string]interface{} // This will be in the order returned by the API,
// which is usually alpha-numeric.
var ips []string
for _, ipObject := range p.FixedIPs { for _, ipObject := range p.FixedIPs {
ip := make(map[string]interface{}) ips = append(ips, ipObject.IPAddress)
ip["subnet_id"] = ipObject.SubnetID
ip["ip_address"] = ipObject.IPAddress
ips = append(ips, ip)
} }
d.Set("fixed_ip", ips) d.Set("all_fixed_ips", ips)
// Convert AllowedAddressPairs to list of map // Convert AllowedAddressPairs to list of map
var pairs []map[string]interface{} var pairs []map[string]interface{}
@ -309,7 +311,7 @@ func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string {
} }
func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} { func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} {
rawIP := d.Get("fixed_ip").(*schema.Set).List() rawIP := d.Get("fixed_ip").([]interface{})
if len(rawIP) == 0 { if len(rawIP) == 0 {
return nil return nil

Some files were not shown because too many files have changed in this diff Show More