diff --git a/.travis.yml b/.travis.yml index 88c853d681..04cc6f3096 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,10 @@ language: go go: - 1.8 +# add TF_CONSUL_TEST=1 to run consul tests +# they were causing timouts in travis 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 before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f330066d..50a440d97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,55 +1,95 @@ -## 0.9.2 (unreleased) +## 0.9.3 (unreleased) + +IMPROVEMENTS: + + * config: New interpolation functions `basename` and `dirname`, for file path manipulation [GH-13080] + * helper/resource: Allow unknown "pending" states [GH-13099] + * provider/aws: Add support to set iam_role_arn on cloudformation Stack [GH-12547] + * provider/aws: Support priority and listener_arn update of alb_listener_rule [GH-13125] + * provider/aws: Support priority and listener_arn update of alb_listener_rule [GH-13125] + * provider/aws: Deprecate roles in favour of role in iam_instance_profile [GH-13130] + + +## 0.9.2 (March 28, 2017) + +BACKWARDS IMCOMPATIBILITIES / NOTES: + + * 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. + * Environment names must be safe to use as a URL path segment without escaping, and is enforced by the CLI. 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] + * **New Resource:** `alicloud_db_instance` ([#12913](https://github.com/hashicorp/terraform/issues/12913)) + * **New Resource:** `aws_api_gateway_usage_plan` ([#12542](https://github.com/hashicorp/terraform/issues/12542)) + * **New Resource:** `aws_api_gateway_usage_plan_key` ([#12851](https://github.com/hashicorp/terraform/issues/12851)) + * **New Resource:** `github_repository_webhook` ([#12924](https://github.com/hashicorp/terraform/issues/12924)) + * **New Resource:** `random_pet` ([#12903](https://github.com/hashicorp/terraform/issues/12903)) + * **New Interpolation:** `substr` ([#12870](https://github.com/hashicorp/terraform/issues/12870)) + * **S3 Environments:** The S3 remote state backend now supports named environments IMPROVEMENTS: - * core: fix `ignore_changes` causing fields to be removed during apply [GH-12897] - * core: add `-force-copy` option to `terraform init` to supress prompts for copying state [GH-12939] - * helper/acctest: Add NewSSHKeyPair function [GH-12894] - * provider/alicloud: simplify validators [GH-12982] - * provider/aws: Added support for EMR AutoScalingRole [GH-12823] - * provider/aws: Add `name_prefix` to `aws_autoscaling_group` and `aws_elb` resources [GH-12629] - * 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] - * 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] - * provider/cloudstack: Add `zone_id` to `cloudstack_ipaddress` resource [GH-11306] - * provider/consul: Add support for basic auth to the provider [GH-12679] - * provider/dnsimple: Allow dnsimple_record.priority attribute to be set [GH-12843] - * 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] - * provider/ignition: ignition_filesystem, explicit option to create the filesystem [GH-12980] - * provider/ns1: Ensure provider checks for credentials [GH-12920] - * provider/openstack: Adding Timeouts to Blockstorage Resources [GH-12862] - * provider/openstack: Adding Timeouts to FWaaS v1 Resources [GH-12863] - * provider/openstack: Adding Timeouts to Image v2 and LBaaS v2 Resources [GH-12865] - * provider/openstack: Adding Timeouts to Network Resources [GH-12866] - * provider/openstack: Adding Timeouts to LBaaS v1 Resources [GH-12867] - * provider/pagerduty: Validate credentials [GH-12854] + * 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: - * core: Remove legacy remote state configuration on state migration. This fixes errors when saving plans. [GH-12888] - * provider/arukas: Default timeout for launching container increased to 15mins (was 10mins) [GH-12849] - * provider/aws: Fix flattened cloudfront lambda function associations to be a set not a slice [GH-11984] - * provider/aws: Consider ACTIVE as pending state during ECS svc deletion [GH-12986] - * provider/aws: Deprecate the usage of Api Gateway Key Stages in favor of Usage Plans [GH-12883] - * provider/aws: prevent panic in resourceAwsSsmDocumentRead [GH-12891] - * provider/aws: Prevent panic when setting AWS CodeBuild Source to state [GH-12915] - * provider/aws: Only call replace Iam Instance Profile on existing machines [GH-12922] - * provider/aws: Increase AWS AMI Destroy timeout [GH-12943] - * provider/aws: Set aws_vpc ipv6 for associated only [GH-12899] - * provider/aws: Fix AWS ECS placement strategy spread fields [GH-12998] - * provider/aws: Specify that aws_network_acl_rule requires a cidr block [GH-13013] - * provider/google: turn compute_instance_group.instances into a set [GH-12790] - * provider/mysql: recreate user/grant if user/grant got deleted manually [GH-12791] + * 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) ([#12849](https://github.com/hashicorp/terraform/issues/12849)) + * 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 ([#12986](https://github.com/hashicorp/terraform/issues/12986)) + * 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 ([#12891](https://github.com/hashicorp/terraform/issues/12891)) + * 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 ([#12922](https://github.com/hashicorp/terraform/issues/12922)) + * provider/aws: Increase AWS AMI Destroy timeout ([#12943](https://github.com/hashicorp/terraform/issues/12943)) + * 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 ([#12998](https://github.com/hashicorp/terraform/issues/12998)) + * provider/aws: Specify that aws_network_acl_rule requires a cidr block ([#13013](https://github.com/hashicorp/terraform/issues/13013)) + * provider/aws: aws_network_acl_rule treat all and -1 for protocol the same ([#13049](https://github.com/hashicorp/terraform/issues/13049)) + * 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) diff --git a/README.md b/README.md index a7b9eea326..f4b36b5646 100644 --- a/README.md +++ b/README.md @@ -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._ + +#### 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" +``` diff --git a/builtin/providers/alicloud/common.go b/builtin/providers/alicloud/common.go index 24c3647db7..c2af2a683c 100644 --- a/builtin/providers/alicloud/common.go +++ b/builtin/providers/alicloud/common.go @@ -2,6 +2,7 @@ package alicloud import ( "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/schema" ) @@ -12,8 +13,12 @@ const ( VpcNet = InstanceNetWork("vpc") ) +// timeout for common product, ecs e.g. const defaultTimeout = 120 +// timeout for long time progerss product, rds e.g. +const defaultLongTimeout = 800 + func getRegion(d *schema.ResourceData, meta interface{}) common.Region { return meta.(*AliyunClient).Region } @@ -50,3 +55,26 @@ func isProtocalValid(value string) bool { } 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" diff --git a/builtin/providers/alicloud/config.go b/builtin/providers/alicloud/config.go index 352e2e21c1..e17003bb2d 100644 --- a/builtin/providers/alicloud/config.go +++ b/builtin/providers/alicloud/config.go @@ -5,6 +5,7 @@ import ( "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" + "github.com/denverdino/aliyungo/rds" "github.com/denverdino/aliyungo/slb" ) @@ -19,8 +20,11 @@ type Config struct { type AliyunClient struct { Region common.Region ecsconn *ecs.Client - vpcconn *ecs.Client - slbconn *slb.Client + rdsconn *rds.Client + // use new version + ecsNewconn *ecs.Client + vpcconn *ecs.Client + slbconn *slb.Client } // Client for AliyunClient @@ -35,6 +39,17 @@ func (c *Config) Client() (*AliyunClient, error) { 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() if err != nil { return nil, err @@ -46,13 +61,17 @@ func (c *Config) Client() (*AliyunClient, error) { } return &AliyunClient{ - Region: c.Region, - ecsconn: ecsconn, - vpcconn: vpcconn, - slbconn: slbconn, + Region: c.Region, + ecsconn: ecsconn, + ecsNewconn: ecsNewconn, + vpcconn: vpcconn, + slbconn: slbconn, + rdsconn: rdsconn, }, nil } +const BusinessInfoKey = "Terraform" + func (c *Config) loadAndValidate() error { err := c.validateRegion() if err != nil { @@ -74,7 +93,9 @@ func (c *Config) validateRegion() 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() if err != nil { @@ -84,20 +105,21 @@ func (c *Config) ecsConn() (*ecs.Client, error) { return client, nil } -func (c *Config) slbConn() (*slb.Client, error) { - client := slb.NewClient(c.AccessKey, c.SecretKey) +func (c *Config) rdsConn() (*rds.Client, error) { + 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 } func (c *Config) vpcConn() (*ecs.Client, error) { - _, err := c.ecsConn() - - if err != nil { - return nil, err - } - - client := &ecs.Client{} - client.Init("https://vpc.aliyuncs.com/", "2016-04-28", c.AccessKey, c.SecretKey) + client := ecs.NewVPCClient(c.AccessKey, c.SecretKey, c.Region) + client.SetBusinessInfo(BusinessInfoKey) return client, nil + } diff --git a/builtin/providers/alicloud/data_source_alicloud_images.go b/builtin/providers/alicloud/data_source_alicloud_images.go index ae5b660693..d9a8737824 100644 --- a/builtin/providers/alicloud/data_source_alicloud_images.go +++ b/builtin/providers/alicloud/data_source_alicloud_images.go @@ -5,10 +5,10 @@ import ( "log" "regexp" "sort" + "time" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/schema" - "time" ) func dataSourceAlicloudImages() *schema.Resource { @@ -175,15 +175,28 @@ func dataSourceAlicloudImagesRead(d *schema.ResourceData, meta interface{}) erro params.ImageOwnerAlias = ecs.ImageOwnerAlias(owners.(string)) } - resp, _, err := conn.DescribeImages(params) - if err != nil { - return err + var allImages []ecs.ImageType + + for { + images, paginationResult, err := conn.DescribeImages(params) + if err != nil { + break + } + + allImages = append(allImages, images...) + + pagination := paginationResult.NextPage() + if pagination == nil { + break + } + + params.Pagination = *pagination } var filteredImages []ecs.ImageType if nameRegexOk { 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 // image name. No name means nothing to attempt a match against, // therefore we are skipping such image. @@ -198,7 +211,7 @@ func dataSourceAlicloudImagesRead(d *schema.ResourceData, meta interface{}) erro } } } else { - filteredImages = resp[:] + filteredImages = allImages[:] } var images []ecs.ImageType diff --git a/builtin/providers/alicloud/data_source_alicloud_images_test.go b/builtin/providers/alicloud/data_source_alicloud_images_test.go index 7512c6e916..9c6e225e43 100644 --- a/builtin/providers/alicloud/data_source_alicloud_images_test.go +++ b/builtin/providers/alicloud/data_source_alicloud_images_test.go @@ -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 const testAccCheckAlicloudImagesDataSourceImagesConfig = ` 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}.*" } ` + +// 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" +} +` diff --git a/builtin/providers/alicloud/data_source_alicloud_instance_types_test.go b/builtin/providers/alicloud/data_source_alicloud_instance_types_test.go index 43a180deff..335da3fbd5 100644 --- a/builtin/providers/alicloud/data_source_alicloud_instance_types_test.go +++ b/builtin/providers/alicloud/data_source_alicloud_instance_types_test.go @@ -17,8 +17,6 @@ func TestAccAlicloudInstanceTypesDataSource_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( 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.memory_size", "8"), resource.TestCheckResourceAttr("data.alicloud_instance_types.4c8g", "instance_types.0.id", "ecs.s3.large"), diff --git a/builtin/providers/alicloud/data_source_alicloud_regions_test.go b/builtin/providers/alicloud/data_source_alicloud_regions_test.go index f2aff1bbe4..9dafaba1e7 100644 --- a/builtin/providers/alicloud/data_source_alicloud_regions_test.go +++ b/builtin/providers/alicloud/data_source_alicloud_regions_test.go @@ -71,11 +71,6 @@ func TestAccAlicloudRegionsDataSource_empty(t *testing.T) { Check: resource.ComposeTestCheckFunc( 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.region_id", "cn-shenzhen"), resource.TestCheckResourceAttr("data.alicloud_regions.empty_params_region", "regions.0.local_name", "华南 1"), diff --git a/builtin/providers/alicloud/data_source_alicloud_zones_test.go b/builtin/providers/alicloud/data_source_alicloud_zones_test.go index 4b07c96717..4757f495c9 100644 --- a/builtin/providers/alicloud/data_source_alicloud_zones_test.go +++ b/builtin/providers/alicloud/data_source_alicloud_zones_test.go @@ -1,7 +1,10 @@ package alicloud import ( + "fmt" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "strconv" "testing" ) @@ -23,6 +26,7 @@ func TestAccAlicloudZonesDataSource_basic(t *testing.T) { } func TestAccAlicloudZonesDataSource_filter(t *testing.T) { + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) @@ -33,7 +37,7 @@ func TestAccAlicloudZonesDataSource_filter(t *testing.T) { Config: testAccCheckAlicloudZonesDataSourceFilter, Check: resource.ComposeTestCheckFunc( 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, Check: resource.ComposeTestCheckFunc( 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 = ` data "alicloud_zones" "foo" { } @@ -55,16 +105,28 @@ data "alicloud_zones" "foo" { const testAccCheckAlicloudZonesDataSourceFilter = ` data "alicloud_zones" "foo" { - "available_instance_type"= "ecs.c2.xlarge" - "available_resource_creation"= "VSwitch" - "available_disk_category"= "cloud_efficiency" + available_instance_type= "ecs.c2.xlarge" + available_resource_creation= "VSwitch" + available_disk_category= "cloud_efficiency" } ` const testAccCheckAlicloudZonesDataSourceFilterIoOptimized = ` data "alicloud_zones" "foo" { - "available_instance_type"= "ecs.c2.xlarge" - "available_resource_creation"= "IoOptimized" - "available_disk_category"= "cloud" + available_instance_type= "ecs.c2.xlarge" + available_resource_creation= "IoOptimized" + 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" } ` diff --git a/builtin/providers/alicloud/errors.go b/builtin/providers/alicloud/errors.go index f285bf968a..3385253302 100644 --- a/builtin/providers/alicloud/errors.go +++ b/builtin/providers/alicloud/errors.go @@ -9,6 +9,7 @@ const ( DiskIncorrectStatus = "IncorrectDiskStatus" DiskCreatingSnapshot = "DiskCreatingSnapshot" InstanceLockedForSecurity = "InstanceLockedForSecurity" + SystemDiskNotFound = "SystemDiskNotFound" // eip EipIncorrectStatus = "IncorrectEipStatus" InstanceIncorrectStatus = "IncorrectInstanceStatus" diff --git a/builtin/providers/alicloud/extension_ecs.go b/builtin/providers/alicloud/extension_ecs.go index 091bd97085..df21138bfc 100644 --- a/builtin/providers/alicloud/extension_ecs.go +++ b/builtin/providers/alicloud/extension_ecs.go @@ -30,3 +30,8 @@ const ( GroupRulePolicyAccept = GroupRulePolicy("accept") GroupRulePolicyDrop = GroupRulePolicy("drop") ) + +const ( + EcsApiVersion20160314 = "2016-03-14" + EcsApiVersion20140526 = "2014-05-26" +) diff --git a/builtin/providers/alicloud/extension_slb.go b/builtin/providers/alicloud/extension_slb.go index 9213f47979..2c4cf787b7 100644 --- a/builtin/providers/alicloud/extension_slb.go +++ b/builtin/providers/alicloud/extension_slb.go @@ -8,13 +8,41 @@ import ( ) type Listener struct { + slb.HTTPListenerType + InstancePort int LoadBalancerPort int Protocol string + //tcp & udp + PersistenceTimeout int + + //https 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 // returns ELB API compatible objects func expandListeners(configured []interface{}) ([]*Listener, error) { @@ -31,13 +59,78 @@ func expandListeners(configured []interface{}) ([]*Listener, error) { InstancePort: ip, LoadBalancerPort: lp, 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 { 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 if l.SSLCertificateId != "" { // validate the protocol is correct diff --git a/builtin/providers/alicloud/provider.go b/builtin/providers/alicloud/provider.go index 907f3271d9..677c1c70d7 100644 --- a/builtin/providers/alicloud/provider.go +++ b/builtin/providers/alicloud/provider.go @@ -26,7 +26,7 @@ func Provider() terraform.ResourceProvider { "region": &schema.Schema{ Type: schema.TypeString, Required: true, - DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", "cn-beijing"), + DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", DEFAULT_REGION), Description: descriptions["region"], }, }, @@ -43,6 +43,7 @@ func Provider() terraform.ResourceProvider { "alicloud_disk_attachment": resourceAliyunDiskAttachment(), "alicloud_security_group": resourceAliyunSecurityGroup(), "alicloud_security_group_rule": resourceAliyunSecurityGroupRule(), + "alicloud_db_instance": resourceAlicloudDBInstance(), "alicloud_vpc": resourceAliyunVpc(), "alicloud_nat_gateway": resourceAliyunNatGateway(), //both subnet and vswith exists,cause compatible old version, and compatible aws habit. diff --git a/builtin/providers/alicloud/resource_alicloud_db_instance.go b/builtin/providers/alicloud/resource_alicloud_db_instance.go new file mode 100644 index 0000000000..c19aef165b --- /dev/null +++ b/builtin/providers/alicloud/resource_alicloud_db_instance.go @@ -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 +} diff --git a/builtin/providers/alicloud/resource_alicloud_db_instance_test.go b/builtin/providers/alicloud/resource_alicloud_db_instance_test.go new file mode 100644 index 0000000000..8348e50894 --- /dev/null +++ b/builtin/providers/alicloud/resource_alicloud_db_instance_test.go @@ -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" +} +` diff --git a/builtin/providers/alicloud/resource_alicloud_disk_attachment_test.go b/builtin/providers/alicloud/resource_alicloud_disk_attachment_test.go index a0fe32a0a1..00239f5c56 100644 --- a/builtin/providers/alicloud/resource_alicloud_disk_attachment_test.go +++ b/builtin/providers/alicloud/resource_alicloud_disk_attachment_test.go @@ -151,4 +151,5 @@ resource "alicloud_security_group" "group" { name = "terraform-test-group" description = "New security group" } + ` diff --git a/builtin/providers/alicloud/resource_alicloud_disk_test.go b/builtin/providers/alicloud/resource_alicloud_disk_test.go index 6cb55bd8ea..b8d73a6626 100644 --- a/builtin/providers/alicloud/resource_alicloud_disk_test.go +++ b/builtin/providers/alicloud/resource_alicloud_disk_test.go @@ -136,9 +136,13 @@ func testAccCheckDiskDestroy(s *terraform.State) error { } const testAccDiskConfig = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" +} + resource "alicloud_disk" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" name = "New-disk" description = "Hello ecs disk." category = "cloud_efficiency" @@ -146,10 +150,15 @@ resource "alicloud_disk" "foo" { } ` const testAccDiskConfigWithTags = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" +} + resource "alicloud_disk" "bar" { # cn-beijing - availability_zone = "cn-beijing-b" - size = "10" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" + category = "cloud_efficiency" + size = "20" tags { Name = "TerraformTest" } diff --git a/builtin/providers/alicloud/resource_alicloud_eip_association_test.go b/builtin/providers/alicloud/resource_alicloud_eip_association_test.go index b039248af8..37c79f0050 100644 --- a/builtin/providers/alicloud/resource_alicloud_eip_association_test.go +++ b/builtin/providers/alicloud/resource_alicloud_eip_association_test.go @@ -108,6 +108,10 @@ func testAccCheckEIPAssociationDestroy(s *terraform.State) error { } const testAccEIPAssociationConfig = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "main" { cidr_block = "10.1.0.0/21" } @@ -115,19 +119,23 @@ resource "alicloud_vpc" "main" { resource "alicloud_vswitch" "main" { vpc_id = "${alicloud_vpc.main.id}" cidr_block = "10.1.1.0/24" - availability_zone = "cn-beijing-a" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" depends_on = [ "alicloud_vpc.main"] } resource "alicloud_instance" "instance" { - image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" - instance_type = "ecs.s1.small" - availability_zone = "cn-beijing-a" - security_groups = ["${alicloud_security_group.group.id}"] + # cn-beijing vswitch_id = "${alicloud_vswitch.main.id}" - instance_name = "hello" - io_optimized = "none" + image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" + + # 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 { Name = "TerraformTest-instance" diff --git a/builtin/providers/alicloud/resource_alicloud_instance.go b/builtin/providers/alicloud/resource_alicloud_instance.go index aeac4b3af1..fe221f17ba 100644 --- a/builtin/providers/alicloud/resource_alicloud_instance.go +++ b/builtin/providers/alicloud/resource_alicloud_instance.go @@ -5,6 +5,7 @@ import ( "log" "encoding/base64" + "encoding/json" "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/schema" @@ -21,8 +22,9 @@ func resourceAliyunInstance() *schema.Resource { Schema: map[string]*schema.Schema{ "availability_zone": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, + Computed: true, }, "image_id": &schema.Schema{ @@ -60,11 +62,6 @@ func resourceAliyunInstance() *schema.Resource { ValidateFunc: validateInstanceDescription, }, - "instance_network_type": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "internet_charge_type": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -104,11 +101,19 @@ func resourceAliyunInstance() *schema.Resource { Default: "cloud", Optional: true, ForceNew: true, + ValidateFunc: validateAllowedStringValue([]string{ + string(ecs.DiskCategoryCloud), + string(ecs.DiskCategoryCloudSSD), + string(ecs.DiskCategoryCloudEfficiency), + string(ecs.DiskCategoryEphemeralSSD), + }), }, "system_disk_size": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Computed: true, + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateIntegerInRange(40, 500), }, //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{ Type: schema.TypeString, - Optional: true, Computed: true, }, @@ -168,6 +172,11 @@ func resourceAliyunInstance() *schema.Resource { func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) error { 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) if err != nil { return err @@ -181,7 +190,8 @@ func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) erro d.SetId(instanceID) 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) { _, err := conn.AllocatePublicIpAddress(d.Id()) @@ -207,11 +217,56 @@ func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) erro 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 { client := meta.(*AliyunClient) conn := client.ecsconn instance, err := client.QueryInstancesById(d.Id()) + if err != nil { if notFoundError(err) { d.SetId("") @@ -220,7 +275,15 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error 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("description", instance.Description) @@ -229,6 +292,8 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("host_name", instance.HostName) d.Set("image_id", instance.ImageId) 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 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") } - 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) != "" { ipAddress := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] d.Set("private_ip", ipAddress) @@ -414,33 +475,71 @@ func resourceAliyunInstanceDelete(d *schema.ResourceData, meta interface{}) erro 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) { client := meta.(*AliyunClient) args := &ecs.CreateInstanceArgs{ - RegionId: getRegion(d, meta), - InstanceType: d.Get("instance_type").(string), - PrivateIpAddress: d.Get("private_ip").(string), + RegionId: getRegion(d, meta), + InstanceType: d.Get("instance_type").(string), } imageID := d.Get("image_id").(string) args.ImageId = imageID + 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) + if err != nil { + return nil, err + } + + if err := client.ResourceAvailable(zone, ecs.ResourceTypeInstance); err != nil { + return nil, err + } + + if err := client.DiskAvailable(zone, systemDiskCategory); err != nil { + return nil, err + } + + args.ZoneId = zoneID - zone, err := client.DescribeZone(zoneID) - if err != nil { - return nil, err } - if err := client.ResourceAvailable(zone, ecs.ResourceTypeInstance); err != nil { - return nil, err + args.SystemDisk = ecs.SystemDiskType{ + Category: systemDiskCategory, + Size: systemDiskSize, } - args.ZoneId = zoneID - sgs, ok := d.GetOk("security_groups") if ok { @@ -451,17 +550,6 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre if err == nil { 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 != "" { @@ -472,7 +560,7 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre 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 != "" { 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 != "" { - 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) diff --git a/builtin/providers/alicloud/resource_alicloud_instance_test.go b/builtin/providers/alicloud/resource_alicloud_instance_test.go index 2fb232f3cd..4e8f0c716a 100644 --- a/builtin/providers/alicloud/resource_alicloud_instance_test.go +++ b/builtin/providers/alicloud/resource_alicloud_instance_test.go @@ -56,6 +56,7 @@ func TestAccAlicloudInstance_basic(t *testing.T) { "alicloud_instance.foo", "internet_charge_type", "PayByBandwidth"), + testAccCheckSystemDiskSize("alicloud_instance.foo", 80), ), }, @@ -355,10 +356,6 @@ func TestAccAlicloudInstance_tags(t *testing.T) { Config: testAccCheckInstanceConfigTagsUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("alicloud_instance.foo", &instance), - resource.TestCheckResourceAttr( - "alicloud_instance.foo", - "tags.foo", - ""), resource.TestCheckResourceAttr( "alicloud_instance.foo", "tags.bar", @@ -418,8 +415,8 @@ func TestAccAlicloudInstance_privateIP(t *testing.T) { testCheckPrivateIP := func() resource.TestCheckFunc { return func(*terraform.State) error { privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] - if privateIP != "172.16.0.229" { - return fmt.Errorf("bad private IP: %s", privateIP) + if privateIP == "" { + return fmt.Errorf("can't get private IP") } 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 testCheckPrivateIP := func() resource.TestCheckFunc { return func(*terraform.State) error { privateIP := instance.VpcAttributes.PrivateIpAddress.IpAddress[0] - if privateIP != "172.16.0.229" { - return fmt.Errorf("bad private IP: %s", privateIP) + if privateIP == "" { + return fmt.Errorf("can't get private IP") } return nil @@ -468,7 +465,7 @@ func TestAccAlicloudInstance_associatePublicIPAndPrivateIP(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccInstanceConfigAssociatePublicIPAndPrivateIP, + Config: testAccInstanceConfigAssociatePublicIP, Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("alicloud_instance.foo", &instance), testCheckPrivateIP(), @@ -597,6 +594,36 @@ func testAccCheckInstanceDestroyWithProvider(s *terraform.State, provider *schem 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 = ` resource "alicloud_security_group" "tf_test_foo" { name = "tf_test_foo" @@ -609,11 +636,10 @@ resource "alicloud_security_group" "tf_test_bar" { } resource "alicloud_instance" "foo" { - # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" system_disk_category = "cloud_ssd" + system_disk_size = 80 instance_type = "ecs.n1.small" internet_charge_type = "PayByBandwidth" @@ -628,15 +654,20 @@ resource "alicloud_instance" "foo" { } ` const testAccInstanceConfigVPC = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { - name = "tf_test_foo" - cidr_block = "172.16.0.0/12" + 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 = "cn-beijing-b" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/21" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { @@ -647,7 +678,6 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" vswitch_id = "${alicloud_vswitch.foo.id}" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" @@ -666,15 +696,20 @@ resource "alicloud_instance" "foo" { ` const testAccInstanceConfigUserData = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { - name = "tf_test_foo" - cidr_block = "172.16.0.0/12" + 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 = "cn-beijing-b" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/21" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { @@ -685,7 +720,6 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" vswitch_id = "${alicloud_vswitch.foo.id}" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" # series II @@ -725,24 +759,22 @@ resource "alicloud_security_group" "tf_test_bar" { } resource "alicloud_instance" "foo" { - # cn-beijing - provider = "alicloud.beijing" - availability_zone = "cn-beijing-b" - image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" + # cn-beijing + provider = "alicloud.beijing" + image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" - internet_charge_type = "PayByBandwidth" + internet_charge_type = "PayByBandwidth" - instance_type = "ecs.n1.medium" - io_optimized = "optimized" - system_disk_category = "cloud_efficiency" - security_groups = ["${alicloud_security_group.tf_test_foo.id}"] - instance_name = "test_foo" + instance_type = "ecs.n1.medium" + io_optimized = "optimized" + system_disk_category = "cloud_efficiency" + security_groups = ["${alicloud_security_group.tf_test_foo.id}"] + instance_name = "test_foo" } resource "alicloud_instance" "bar" { # cn-shanghai provider = "alicloud.shanghai" - availability_zone = "cn-shanghai-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" internet_charge_type = "PayByBandwidth" @@ -768,7 +800,6 @@ resource "alicloud_security_group" "tf_test_bar" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" 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}"] instance_name = "test_foo" io_optimized = "optimized" + system_disk_category = "cloud_efficiency" }` const testAccInstanceConfig_multiSecurityGroup_add = ` @@ -796,7 +828,6 @@ resource "alicloud_security_group" "tf_test_add_sg" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" instance_type = "ecs.s2.large" @@ -805,6 +836,7 @@ resource "alicloud_instance" "foo" { "${alicloud_security_group.tf_test_add_sg.id}"] instance_name = "test_foo" io_optimized = "optimized" + system_disk_category = "cloud_efficiency" } ` @@ -814,9 +846,30 @@ resource "alicloud_security_group" "tf_test_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" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" instance_type = "ecs.s2.large" @@ -824,6 +877,7 @@ resource "alicloud_instance" "foo" { security_groups = ["${alicloud_security_group.tf_test_foo.id}"] instance_name = "test_foo" io_optimized = "optimized" + system_disk_category = "cloud_efficiency" } ` @@ -836,27 +890,32 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" instance_type = "ecs.s2.large" internet_charge_type = "PayByBandwidth" security_groups = ["${alicloud_security_group.tf_test_foo.*.id}"] instance_name = "test_foo" - io_optimized = "none" + io_optimized = "optimized" + system_disk_category = "cloud_efficiency" } ` const testAccInstanceNetworkInstanceSecurityGroups = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { - name = "tf_test_foo" - cidr_block = "172.16.0.0/12" + 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 = "cn-beijing-b" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/21" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { @@ -867,7 +926,6 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" vswitch_id = "${alicloud_vswitch.foo.id}" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" @@ -892,7 +950,6 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" # series II @@ -918,7 +975,6 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" # series II @@ -941,9 +997,30 @@ resource "alicloud_security_group" "tf_test_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" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" # series II @@ -965,9 +1042,30 @@ resource "alicloud_security_group" "tf_test_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" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" # series II @@ -984,15 +1082,20 @@ resource "alicloud_instance" "foo" { ` const testAccInstanceConfigPrivateIP = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { - name = "tf_test_foo" - cidr_block = "172.16.0.0/12" + 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/24" - availability_zone = "cn-beijing-b" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/24" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { @@ -1003,11 +1106,9 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" security_groups = ["${alicloud_security_group.tf_test_foo.id}"] vswitch_id = "${alicloud_vswitch.foo.id}" - private_ip = "172.16.0.229" # series II instance_type = "ecs.n1.medium" @@ -1017,16 +1118,21 @@ resource "alicloud_instance" "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" { - name = "tf_test_foo" - cidr_block = "172.16.0.0/12" + 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/24" - availability_zone = "cn-beijing-b" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/24" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { @@ -1037,11 +1143,9 @@ resource "alicloud_security_group" "tf_test_foo" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-b" security_groups = ["${alicloud_security_group.tf_test_foo.id}"] vswitch_id = "${alicloud_vswitch.foo.id}" - private_ip = "172.16.0.229" allocate_public_ip = "true" internet_max_bandwidth_out = 5 internet_charge_type = "PayByBandwidth" @@ -1055,52 +1159,56 @@ resource "alicloud_instance" "foo" { } ` const testAccVpcInstanceWithSecurityRule = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { - name = "tf_test_foo" - cidr_block = "10.1.0.0/21" + name = "tf_test_foo" + cidr_block = "10.1.0.0/21" } resource "alicloud_vswitch" "foo" { - vpc_id = "${alicloud_vpc.foo.id}" - cidr_block = "10.1.1.0/24" - availability_zone = "cn-beijing-c" + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "10.1.1.0/24" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } resource "alicloud_security_group" "tf_test_foo" { - name = "tf_test_foo" - description = "foo" - vpc_id = "${alicloud_vpc.foo.id}" + name = "tf_test_foo" + description = "foo" + vpc_id = "${alicloud_vpc.foo.id}" } resource "alicloud_security_group_rule" "ingress" { - type = "ingress" - ip_protocol = "tcp" - nic_type = "intranet" - 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" + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + 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" { - # cn-beijing - availability_zone = "cn-beijing-c" - security_groups = ["${alicloud_security_group.tf_test_foo.id}"] + # cn-beijing + security_groups = ["${alicloud_security_group.tf_test_foo.id}"] - vswitch_id = "${alicloud_vswitch.foo.id}" - allocate_public_ip = true + vswitch_id = "${alicloud_vswitch.foo.id}" + allocate_public_ip = true - # series II - instance_charge_type = "PostPaid" - instance_type = "ecs.n1.small" - internet_charge_type = "PayByBandwidth" - internet_max_bandwidth_out = 5 + # series II + instance_charge_type = "PostPaid" + instance_type = "ecs.n1.small" + internet_charge_type = "PayByBandwidth" + internet_max_bandwidth_out = 5 - system_disk_category = "cloud_efficiency" - image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" - instance_name = "test_foo" - io_optimized = "optimized" + system_disk_category = "cloud_efficiency" + image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" + instance_name = "test_foo" + io_optimized = "optimized" } ` diff --git a/builtin/providers/alicloud/resource_alicloud_nat_gateway.go b/builtin/providers/alicloud/resource_alicloud_nat_gateway.go index 51622d86eb..99e71347a5 100644 --- a/builtin/providers/alicloud/resource_alicloud_nat_gateway.go +++ b/builtin/providers/alicloud/resource_alicloud_nat_gateway.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "log" @@ -71,7 +72,7 @@ func resourceAliyunNatGateway() *schema.Resource { func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AliyunClient).vpcconn - args := &CreateNatGatewayArgs{ + args := &ecs.CreateNatGatewayArgs{ RegionId: getRegion(d, meta), VpcId: d.Get("vpc_id").(string), Spec: d.Get("spec").(string), @@ -79,11 +80,11 @@ func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) er bandwidthPackages := d.Get("bandwidth_packages").([]interface{}) - bandwidthPackageTypes := []BandwidthPackageType{} + bandwidthPackageTypes := []ecs.BandwidthPackageType{} for _, e := range bandwidthPackages { pack := e.(map[string]interface{}) - bandwidthPackage := BandwidthPackageType{ + bandwidthPackage := ecs.BandwidthPackageType{ IpCount: pack["ip_count"].(int), Bandwidth: pack["bandwidth"].(int), } @@ -106,8 +107,7 @@ func resourceAliyunNatGatewayCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("description"); ok { args.Description = v.(string) } - - resp, err := CreateNatGateway(conn, args) + resp, err := conn.CreateNatGateway(args) if err != nil { 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 { client := meta.(*AliyunClient) + conn := client.vpcconn natGateway, err := client.DescribeNatGateway(d.Id()) if err != nil { @@ -150,7 +151,7 @@ func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) er d.Partial(true) attributeUpdate := false - args := &ModifyNatGatewayAttributeArgs{ + args := &ecs.ModifyNatGatewayAttributeArgs{ RegionId: natGateway.RegionId, NatGatewayId: natGateway.NatGatewayId, } @@ -183,28 +184,28 @@ func resourceAliyunNatGatewayUpdate(d *schema.ResourceData, meta interface{}) er } if attributeUpdate { - if err := ModifyNatGatewayAttribute(client.vpcconn, args); err != nil { + if err := conn.ModifyNatGatewayAttribute(args); err != nil { return err } } if d.HasChange("spec") { d.SetPartial("spec") - var spec NatGatewaySpec + var spec ecs.NatGatewaySpec if v, ok := d.GetOk("spec"); ok { - spec = NatGatewaySpec(v.(string)) + spec = ecs.NatGatewaySpec(v.(string)) } else { // set default to small spec - spec = NatGatewaySmallSpec + spec = ecs.NatGatewaySmallSpec } - args := &ModifyNatGatewaySpecArgs{ + args := &ecs.ModifyNatGatewaySpecArgs{ RegionId: natGateway.RegionId, NatGatewayId: natGateway.NatGatewayId, Spec: spec, } - err := ModifyNatGatewaySpec(client.vpcconn, args) + err := conn.ModifyNatGatewaySpec(args) if err != nil { 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 { client := meta.(*AliyunClient) + conn := client.vpcconn 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), NatGatewayId: d.Id(), }) @@ -232,7 +234,7 @@ func resourceAliyunNatGatewayDelete(d *schema.ResourceData, meta interface{}) er retry := false for _, pack := range packages { - err = DeleteBandwidthPackage(client.vpcconn, &DeleteBandwidthPackageArgs{ + err = conn.DeleteBandwidthPackage(&ecs.DeleteBandwidthPackageArgs{ RegionId: getRegion(d, meta), 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.")) } - args := &DeleteNatGatewayArgs{ + args := &ecs.DeleteNatGatewayArgs{ RegionId: client.Region, NatGatewayId: d.Id(), } - err = DeleteNatGateway(client.vpcconn, args) + err = conn.DeleteNatGateway(args) if err != nil { er, _ := err.(*common.Error) 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, NatGatewayId: d.Id(), } - gw, _, gwErr := DescribeNatGateways(client.vpcconn, describeArgs) + gw, _, gwErr := conn.DescribeNatGateways(describeArgs) if gwErr != nil { log.Printf("[ERROR] Describe NatGateways failed.") diff --git a/builtin/providers/alicloud/resource_alicloud_nat_gateway_test.go b/builtin/providers/alicloud/resource_alicloud_nat_gateway_test.go index ad8fba1662..a928c5dc1a 100644 --- a/builtin/providers/alicloud/resource_alicloud_nat_gateway_test.go +++ b/builtin/providers/alicloud/resource_alicloud_nat_gateway_test.go @@ -3,13 +3,14 @@ package alicloud import ( "fmt" "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "testing" ) func TestAccAlicloudNatGateway_basic(t *testing.T) { - var nat NatGatewaySetType + var nat ecs.NatGatewaySetType testCheck := func(*terraform.State) error { if nat.BusinessStatus != "Normal" { @@ -55,7 +56,7 @@ func TestAccAlicloudNatGateway_basic(t *testing.T) { } func TestAccAlicloudNatGateway_spec(t *testing.T) { - var nat NatGatewaySetType + var nat ecs.NatGatewaySetType resource.Test(t, resource.TestCase{ 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 { rs, ok := s.RootModule().Resources[n] if !ok { @@ -151,6 +152,10 @@ func testAccCheckNatGatewayDestroy(s *terraform.State) error { } const testAccNatGatewayConfig = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "172.16.0.0/12" @@ -159,7 +164,7 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" 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" { @@ -169,11 +174,11 @@ resource "alicloud_nat_gateway" "foo" { bandwidth_packages = [{ ip_count = 1 bandwidth = 5 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }, { ip_count = 2 bandwidth = 10 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }] depends_on = [ "alicloud_vswitch.foo"] @@ -181,6 +186,10 @@ resource "alicloud_nat_gateway" "foo" { ` const testAccNatGatewayConfigSpec = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "172.16.0.0/12" @@ -189,7 +198,7 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" 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" { @@ -199,11 +208,11 @@ resource "alicloud_nat_gateway" "foo" { bandwidth_packages = [{ ip_count = 1 bandwidth = 5 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }, { ip_count = 2 bandwidth = 10 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }] depends_on = [ "alicloud_vswitch.foo"] @@ -211,6 +220,10 @@ resource "alicloud_nat_gateway" "foo" { ` const testAccNatGatewayConfigSpecUpgrade = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "172.16.0.0/12" @@ -219,7 +232,7 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" 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" { @@ -229,11 +242,11 @@ resource "alicloud_nat_gateway" "foo" { bandwidth_packages = [{ ip_count = 1 bandwidth = 5 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }, { ip_count = 2 bandwidth = 10 - zone = "cn-beijing-b" + zone = "${data.alicloud_zones.default.zones.0.id}" }] depends_on = [ "alicloud_vswitch.foo"] diff --git a/builtin/providers/alicloud/resource_alicloud_security_group.go b/builtin/providers/alicloud/resource_alicloud_security_group.go index f21ae4b270..5f85bfd294 100644 --- a/builtin/providers/alicloud/resource_alicloud_security_group.go +++ b/builtin/providers/alicloud/resource_alicloud_security_group.go @@ -6,7 +6,6 @@ import ( "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/helper/schema" "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.")) }) + } func buildAliyunSecurityGroupArgs(d *schema.ResourceData, meta interface{}) (*ecs.CreateSecurityGroupArgs, error) { diff --git a/builtin/providers/alicloud/resource_alicloud_security_group_rule.go b/builtin/providers/alicloud/resource_alicloud_security_group_rule.go index 4627d8e2b6..c43db23a80 100644 --- a/builtin/providers/alicloud/resource_alicloud_security_group_rule.go +++ b/builtin/providers/alicloud/resource_alicloud_security_group_rule.go @@ -34,6 +34,7 @@ func resourceAliyunSecurityGroupRule() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, ValidateFunc: validateSecurityRuleNicType, }, @@ -67,7 +68,6 @@ func resourceAliyunSecurityGroupRule() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "0.0.0.0/0", }, "source_security_group_id": &schema.Schema{ @@ -86,15 +86,17 @@ func resourceAliyunSecurityGroupRule() *schema.Resource { } 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) ptl := d.Get("ip_protocol").(string) port := d.Get("port_range").(string) + nicType := d.Get("nic_type").(string) var autherr error - switch GroupRuleDirection(ruleType) { + switch GroupRuleDirection(direction) { case GroupRuleIngress: args, err := buildAliyunSecurityIngressArgs(d, meta) if err != nil { @@ -114,10 +116,11 @@ func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interfac if autherr != nil { return fmt.Errorf( "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) } @@ -125,10 +128,11 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ client := meta.(*AliyunClient) parts := strings.Split(d.Id(), ":") sgId := parts[0] - types := parts[1] + direction := parts[1] ip_protocol := parts[2] 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 notFoundError(err) { @@ -137,7 +141,7 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ } 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("ip_protocol", strings.ToLower(string(rule.IpProtocol))) d.Set("nic_type", rule.NicType) @@ -146,7 +150,7 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ d.Set("priority", rule.Priority) d.Set("security_group_id", sgId) //support source and desc by type - if GroupRuleDirection(types) == GroupRuleIngress { + if GroupRuleDirection(direction) == GroupRuleIngress { d.Set("cidr_ip", rule.SourceCidrIp) d.Set("source_security_group_id", rule.SourceGroupId) d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount) @@ -161,17 +165,41 @@ func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{ func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { 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 { + return err + } + revokeArgs := &ecs.RevokeSecurityGroupArgs{ + AuthorizeSecurityGroupArgs: *args, + } + return client.RevokeSecurityGroup(revokeArgs) + } + + args, err := buildAliyunSecurityEgressArgs(d, meta) if err != nil { return err } - revokeArgs := &ecs.RevokeSecurityGroupArgs{ - AuthorizeSecurityGroupArgs: *args, + revokeArgs := &ecs.RevokeSecurityGroupEgressArgs{ + AuthorizeSecurityGroupEgressArgs: *args, } - return client.RevokeSecurityGroup(revokeArgs) + 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) { conn := meta.(*AliyunClient).ecsconn @@ -199,12 +227,17 @@ func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (* args.NicType = ecs.NicType(v) } - if v := d.Get("cidr_ip").(string); v != "" { - args.SourceCidrIp = v + cidrIp := d.Get("cidr_ip").(string) + 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 != "" { - args.SourceGroupId = v + if sourceGroupId != "" { + args.SourceGroupId = sourceGroupId } 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) } - if v := d.Get("cidr_ip").(string); v != "" { - args.DestCidrIp = v + cidrIp := d.Get("cidr_ip").(string) + 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 != "" { - args.DestGroupId = v + if sourceGroupId != "" { + args.DestGroupId = sourceGroupId } if v := d.Get("source_group_owner_account").(string); v != "" { diff --git a/builtin/providers/alicloud/resource_alicloud_security_group_rule_test.go b/builtin/providers/alicloud/resource_alicloud_security_group_rule_test.go index 7eb267fcba..0792966f2f 100644 --- a/builtin/providers/alicloud/resource_alicloud_security_group_rule_test.go +++ b/builtin/providers/alicloud/resource_alicloud_security_group_rule_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "log" + "regexp" "strings" "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) { 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 { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -128,7 +236,8 @@ func testAccCheckSecurityGroupRuleExists(n string, m *ecs.PermissionType) resour client := testAccProvider.Meta().(*AliyunClient) log.Printf("[WARN]get sg rule %s", 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 { return err @@ -152,7 +261,7 @@ func testAccCheckSecurityGroupRuleDestroy(s *terraform.State) error { } 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 { 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 = ` resource "alicloud_security_group" "foo" { vpc_id = "${alicloud_vpc.vpc.id}" @@ -231,6 +357,22 @@ resource "alicloud_security_group_rule" "ingress" { 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 = ` @@ -260,4 +402,27 @@ resource "alicloud_security_group_rule" "ingress2" { 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}" +} + + ` diff --git a/builtin/providers/alicloud/resource_alicloud_slb.go b/builtin/providers/alicloud/resource_alicloud_slb.go index 8bc787b3d6..f3d2af9d3c 100644 --- a/builtin/providers/alicloud/resource_alicloud_slb.go +++ b/builtin/providers/alicloud/resource_alicloud_slb.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "errors" "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/slb" "github.com/hashicorp/terraform/helper/hashcode" @@ -83,40 +84,124 @@ func resourceAliyunSlb() *schema.Resource { ValidateFunc: validateSlbListenerBandwidth, Required: true, }, - //http "scheduler": &schema.Schema{ Type: schema.TypeString, ValidateFunc: validateSlbListenerScheduler, Optional: true, - Default: "wrr", + Default: slb.WRRScheduler, }, - + //http & https "sticky_session": &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validateSlbListenerStickySession, - Optional: true, + Type: schema.TypeString, + ValidateFunc: validateAllowedStringValue([]string{ + string(slb.OnFlag), + string(slb.OffFlag)}), + Optional: true, + Default: slb.OffFlag, }, + //http & https "sticky_session_type": &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validateSlbListenerStickySessionType, + Type: schema.TypeString, + ValidateFunc: validateAllowedStringValue([]string{ + string(slb.InsertStickySessionType), + string(slb.ServerStickySessionType)}), + Optional: true, + }, + //http & https + "cookie_timeout": &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validateSlbListenerCookieTimeout, Optional: true, }, + //http & https "cookie": &schema.Schema{ Type: schema.TypeString, ValidateFunc: validateSlbListenerCookie, Optional: true, }, - "PersistenceTimeout": &schema.Schema{ + //tcp & udp + "persistence_timeout": &schema.Schema{ Type: schema.TypeInt, ValidateFunc: validateSlbListenerPersistenceTimeout, Optional: true, 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 "ssl_certificate_id": &schema.Schema{ Type: schema.TypeString, Optional: true, }, + //https + //"ca_certificate_id": &schema.Schema{ + // Type: schema.TypeString, + // Optional: true, + //}, }, }, Set: resourceAliyunSlbListenerHash, @@ -349,44 +434,53 @@ func resourceAliyunSlbListenerHash(v interface{}) int { } 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") { - args := &slb.CreateLoadBalancerTCPListenerArgs{ - LoadBalancerId: loadBalancerId, - ListenerPort: listener.LoadBalancerPort, - BackendServerPort: listener.InstancePort, - Bandwidth: listener.Bandwidth, - } - if err := conn.CreateLoadBalancerTCPListener(args); err != nil { + + args := getTcpListenerArgs(loadBalancerId, listener) + + if err := conn.CreateLoadBalancerTCPListener(&args); err != nil { return err } - } - - if listener.Protocol == strings.ToLower("http") { - args := &slb.CreateLoadBalancerHTTPListenerArgs{ - LoadBalancerId: loadBalancerId, - ListenerPort: listener.LoadBalancerPort, - BackendServerPort: listener.InstancePort, - Bandwidth: listener.Bandwidth, - StickySession: slb.OffFlag, - HealthCheck: slb.OffFlag, + } else if listener.Protocol == strings.ToLower("http") { + args, argsErr := getHttpListenerArgs(loadBalancerId, listener) + if paramErr := errTypeJudge(argsErr); paramErr != nil { + return paramErr } - if err := conn.CreateLoadBalancerHTTPListener(args); err != nil { + if err := conn.CreateLoadBalancerHTTPListener(&args); err != nil { 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{ - - HTTPListenerType: slb.HTTPListenerType{ - LoadBalancerId: loadBalancerId, - ListenerPort: listener.LoadBalancerPort, - BackendServerPort: listener.InstancePort, - Bandwidth: listener.Bandwidth, - StickySession: slb.OffFlag, - HealthCheck: slb.OffFlag, - }, + HTTPListenerType: listenerType, } if listener.SSLCertificateId == "" { 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 { return err } - } + } else if listener.Protocol == strings.ToLower("udp") { + args := getUdpListenerArgs(loadBalancerId, listener) - if listener.Protocol == strings.ToLower("udp") { - args := &slb.CreateLoadBalancerUDPListenerArgs{ - LoadBalancerId: loadBalancerId, - ListenerPort: listener.LoadBalancerPort, - BackendServerPort: listener.InstancePort, - Bandwidth: listener.Bandwidth, - } - - if err := conn.CreateLoadBalancerUDPListener(args); err != nil { + if err := conn.CreateLoadBalancerUDPListener(&args); err != nil { return err } } @@ -418,3 +505,102 @@ func createListener(conn *slb.Client, loadBalancerId string, listener *Listener) 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 +} diff --git a/builtin/providers/alicloud/resource_alicloud_slb_attachment_test.go b/builtin/providers/alicloud/resource_alicloud_slb_attachment_test.go index 90a70ead8b..5caa4a710e 100644 --- a/builtin/providers/alicloud/resource_alicloud_slb_attachment_test.go +++ b/builtin/providers/alicloud/resource_alicloud_slb_attachment_test.go @@ -79,9 +79,30 @@ resource "alicloud_security_group" "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" { # cn-beijing - availability_zone = "cn-beijing-b" image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" # series II diff --git a/builtin/providers/alicloud/resource_alicloud_slb_test.go b/builtin/providers/alicloud/resource_alicloud_slb_test.go index 3e68a4c149..42308f1873 100644 --- a/builtin/providers/alicloud/resource_alicloud_slb_test.go +++ b/builtin/providers/alicloud/resource_alicloud_slb_test.go @@ -85,7 +85,7 @@ func TestAccAlicloudSlb_listener(t *testing.T) { testListener := func() resource.TestCheckFunc { return func(*terraform.State) error { listenerPorts := slb.ListenerPorts.ListenerPort[0] - if listenerPorts != 161 { + if listenerPorts != 2001 { return fmt.Errorf("bad loadbalancer listener: %#v", listenerPorts) } @@ -260,21 +260,49 @@ resource "alicloud_slb" "listener" { "lb_port" = "21" "lb_protocol" = "tcp" "bandwidth" = 1 + "persistence_timeout" = 500 + "health_check_type" = "http" },{ "instance_port" = "8000" "lb_port" = "80" "lb_protocol" = "http" + "sticky_session" = "on" + "sticky_session_type" = "insert" + "cookie_timeout" = 800 "bandwidth" = 1 },{ - "instance_port" = "1611" - "lb_port" = "161" + "instance_port" = "8001" + "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" "bandwidth" = 1 + "persistence_timeout" = 700 }] } ` const testAccSlb4Vpc = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "172.16.0.0/12" @@ -283,7 +311,7 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" 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" { diff --git a/builtin/providers/alicloud/resource_alicloud_vroute_entry_test.go b/builtin/providers/alicloud/resource_alicloud_vroute_entry_test.go index cbdb59bef1..8726de64c6 100644 --- a/builtin/providers/alicloud/resource_alicloud_vroute_entry_test.go +++ b/builtin/providers/alicloud/resource_alicloud_vroute_entry_test.go @@ -124,6 +124,10 @@ func testAccCheckRouteEntryDestroy(s *terraform.State) error { } const testAccRouteEntryConfig = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "10.1.0.0/21" @@ -132,7 +136,7 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" 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" { @@ -162,7 +166,6 @@ resource "alicloud_security_group_rule" "ingress" { resource "alicloud_instance" "foo" { # cn-beijing - availability_zone = "cn-beijing-c" security_groups = ["${alicloud_security_group.tf_test_foo.id}"] vswitch_id = "${alicloud_vswitch.foo.id}" diff --git a/builtin/providers/alicloud/resource_alicloud_vswitch_test.go b/builtin/providers/alicloud/resource_alicloud_vswitch_test.go index bcd70a2cc1..1a1a75bd67 100644 --- a/builtin/providers/alicloud/resource_alicloud_vswitch_test.go +++ b/builtin/providers/alicloud/resource_alicloud_vswitch_test.go @@ -92,6 +92,10 @@ func testAccCheckVswitchDestroy(s *terraform.State) error { } const testAccVswitchConfig = ` +data "alicloud_zones" "default" { + "available_resource_creation"= "VSwitch" +} + resource "alicloud_vpc" "foo" { name = "tf_test_foo" cidr_block = "172.16.0.0/12" @@ -100,6 +104,6 @@ resource "alicloud_vpc" "foo" { resource "alicloud_vswitch" "foo" { vpc_id = "${alicloud_vpc.foo.id}" cidr_block = "172.16.0.0/21" - availability_zone = "cn-beijing-b" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" } ` diff --git a/builtin/providers/alicloud/service_alicloud_ecs.go b/builtin/providers/alicloud/service_alicloud_ecs.go index 2c892ce242..4ff0e5f04d 100644 --- a/builtin/providers/alicloud/service_alicloud_ecs.go +++ b/builtin/providers/alicloud/service_alicloud_ecs.go @@ -84,6 +84,24 @@ func (client *AliyunClient) DescribeZone(zoneID string) (*ecs.ZoneType, error) { 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) { idsStr, jerr := json.Marshal(ids) if jerr != nil { @@ -119,6 +137,23 @@ func (client *AliyunClient) QueryInstancesById(id string) (instance *ecs.Instanc 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 func (client *AliyunClient) ResourceAvailable(zone *ecs.ZoneType, resourceType ecs.ResourceType) error { available := false @@ -186,15 +221,26 @@ func (client *AliyunClient) DescribeSecurity(securityGroupId string) (*ecs.Descr 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 { return nil, err } 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 } } @@ -203,6 +249,11 @@ func (client *AliyunClient) DescribeSecurityGroupRule(securityGroupId, types, ip } 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) } + +func (client *AliyunClient) RevokeSecurityGroupEgress(args *ecs.RevokeSecurityGroupEgressArgs) error { + //when the rule is not exist, api will return success(200) + return client.ecsconn.RevokeSecurityGroupEgress(args) +} diff --git a/builtin/providers/alicloud/service_alicloud_rds.go b/builtin/providers/alicloud/service_alicloud_rds.go new file mode 100644 index 0000000000..903374fe68 --- /dev/null +++ b/builtin/providers/alicloud/service_alicloud_rds.go @@ -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 +} diff --git a/builtin/providers/alicloud/service_alicloud_vpc.go b/builtin/providers/alicloud/service_alicloud_vpc.go index 102c611653..775fe112cc 100644 --- a/builtin/providers/alicloud/service_alicloud_vpc.go +++ b/builtin/providers/alicloud/service_alicloud_vpc.go @@ -24,14 +24,14 @@ func (client *AliyunClient) DescribeEipAddress(allocationId string) (*ecs.EipAdd 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, NatGatewayId: natGatewayId, } - natGateways, _, err := DescribeNatGateways(client.ecsconn, args) + natGateways, _, err := client.vpcconn.DescribeNatGateways(args) if err != nil { return nil, err } @@ -132,3 +132,23 @@ func (client *AliyunClient) QueryRouteEntry(routeTableId, cidrBlock, nextHopType } 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}} +} diff --git a/builtin/providers/alicloud/validators.go b/builtin/providers/alicloud/validators.go index 9c7fec01af..9687e68e8f 100644 --- a/builtin/providers/alicloud/validators.go +++ b/builtin/providers/alicloud/validators.go @@ -2,17 +2,27 @@ package alicloud import ( "fmt" - "regexp" + "net" + "strconv" "strings" "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" - "github.com/hashicorp/terraform/helper/validation" + "github.com/denverdino/aliyungo/slb" + "github.com/hashicorp/terraform/helper/schema" + "regexp" ) // common 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) { @@ -28,11 +38,12 @@ func validateInstanceProtocol(v interface{}, k string) (ws []string, errors []er // ecs func validateDiskCategory(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - string(ecs.DiskCategoryCloud), - string(ecs.DiskCategoryCloudEfficiency), - string(ecs.DiskCategoryCloudSSD), - }, false)(v, k) + category := ecs.DiskCategory(v.(string)) + if category != ecs.DiskCategoryCloud && category != ecs.DiskCategoryCloudEfficiency && category != ecs.DiskCategoryCloudSSD { + errors = append(errors, fmt.Errorf("%s must be one of %s %s %s", k, ecs.DiskCategoryCloud, ecs.DiskCategoryCloudEfficiency, ecs.DiskCategoryCloudSSD)) + } + + return } 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) { - 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) { @@ -71,7 +87,12 @@ func validateDiskName(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 @@ -89,114 +110,225 @@ func validateSecurityGroupName(v interface{}, k string) (ws []string, errors []e } 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) { - return validation.StringInSlice([]string{ - string(GroupRuleIngress), - string(GroupRuleEgress), - }, false)(v, k) + rt := GroupRuleDirection(v.(string)) + if rt != GroupRuleIngress && rt != GroupRuleEgress { + errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRuleIngress, GroupRuleEgress)) + } + + return } func validateSecurityRuleIpProtocol(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - string(GroupRuleTcp), - string(GroupRuleUdp), - string(GroupRuleIcmp), - string(GroupRuleGre), - string(GroupRuleAll), - }, false)(v, k) + pt := GroupRuleIpProtocol(v.(string)) + if pt != GroupRuleTcp && pt != GroupRuleUdp && pt != GroupRuleIcmp && pt != GroupRuleGre && pt != GroupRuleAll { + errors = append(errors, fmt.Errorf("%s must be one of %s %s %s %s %s", k, + GroupRuleTcp, GroupRuleUdp, GroupRuleIcmp, GroupRuleGre, GroupRuleAll)) + } + + return } func validateSecurityRuleNicType(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - string(GroupRuleInternet), - string(GroupRuleIntranet), - }, false)(v, k) + pt := GroupRuleNicType(v.(string)) + if pt != GroupRuleInternet && pt != GroupRuleIntranet { + errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRuleInternet, GroupRuleIntranet)) + } + + return } func validateSecurityRulePolicy(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - string(GroupRulePolicyAccept), - string(GroupRulePolicyDrop), - }, false)(v, k) + pt := GroupRulePolicy(v.(string)) + if pt != GroupRulePolicyAccept && pt != GroupRulePolicyDrop { + errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, GroupRulePolicyAccept, GroupRulePolicyDrop)) + } + + return } 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 // represents a network address - it adds an error otherwise 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) { - return validation.StringInSlice([]string{ - string(ecs.NextHopIntance), - string(ecs.NextHopTunnel), - }, false)(v, k) + nht := ecs.NextHopType(v.(string)) + if nht != ecs.NextHopIntance && nht != ecs.NextHopTunnel { + errors = append(errors, fmt.Errorf("%s must be one of %s %s", k, + ecs.NextHopIntance, ecs.NextHopTunnel)) + } + + return } 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 // represents a IoOptimized - it adds an error otherwise func validateIoOptimized(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - "", - string(ecs.IoOptimizedNone), - string(ecs.IoOptimizedOptimized), - }, false)(v, k) + if value := v.(string); value != "" { + ioOptimized := ecs.IoOptimized(value) + if ioOptimized != ecs.IoOptimizedNone && + ioOptimized != ecs.IoOptimizedOptimized { + 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 func validateInstanceNetworkType(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - "", - string(ClassicNet), - string(VpcNet), - }, false)(v, k) + if value := v.(string); value != "" { + network := InstanceNetWork(value) + if network != ClassicNet && + network != VpcNet { + 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) { - return validation.StringInSlice([]string{ - "", - string(common.PrePaid), - string(common.PostPaid), - }, false)(v, k) + if value := v.(string); value != "" { + chargeType := common.InstanceChargeType(value) + if chargeType != common.PrePaid && + chargeType != common.PostPaid { + 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) { - return validation.StringInSlice([]string{ - "", - string(common.PayByBandwidth), - string(common.PayByTraffic), - }, false)(v, k) + if value := v.(string); value != "" { + chargeType := common.InternetChargeType(value) + if chargeType != common.PayByBandwidth && + chargeType != common.PayByTraffic { + 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) { - 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 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) { - return validation.StringInSlice([]string{ - "paybybandwidth", - "paybytraffic", - }, false)(v, k) + if value := v.(string); value != "" { + chargeType := common.InternetChargeType(value) + + 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) { - 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) { @@ -211,23 +343,180 @@ func validateSlbListenerBandwidth(v interface{}, k string) (ws []string, errors } 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) { - return validation.StringInSlice([]string{"", "on", "off"}, false)(v, k) -} + if scheduler != "wrr" && scheduler != "wlc" { + 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 validation.StringInSlice([]string{"", "insert", "server"}, false)(v, k) + return } 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) { - 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 @@ -244,14 +533,19 @@ func validateNameRegex(v interface{}, k string) (ws []string, errors []error) { } func validateImageOwners(v interface{}, k string) (ws []string, errors []error) { - return validation.StringInSlice([]string{ - "", - string(ecs.ImageOwnerSystem), - string(ecs.ImageOwnerSelf), - string(ecs.ImageOwnerOthers), - string(ecs.ImageOwnerMarketplace), - string(ecs.ImageOwnerDefault), - }, false)(v, k) + if value := v.(string); value != "" { + owners := ecs.ImageOwnerAlias(value) + if owners != ecs.ImageOwnerSystem && + owners != ecs.ImageOwnerSelf && + owners != ecs.ImageOwnerOthers && + owners != ecs.ImageOwnerMarketplace && + owners != ecs.ImageOwnerDefault { + 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) { diff --git a/builtin/providers/alicloud/validators_test.go b/builtin/providers/alicloud/validators_test.go index fa5a8aed00..7d40de6b79 100644 --- a/builtin/providers/alicloud/validators_test.go +++ b/builtin/providers/alicloud/validators_test.go @@ -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) + } + } +} diff --git a/builtin/providers/aws/data_source_aws_cloudformation_stack.go b/builtin/providers/aws/data_source_aws_cloudformation_stack.go index 0966ddb548..b834e0a29b 100644 --- a/builtin/providers/aws/data_source_aws_cloudformation_stack.go +++ b/builtin/providers/aws/data_source_aws_cloudformation_stack.go @@ -58,6 +58,10 @@ func dataSourceAwsCloudFormationStack() *schema.Resource { Type: schema.TypeInt, Computed: true, }, + "iam_role_arn": { + Type: schema.TypeString, + Computed: true, + }, "tags": { Type: schema.TypeMap, Computed: true, @@ -86,6 +90,7 @@ func dataSourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface d.Set("description", stack.Description) d.Set("disable_rollback", stack.DisableRollback) d.Set("timeout_in_minutes", stack.TimeoutInMinutes) + d.Set("iam_role_arn", stack.RoleARN) if len(stack.NotificationARNs) > 0 { d.Set("notification_arns", schema.NewSet(schema.HashString, flattenStringList(stack.NotificationARNs))) diff --git a/builtin/providers/aws/data_source_aws_iam_server_certificate_test.go b/builtin/providers/aws/data_source_aws_iam_server_certificate_test.go index fc6de4303d..b840ac1158 100644 --- a/builtin/providers/aws/data_source_aws_iam_server_certificate_test.go +++ b/builtin/providers/aws/data_source_aws_iam_server_certificate_test.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" ) @@ -18,15 +19,15 @@ func timePtr(t time.Time) *time.Time { func TestResourceSortByExpirationDate(t *testing.T) { certs := []*iam.ServerCertificateMetadata{ - &iam.ServerCertificateMetadata{ + { ServerCertificateName: aws.String("oldest"), Expiration: timePtr(time.Now()), }, - &iam.ServerCertificateMetadata{ + { ServerCertificateName: aws.String("latest"), Expiration: timePtr(time.Now().Add(3 * time.Hour)), }, - &iam.ServerCertificateMetadata{ + { ServerCertificateName: aws.String("in between"), Expiration: timePtr(time.Now().Add(2 * time.Hour)), }, @@ -38,13 +39,18 @@ func TestResourceSortByExpirationDate(t *testing.T) { } func TestAccAWSDataSourceIAMServerCertificate_basic(t *testing.T) { + rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckIAMServerCertificateDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsDataIAMServerCertConfig, + Config: testAccIAMServerCertConfig(rInt), + }, + { + Config: testAccAwsDataIAMServerCertConfig(rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("aws_iam_server_certificate.test_cert", "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" { name = "${aws_iam_server_certificate.test_cert.name}" latest = true } -`, testAccIAMServerCertConfig) +`, testAccIAMServerCertConfig(rInt)) +} var testAccAwsDataIAMServerCertConfigMatchNamePrefix = ` data "aws_iam_server_certificate" "test" { diff --git a/builtin/providers/aws/data_source_aws_route53_zone_test.go b/builtin/providers/aws/data_source_aws_route53_zone_test.go index 42d0eb72f6..49c684f29e 100644 --- a/builtin/providers/aws/data_source_aws_route53_zone_test.go +++ b/builtin/providers/aws/data_source_aws_route53_zone_test.go @@ -11,7 +11,7 @@ import ( func TestAccDataSourceAwsRoute53Zone(t *testing.T) { rInt := acctest.RandInt() - publicResourceName := "aws_route53_zon.test" + publicResourceName := "aws_route53_zone.test" publicDomain := fmt.Sprintf("terraformtestacchz-%d.com.", rInt) privateResourceName := "aws_route53_zone.test_private" privateDomain := fmt.Sprintf("test.acc-%d.", rInt) diff --git a/builtin/providers/aws/import_aws_customer_gateway_test.go b/builtin/providers/aws/import_aws_customer_gateway_test.go index 37662760db..0c066a33f0 100644 --- a/builtin/providers/aws/import_aws_customer_gateway_test.go +++ b/builtin/providers/aws/import_aws_customer_gateway_test.go @@ -3,19 +3,20 @@ package aws import ( "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" ) func TestAccAWSCustomerGateway_importBasic(t *testing.T) { resourceName := "aws_customer_gateway.foo" - + randInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccCustomerGatewayConfig, + Config: testAccCustomerGatewayConfig(randInt), }, resource.TestStep{ diff --git a/builtin/providers/aws/import_aws_efs_file_system_test.go b/builtin/providers/aws/import_aws_efs_file_system_test.go index 46e0de4595..885ee9ddda 100644 --- a/builtin/providers/aws/import_aws_efs_file_system_test.go +++ b/builtin/providers/aws/import_aws_efs_file_system_test.go @@ -3,11 +3,13 @@ package aws import ( "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" ) func TestAccAWSEFSFileSystem_importBasic(t *testing.T) { resourceName := "aws_efs_file_system.foo-with-tags" + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -15,7 +17,7 @@ func TestAccAWSEFSFileSystem_importBasic(t *testing.T) { CheckDestroy: testAccCheckEfsFileSystemDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSEFSFileSystemConfigWithTags, + Config: testAccAWSEFSFileSystemConfigWithTags(rInt), }, resource.TestStep{ diff --git a/builtin/providers/aws/import_aws_iam_server_certificate_test.go b/builtin/providers/aws/import_aws_iam_server_certificate_test.go new file mode 100644 index 0000000000..6d342ec76b --- /dev/null +++ b/builtin/providers/aws/import_aws_iam_server_certificate_test.go @@ -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"}, + }, + }, + }) +} diff --git a/builtin/providers/aws/resource_aws_alb_listener_rule.go b/builtin/providers/aws/resource_aws_alb_listener_rule.go index 1d5ba163f6..21292753cc 100644 --- a/builtin/providers/aws/resource_aws_alb_listener_rule.go +++ b/builtin/providers/aws/resource_aws_alb_listener_rule.go @@ -31,10 +31,12 @@ func resourceAwsAlbListenerRule() *schema.Resource { "listener_arn": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "priority": { Type: schema.TypeInt, Required: true, + ForceNew: true, ValidateFunc: validateAwsAlbListenerRulePriority, }, "action": { @@ -66,6 +68,7 @@ func resourceAwsAlbListenerRule() *schema.Resource { }, "values": { Type: schema.TypeList, + MaxItems: 1, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, }, @@ -183,42 +186,75 @@ func resourceAwsAlbListenerRuleRead(d *schema.ResourceData, meta interface{}) er func resourceAwsAlbListenerRuleUpdate(d *schema.ResourceData, meta interface{}) error { 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{ RuleArn: aws.String(d.Id()), } - actions := d.Get("action").([]interface{}) - params.Actions = make([]*elbv2.Action, len(actions)) - for i, action := range actions { - actionMap := action.(map[string]interface{}) - params.Actions[i] = &elbv2.Action{ - TargetGroupArn: aws.String(actionMap["target_group_arn"].(string)), - Type: aws.String(actionMap["type"].(string)), + if d.HasChange("action") { + actions := d.Get("action").([]interface{}) + params.Actions = make([]*elbv2.Action, len(actions)) + for i, action := range actions { + actionMap := action.(map[string]interface{}) + params.Actions[i] = &elbv2.Action{ + TargetGroupArn: aws.String(actionMap["target_group_arn"].(string)), + Type: aws.String(actionMap["type"].(string)), + } + } + requestUpdate = true + d.SetPartial("action") + } + + if d.HasChange("condition") { + conditions := d.Get("condition").([]interface{}) + params.Conditions = make([]*elbv2.RuleCondition, len(conditions)) + for i, condition := range conditions { + conditionMap := condition.(map[string]interface{}) + values := conditionMap["values"].([]interface{}) + params.Conditions[i] = &elbv2.RuleCondition{ + Field: aws.String(conditionMap["field"].(string)), + Values: make([]*string, len(values)), + } + for j, value := range values { + params.Conditions[i].Values[j] = aws.String(value.(string)) + } + } + requestUpdate = true + d.SetPartial("condition") + } + + if requestUpdate { + resp, err := elbconn.ModifyRule(params) + if err != nil { + return errwrap.Wrapf("Error modifying ALB Listener Rule: {{err}}", err) + } + + if len(resp.Rules) == 0 { + return errors.New("Error modifying creating ALB Listener Rule: no rules returned in response") } } - conditions := d.Get("condition").([]interface{}) - params.Conditions = make([]*elbv2.RuleCondition, len(conditions)) - for i, condition := range conditions { - conditionMap := condition.(map[string]interface{}) - values := conditionMap["values"].([]interface{}) - params.Conditions[i] = &elbv2.RuleCondition{ - Field: aws.String(conditionMap["field"].(string)), - Values: make([]*string, len(values)), - } - for j, value := range values { - params.Conditions[i].Values[j] = aws.String(value.(string)) - } - } - - resp, err := elbconn.ModifyRule(params) - if err != nil { - return errwrap.Wrapf("Error modifying ALB Listener Rule: {{err}}", err) - } - - if len(resp.Rules) == 0 { - return errors.New("Error modifying creating ALB Listener Rule: no rules returned in response") - } + d.Partial(false) return resourceAwsAlbListenerRuleRead(d, meta) } diff --git a/builtin/providers/aws/resource_aws_alb_listener_rule_test.go b/builtin/providers/aws/resource_aws_alb_listener_rule_test.go index e27a0a57db..8ddc0ef9ee 100644 --- a/builtin/providers/aws/resource_aws_alb_listener_rule_test.go +++ b/builtin/providers/aws/resource_aws_alb_listener_rule_test.go @@ -3,6 +3,7 @@ package aws import ( "errors" "fmt" + "regexp" "testing" "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 { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -104,6 +189,117 @@ func testAccCheckAWSALBListenerRuleDestroy(s *terraform.State) error { 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 { return fmt.Sprintf(`resource "aws_alb_listener_rule" "static" { listener_arn = "${aws_alb_listener.front_end.arn}" @@ -214,3 +410,238 @@ resource "aws_security_group" "alb_test" { } }`, 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) +} diff --git a/builtin/providers/aws/resource_aws_cloudformation_stack.go b/builtin/providers/aws/resource_aws_cloudformation_stack.go index a4321d34bb..8b9f1617ec 100644 --- a/builtin/providers/aws/resource_aws_cloudformation_stack.go +++ b/builtin/providers/aws/resource_aws_cloudformation_stack.go @@ -22,12 +22,12 @@ func resourceAwsCloudFormationStack() *schema.Resource { Delete: resourceAwsCloudFormationStackDelete, Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "template_body": &schema.Schema{ + "template_body": { Type: schema.TypeString, Optional: true, Computed: true, @@ -37,42 +37,42 @@ func resourceAwsCloudFormationStack() *schema.Resource { return template }, }, - "template_url": &schema.Schema{ + "template_url": { Type: schema.TypeString, Optional: true, }, - "capabilities": &schema.Schema{ + "capabilities": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "disable_rollback": &schema.Schema{ + "disable_rollback": { Type: schema.TypeBool, Optional: true, ForceNew: true, }, - "notification_arns": &schema.Schema{ + "notification_arns": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, - "on_failure": &schema.Schema{ + "on_failure": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "parameters": &schema.Schema{ + "parameters": { Type: schema.TypeMap, Optional: true, Computed: true, }, - "outputs": &schema.Schema{ + "outputs": { Type: schema.TypeMap, Computed: true, }, - "policy_body": &schema.Schema{ + "policy_body": { Type: schema.TypeString, Optional: true, Computed: true, @@ -82,20 +82,24 @@ func resourceAwsCloudFormationStack() *schema.Resource { return json }, }, - "policy_url": &schema.Schema{ + "policy_url": { Type: schema.TypeString, Optional: true, }, - "timeout_in_minutes": &schema.Schema{ + "timeout_in_minutes": { Type: schema.TypeInt, Optional: true, ForceNew: true, }, - "tags": &schema.Schema{ + "tags": { Type: schema.TypeMap, Optional: 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) } } + if v, ok := d.GetOk("iam_role_arn"); ok { + input.RoleARN = aws.String(v.(string)) + } log.Printf("[DEBUG] Creating CloudFormation Stack: %s", input) resp, err := conn.CreateStack(&input) @@ -297,6 +304,7 @@ func resourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{} d.Set("name", stack.StackName) d.Set("arn", stack.StackId) + d.Set("iam_role_arn", stack.RoleARN) if stack.TimeoutInMinutes != nil { 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)) } + if d.HasChange("iam_role_arn") { + input.RoleARN = aws.String(d.Get("iam_role_arn").(string)) + } + log.Printf("[DEBUG] Updating CloudFormation stack: %s", input) stack, err := conn.UpdateStack(input) if err != nil { diff --git a/builtin/providers/aws/resource_aws_cloudformation_stack_test.go b/builtin/providers/aws/resource_aws_cloudformation_stack_test.go index 40d2eb2c7d..d7aae42fc1 100644 --- a/builtin/providers/aws/resource_aws_cloudformation_stack_test.go +++ b/builtin/providers/aws/resource_aws_cloudformation_stack_test.go @@ -20,7 +20,7 @@ func TestAccAWSCloudFormation_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.network", &stack), @@ -38,7 +38,7 @@ func TestAccAWSCloudFormation_yaml(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_yaml, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.yaml", &stack), @@ -56,7 +56,7 @@ func TestAccAWSCloudFormation_defaultParams(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_defaultParams, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.asg-demo", &stack), @@ -75,7 +75,7 @@ func TestAccAWSCloudFormation_allAttributes(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_allAttributesWithBodies, Check: resource.ComposeTestCheckFunc( 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.TestStep{ + { Config: testAccAWSCloudFormationConfig_allAttributesWithBodies_modified, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.full", &stack), @@ -124,13 +124,13 @@ func TestAccAWSCloudFormation_withParams(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_withParams, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack), ), }, - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_withParams_modified, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with_params", &stack), @@ -149,13 +149,13 @@ func TestAccAWSCloudFormation_withUrl_withParams(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_templateUrl_withParams, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack), ), }, - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_templateUrl_withParams_modified, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params", &stack), @@ -173,7 +173,7 @@ func TestAccAWSCloudFormation_withUrl_withParams_withYaml(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudFormationDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudFormationConfig_templateUrl_withParams_withYaml, Check: resource.ComposeTestCheckFunc( testAccCheckCloudFormationStackExists("aws_cloudformation_stack.with-url-and-params-and-yaml", &stack), diff --git a/builtin/providers/aws/resource_aws_customer_gateway_test.go b/builtin/providers/aws/resource_aws_customer_gateway_test.go index 1938ce0bdd..118c2d71e2 100644 --- a/builtin/providers/aws/resource_aws_customer_gateway_test.go +++ b/builtin/providers/aws/resource_aws_customer_gateway_test.go @@ -10,12 +10,14 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) func TestAccAWSCustomerGateway_basic(t *testing.T) { var gateway ec2.CustomerGateway + randInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, IDRefreshName: "aws_customer_gateway.foo", @@ -23,19 +25,19 @@ func TestAccAWSCustomerGateway_basic(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfig, + Config: testAccCustomerGatewayConfig(randInt), Check: resource.ComposeTestCheckFunc( testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), ), }, { - Config: testAccCustomerGatewayConfigUpdateTags, + Config: testAccCustomerGatewayConfigUpdateTags(randInt), Check: resource.ComposeTestCheckFunc( testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), ), }, { - Config: testAccCustomerGatewayConfigForceReplace, + Config: testAccCustomerGatewayConfigForceReplace(randInt), Check: resource.ComposeTestCheckFunc( testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), ), @@ -46,6 +48,7 @@ func TestAccAWSCustomerGateway_basic(t *testing.T) { func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) { var gateway ec2.CustomerGateway + randInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, IDRefreshName: "aws_customer_gateway.foo", @@ -53,13 +56,13 @@ func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) { CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfig, + Config: testAccCustomerGatewayConfig(randInt), Check: resource.ComposeTestCheckFunc( testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), ), }, { - Config: testAccCustomerGatewayConfigIdentical, + Config: testAccCustomerGatewayConfigIdentical(randInt), ExpectError: regexp.MustCompile("An existing customer gateway"), }, }, @@ -68,13 +71,14 @@ func TestAccAWSCustomerGateway_similarAlreadyExists(t *testing.T) { func TestAccAWSCustomerGateway_disappears(t *testing.T) { var gateway ec2.CustomerGateway + randInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckCustomerGatewayDestroy, Steps: []resource.TestStep{ { - Config: testAccCustomerGatewayConfig, + Config: testAccCustomerGatewayConfig(randInt), Check: resource.ComposeTestCheckFunc( testAccCheckCustomerGateway("aws_customer_gateway.foo", &gateway), testAccAWSCustomerGatewayDisappears(&gateway), @@ -190,59 +194,67 @@ func testAccCheckCustomerGateway(gatewayResource string, cgw *ec2.CustomerGatewa } } -const testAccCustomerGatewayConfig = ` -resource "aws_customer_gateway" "foo" { - bgp_asn = 65000 - ip_address = "172.0.0.1" - type = "ipsec.1" - tags { - Name = "foo-gateway" - } -} -` - -const testAccCustomerGatewayConfigIdentical = ` -resource "aws_customer_gateway" "foo" { - bgp_asn = 65000 - ip_address = "172.0.0.1" - type = "ipsec.1" - tags { - Name = "foo-gateway" +func testAccCustomerGatewayConfig(randInt int) string { + return fmt.Sprintf(` + resource "aws_customer_gateway" "foo" { + bgp_asn = 65000 + ip_address = "172.0.0.1" + type = "ipsec.1" + tags { + Name = "foo-gateway-%d" + } } + `, randInt) } -resource "aws_customer_gateway" "identical" { - bgp_asn = 65000 - ip_address = "172.0.0.1" - type = "ipsec.1" - tags { - Name = "foo-gateway-identical" - } +func testAccCustomerGatewayConfigIdentical(randInt int) string { + return fmt.Sprintf(` + resource "aws_customer_gateway" "foo" { + bgp_asn = 65000 + ip_address = "172.0.0.1" + type = "ipsec.1" + tags { + 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. -const testAccCustomerGatewayConfigUpdateTags = ` -resource "aws_customer_gateway" "foo" { - bgp_asn = 65000 - ip_address = "172.0.0.1" - type = "ipsec.1" - tags { - Name = "foo-gateway" - Another = "tag" - } +func testAccCustomerGatewayConfigUpdateTags(randInt int) string { + return fmt.Sprintf(` + resource "aws_customer_gateway" "foo" { + bgp_asn = 65000 + ip_address = "172.0.0.1" + type = "ipsec.1" + tags { + Name = "foo-gateway-%d" + Another = "tag-%d" + } + } + `, randInt, randInt) } -` // Change the ip_address. -const testAccCustomerGatewayConfigForceReplace = ` -resource "aws_customer_gateway" "foo" { - bgp_asn = 65000 - ip_address = "172.10.10.1" - type = "ipsec.1" - tags { - Name = "foo-gateway" - Another = "tag" +func testAccCustomerGatewayConfigForceReplace(randInt int) string { + return fmt.Sprintf(` + resource "aws_customer_gateway" "foo" { + bgp_asn = 65000 + ip_address = "172.10.10.1" + type = "ipsec.1" + tags { + Name = "foo-gateway-%d" + Another = "tag-%d" + } } + `, randInt, randInt) } -` diff --git a/builtin/providers/aws/resource_aws_default_network_acl.go b/builtin/providers/aws/resource_aws_default_network_acl.go index 44443e9242..419972b18a 100644 --- a/builtin/providers/aws/resource_aws_default_network_acl.go +++ b/builtin/providers/aws/resource_aws_default_network_acl.go @@ -9,11 +9,14 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -// ACL Network ACLs all contain an explicit deny-all rule that cannot be -// destroyed or changed by users. This rule is numbered very high to be a +// ACL Network ACLs all contain explicit deny-all rules that cannot be +// destroyed or changed by users. This rules are numbered very high to be a // catch-all. // 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 { return &schema.Resource{ @@ -258,7 +261,8 @@ func revokeAllNetworkACLEntries(netaclId string, meta interface{}) error { for _, e := range networkAcl.Entries { // 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 - if *e.RuleNumber == awsDefaultAclRuleNumber { + if *e.RuleNumber == awsDefaultAclRuleNumberIpv4 || + *e.RuleNumber == awsDefaultAclRuleNumberIpv6 { continue } diff --git a/builtin/providers/aws/resource_aws_default_network_acl_test.go b/builtin/providers/aws/resource_aws_default_network_acl_test.go index 628943634b..c5f9e02d1a 100644 --- a/builtin/providers/aws/resource_aws_default_network_acl_test.go +++ b/builtin/providers/aws/resource_aws_default_network_acl_test.go @@ -36,8 +36,27 @@ func TestAccAWSDefaultNetworkAcl_basic(t *testing.T) { resource.TestStep{ Config: testAccAWSDefaultNetworkConfig_basic, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + 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{ Config: testAccAWSDefaultNetworkConfig_deny_ingress, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{defaultEgressAcl}, 0), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{defaultEgressAcl}, 0, 2), ), }, }, @@ -77,8 +96,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetRemoval(t *testing.T) { resource.TestStep{ Config: testAccAWSDefaultNetworkConfig_Subnets, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2), ), }, @@ -88,8 +107,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetRemoval(t *testing.T) { resource.TestStep{ Config: testAccAWSDefaultNetworkConfig_Subnets_remove, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2), ), ExpectNonEmptyPlan: true, }, @@ -108,8 +127,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetReassign(t *testing.T) { resource.TestStep{ Config: testAccAWSDefaultNetworkConfig_Subnets, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 2, 2), ), }, @@ -128,8 +147,8 @@ func TestAccAWSDefaultNetworkAcl_SubnetReassign(t *testing.T) { resource.TestStep{ Config: testAccAWSDefaultNetworkConfig_Subnets_move, Check: resource.ComposeTestCheckFunc( - testAccGetWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), - testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0), + testAccGetAWSDefaultNetworkAcl("aws_default_network_acl.default", &networkAcl), + testAccCheckAWSDefaultACLAttributes(&networkAcl, []*ec2.NetworkAclEntry{}, 0, 2), ), }, }, @@ -141,14 +160,14 @@ func testAccCheckAWSDefaultNetworkAclDestroy(s *terraform.State) error { 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 { aclEntriesCount := len(acl.Entries) ruleCount := len(rules) - // Default ACL has 2 hidden rules we can't do anything about - ruleCount = ruleCount + 2 + // Default ACL has hidden rules we can't do anything about + ruleCount = ruleCount + hiddenRuleCount if 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 { rs, ok := s.RootModule().Resources[n] 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" + } +} +` diff --git a/builtin/providers/aws/resource_aws_dms_replication_instance_test.go b/builtin/providers/aws/resource_aws_dms_replication_instance_test.go index 17e7f85c8d..3b6bb0d0ef 100644 --- a/builtin/providers/aws/resource_aws_dms_replication_instance_test.go +++ b/builtin/providers/aws/resource_aws_dms_replication_instance_test.go @@ -169,7 +169,7 @@ resource "aws_dms_replication_instance" "dms_replication_instance" { func dmsReplicationInstanceConfigUpdate(randId string) string { return fmt.Sprintf(` 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\"}]}" } diff --git a/builtin/providers/aws/resource_aws_dms_replication_subnet_group_test.go b/builtin/providers/aws/resource_aws_dms_replication_subnet_group_test.go index 574745f9e2..a6da50c9cc 100644 --- a/builtin/providers/aws/resource_aws_dms_replication_subnet_group_test.go +++ b/builtin/providers/aws/resource_aws_dms_replication_subnet_group_test.go @@ -102,7 +102,7 @@ func dmsReplicationSubnetGroupDestroy(s *terraform.State) error { func dmsReplicationSubnetGroupConfig(randId string) string { return fmt.Sprintf(` 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\"}]}" } diff --git a/builtin/providers/aws/resource_aws_dms_replication_task_test.go b/builtin/providers/aws/resource_aws_dms_replication_task_test.go index 07ac7f58f3..8b20abf863 100644 --- a/builtin/providers/aws/resource_aws_dms_replication_task_test.go +++ b/builtin/providers/aws/resource_aws_dms_replication_task_test.go @@ -102,7 +102,7 @@ func dmsReplicationTaskDestroy(s *terraform.State) error { func dmsReplicationTaskConfig(randId string) string { return fmt.Sprintf(` 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\"}]}" } diff --git a/builtin/providers/aws/resource_aws_efs_file_system_test.go b/builtin/providers/aws/resource_aws_efs_file_system_test.go index c404679c2b..b242fbf163 100644 --- a/builtin/providers/aws/resource_aws_efs_file_system_test.go +++ b/builtin/providers/aws/resource_aws_efs_file_system_test.go @@ -82,6 +82,7 @@ func TestResourceAWSEFSFileSystem_hasEmptyFileSystems(t *testing.T) { } func TestAccAWSEFSFileSystem_basic(t *testing.T) { + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -104,7 +105,7 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { ), }, { - Config: testAccAWSEFSFileSystemConfigWithTags, + Config: testAccAWSEFSFileSystemConfigWithTags(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckEfsFileSystem( "aws_efs_file_system.foo-with-tags", @@ -116,7 +117,7 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { testAccCheckEfsFileSystemTags( "aws_efs_file_system.foo-with-tags", map[string]string{ - "Name": "foo-efs", + "Name": fmt.Sprintf("foo-efs-%d", rInt), "Another": "tag", }, ), @@ -143,13 +144,14 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { } func TestAccAWSEFSFileSystem_pagedTags(t *testing.T) { + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckEfsFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSEFSFileSystemConfigPagedTags, + Config: testAccAWSEFSFileSystemConfigPagedTags(rInt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "aws_efs_file_system.foo", @@ -312,34 +314,38 @@ resource "aws_efs_file_system" "foo" { } ` -const testAccAWSEFSFileSystemConfigPagedTags = ` -resource "aws_efs_file_system" "foo" { - creation_token = "radeksimko" - tags { - Name = "foo-efs" - Another = "tag" - Test = "yes" - User = "root" - Page = "1" - Environment = "prod" - CostCenter = "terraform" - AcceptanceTest = "PagedTags" - CreationToken = "radek" - PerfMode = "max" - Region = "us-west-2" +func testAccAWSEFSFileSystemConfigPagedTags(rInt int) string { + return fmt.Sprintf(` + resource "aws_efs_file_system" "foo" { + creation_token = "radeksimko" + tags { + Name = "foo-efs-%d" + Another = "tag" + Test = "yes" + User = "root" + Page = "1" + Environment = "prod" + CostCenter = "terraform" + AcceptanceTest = "PagedTags" + CreationToken = "radek" + PerfMode = "max" + Region = "us-west-2" + } } + `, rInt) } -` -const testAccAWSEFSFileSystemConfigWithTags = ` -resource "aws_efs_file_system" "foo-with-tags" { - creation_token = "yada_yada" - tags { - Name = "foo-efs" - Another = "tag" +func testAccAWSEFSFileSystemConfigWithTags(rInt int) string { + return fmt.Sprintf(` + resource "aws_efs_file_system" "foo-with-tags" { + creation_token = "yada_yada" + tags { + Name = "foo-efs-%d" + Another = "tag" + } } + `, rInt) } -` const testAccAWSEFSFileSystemConfigWithPerformanceMode = ` resource "aws_efs_file_system" "foo-with-performance-mode" { diff --git a/builtin/providers/aws/resource_aws_elastic_beanstalk_environment_test.go b/builtin/providers/aws/resource_aws_elastic_beanstalk_environment_test.go index 305ab9c4a6..2a3b1e7629 100644 --- a/builtin/providers/aws/resource_aws_elastic_beanstalk_environment_test.go +++ b/builtin/providers/aws/resource_aws_elastic_beanstalk_environment_test.go @@ -661,7 +661,7 @@ resource "aws_elastic_beanstalk_environment" "tfenvtest" { func testAccBeanstalkWorkerEnvConfig(rInt int) string { return fmt.Sprintf(` resource "aws_iam_instance_profile" "tftest" { - name = "tftest_profile" + name = "tftest_profile-%d" roles = ["${aws_iam_role.tftest.name}"] } @@ -693,7 +693,7 @@ func testAccBeanstalkWorkerEnvConfig(rInt int) string { name = "IamInstanceProfile" value = "${aws_iam_instance_profile.tftest.name}" } - }`, rInt, rInt) + }`, rInt, rInt, rInt) } func testAccBeanstalkEnvCnamePrefixConfig(randString string, rInt int) string { @@ -937,24 +937,24 @@ resource "aws_s3_bucket_object" "default" { } resource "aws_elastic_beanstalk_application" "default" { - name = "tf-test-name" + name = "tf-test-name-%d" description = "tf-test-desc" } resource "aws_elastic_beanstalk_application_version" "default" { - application = "tf-test-name" + application = "tf-test-name-%d" name = "tf-test-version-label" bucket = "${aws_s3_bucket.default.id}" key = "${aws_s3_bucket_object.default.id}" } resource "aws_elastic_beanstalk_environment" "default" { - name = "tf-test-name" + name = "tf-test-name-%d" application = "${aws_elastic_beanstalk_application.default.name}" version_label = "${aws_elastic_beanstalk_application_version.default.name}" solution_stack_name = "64bit Amazon Linux running Python" } -`, randInt) +`, randInt, randInt, randInt, randInt) } func testAccBeanstalkEnvApplicationVersionConfigUpdate(randInt int) string { @@ -970,22 +970,22 @@ resource "aws_s3_bucket_object" "default" { } resource "aws_elastic_beanstalk_application" "default" { - name = "tf-test-name" + name = "tf-test-name-%d" description = "tf-test-desc" } resource "aws_elastic_beanstalk_application_version" "default" { - application = "tf-test-name" + application = "tf-test-name-%d" name = "tf-test-version-label-v2" bucket = "${aws_s3_bucket.default.id}" key = "${aws_s3_bucket_object.default.id}" } resource "aws_elastic_beanstalk_environment" "default" { - name = "tf-test-name" + name = "tf-test-name-%d" application = "${aws_elastic_beanstalk_application.default.name}" version_label = "${aws_elastic_beanstalk_application_version.default.name}" solution_stack_name = "64bit Amazon Linux running Python" } -`, randInt) +`, randInt, randInt, randInt, randInt) } diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile.go b/builtin/providers/aws/resource_aws_iam_instance_profile.go index 2e45a655f8..8e1d2d68ec 100644 --- a/builtin/providers/aws/resource_aws_iam_instance_profile.go +++ b/builtin/providers/aws/resource_aws_iam_instance_profile.go @@ -86,10 +86,20 @@ func resourceAwsIamInstanceProfile() *schema.Resource { }, "roles": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConflictsWith: []string{"role"}, + Elem: &schema.Schema{Type: schema.TypeString}, + 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() } + _, 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{ InstanceProfileName: aws.String(name), 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 instanceProfileSetRoles(d, iamconn) + return resourceAwsIamInstanceProfileUpdate(d, meta) } 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 { iamconn := meta.(*AWSClient).iamconn - if !d.HasChange("roles") { - return nil + d.Partial(true) + + 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") } - return instanceProfileSetRoles(d, iamconn) + if d.HasChange("roles") { + return instanceProfileSetRoles(d, iamconn) + } + + d.Partial(false) + + return nil } 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) + 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} for _, role := range result.Roles { roles.Add(*role.RoleName) diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go index 0bd3d5ffb1..0de1371021 100644 --- a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go +++ b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "strings" "testing" @@ -37,6 +38,8 @@ func TestAccAWSIAMInstanceProfile_importBasic(t *testing.T) { } func TestAccAWSIAMInstanceProfile_basic(t *testing.T) { + var conf iam.GetInstanceProfileOutput + rName := acctest.RandString(5) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,6 +47,41 @@ func TestAccAWSIAMInstanceProfile_basic(t *testing.T) { Steps: []resource.TestStep{ { 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) } +func testAccAwsIamInstanceProfileConfigMissingRole(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_instance_profile" "test" { + name = "test-%s" +}`, rName) +} + func testAccAWSInstanceProfilePrefixNameConfig(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "test" { @@ -169,3 +214,16 @@ resource "aws_iam_instance_profile" "test" { roles = ["${aws_iam_role.test.name}"] }`, 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) +} diff --git a/builtin/providers/aws/resource_aws_iam_server_certificate.go b/builtin/providers/aws/resource_aws_iam_server_certificate.go index 28258ef15e..8b29fbf766 100644 --- a/builtin/providers/aws/resource_aws_iam_server_certificate.go +++ b/builtin/providers/aws/resource_aws_iam_server_certificate.go @@ -20,37 +20,41 @@ func resourceAwsIAMServerCertificate() *schema.Resource { Create: resourceAwsIAMServerCertificateCreate, Read: resourceAwsIAMServerCertificateRead, Delete: resourceAwsIAMServerCertificateDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsIAMServerCertificateImport, + }, Schema: map[string]*schema.Schema{ - "certificate_body": &schema.Schema{ + "certificate_body": { Type: schema.TypeString, Required: true, ForceNew: true, StateFunc: normalizeCert, }, - "certificate_chain": &schema.Schema{ + "certificate_chain": { Type: schema.TypeString, Optional: true, ForceNew: true, StateFunc: normalizeCert, }, - "path": &schema.Schema{ + "path": { Type: schema.TypeString, Optional: true, Default: "/", ForceNew: true, }, - "private_key": &schema.Schema{ + "private_key": { Type: schema.TypeString, Required: true, ForceNew: true, StateFunc: normalizeCert, + Sensitive: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, Computed: true, @@ -66,7 +70,7 @@ func resourceAwsIAMServerCertificate() *schema.Resource { }, }, - "name_prefix": &schema.Schema{ + "name_prefix": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -80,7 +84,7 @@ func resourceAwsIAMServerCertificate() *schema.Resource { }, }, - "arn": &schema.Schema{ + "arn": { Type: schema.TypeString, Optional: 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) } + d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId) + // these values should always be present, and have a default if not set in // configuration, and so safe to reference with nil checks d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody)) @@ -196,6 +202,13 @@ func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interfac 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 { if cert == nil || cert == (*string)(nil) { return "" diff --git a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go index 11b1092961..1dad7b829e 100644 --- a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go +++ b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go @@ -2,10 +2,8 @@ package aws import ( "fmt" - "math/rand" "strings" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" @@ -16,14 +14,15 @@ import ( func TestAccAWSIAMServerCertificate_basic(t *testing.T) { var cert iam.ServerCertificate + rInt := acctest.RandInt() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckIAMServerCertificateDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccIAMServerCertConfig, + { + Config: testAccIAMServerCertConfig(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), testAccCheckAWSServerCertAttributes(&cert), @@ -41,7 +40,7 @@ func TestAccAWSIAMServerCertificate_name_prefix(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckIAMServerCertificateDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccIAMServerCertConfig_random, Check: resource.ComposeTestCheckFunc( testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), @@ -74,7 +73,7 @@ func TestAccAWSIAMServerCertificate_disappears(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckIAMServerCertificateDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccIAMServerCertConfig_random, Check: resource.ComposeTestCheckFunc( testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), @@ -97,7 +96,7 @@ func TestAccAWSIAMServerCertificate_file(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckIAMServerCertificateDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-unix-line-endings"), Check: resource.ComposeTestCheckFunc( 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"), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), @@ -202,7 +201,8 @@ CqDUFjhydXxYRsxXBBrEiLOE5BdtJR1sH/QHxIJe23C9iHI2nS1NbLziNEApLwC4 GnSud83VUo9G9w== -----END CERTIFICATE-----`) -var testAccIAMServerCertConfig = fmt.Sprintf(` +func testAccIAMServerCertConfig(rInt int) string { + return fmt.Sprintf(` resource "aws_iam_server_certificate" "test_cert" { name = "terraform-test-cert-%d" certificate_body = < float32(c.Period) { return fmt.Errorf("Timeout (%f) can not exceed period (%d)", c.Timeout, c.Period) } + // Check-type specific validation switch apiCheckType(c.Type) { case apiCheckTypeCloudWatchAttr: if !(c.Period == 60 || c.Period == 300) { 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 diff --git a/builtin/providers/circonus/consts.go b/builtin/providers/circonus/consts.go index 6b505482ac..9dd0d248f6 100644 --- a/builtin/providers/circonus/consts.go +++ b/builtin/providers/circonus/consts.go @@ -17,6 +17,19 @@ const ( providerAutoTagAttr = "auto_tag" 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" defaultCheckJSONPort = "443" defaultCheckJSONVersion = "1.1" diff --git a/builtin/providers/circonus/resource_circonus_check.go b/builtin/providers/circonus/resource_circonus_check.go index 0c2b6d5016..06bf2e9cf3 100644 --- a/builtin/providers/circonus/resource_circonus_check.go +++ b/builtin/providers/circonus/resource_circonus_check.go @@ -33,21 +33,22 @@ const ( checkCAQLAttr = "caql" checkCloudWatchAttr = "cloudwatch" checkCollectorAttr = "collector" + checkConsulAttr = "consul" checkHTTPAttr = "http" checkHTTPTrapAttr = "httptrap" checkICMPPingAttr = "icmp_ping" checkJSONAttr = "json" + checkMetricAttr = "metric" checkMetricLimitAttr = "metric_limit" checkMySQLAttr = "mysql" checkNameAttr = "name" checkNotesAttr = "notes" checkPeriodAttr = "period" checkPostgreSQLAttr = "postgresql" - checkMetricAttr = "metric" checkStatsdAttr = "statsd" + checkTCPAttr = "tcp" checkTagsAttr = "tags" checkTargetAttr = "target" - checkTCPAttr = "tcp" checkTimeoutAttr = "timeout" checkTypeAttr = "type" @@ -75,6 +76,7 @@ const ( // Circonus API constants from their API endpoints apiCheckTypeCAQLAttr apiCheckType = "caql" apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch" + apiCheckTypeConsulAttr apiCheckType = "consul" apiCheckTypeHTTPAttr apiCheckType = "http" apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap" apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp" @@ -90,6 +92,7 @@ var checkDescriptions = attrDescrs{ checkCAQLAttr: "CAQL check configuration", checkCloudWatchAttr: "CloudWatch check configuration", checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics", + checkConsulAttr: "Consul check configuration", checkHTTPAttr: "HTTP check configuration", checkHTTPTrapAttr: "HTTP Trap check configuration", checkICMPPingAttr: "ICMP ping check configuration", @@ -157,6 +160,7 @@ func resourceCheck() *schema.Resource { }), }, }, + checkConsulAttr: schemaCheckConsul, checkHTTPAttr: schemaCheckHTTP, checkHTTPTrapAttr: schemaCheckHTTPTrap, checkJSONAttr: schemaCheckJSON, @@ -577,6 +581,7 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error { checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{ checkCAQLAttr: checkConfigToAPICAQL, checkCloudWatchAttr: checkConfigToAPICloudWatch, + checkConsulAttr: checkConfigToAPIConsul, checkHTTPAttr: checkConfigToAPIHTTP, checkHTTPTrapAttr: checkConfigToAPIHTTPTrap, checkICMPPingAttr: checkConfigToAPIICMPPing, @@ -589,8 +594,17 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error { for checkType, fn := range checkTypeParseMap { if listRaw, found := d.GetOk(checkType); found { - if err := fn(c, listRaw.(*schema.Set).List()); err != nil { - return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err) + 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) + } + 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{ apiCheckTypeCAQLAttr: checkAPIToStateCAQL, apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch, + apiCheckTypeConsulAttr: checkAPIToStateConsul, apiCheckTypeHTTPAttr: checkAPIToStateHTTP, apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap, apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing, diff --git a/builtin/providers/circonus/resource_circonus_check_consul.go b/builtin/providers/circonus/resource_circonus_check_consul.go new file mode 100644 index 0000000000..dd3f496c5b --- /dev/null +++ b/builtin/providers/circonus/resource_circonus_check_consul.go @@ -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 +} diff --git a/builtin/providers/circonus/resource_circonus_check_consul_test.go b/builtin/providers/circonus/resource_circonus_check_consul_test.go new file mode 100644 index 0000000000..f7ca7993de --- /dev/null +++ b/builtin/providers/circonus/resource_circonus_check_consul_test.go @@ -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" +} +` diff --git a/builtin/providers/circonus/resource_circonus_check_http.go b/builtin/providers/circonus/resource_circonus_check_http.go index 8a9e7d67dd..7b6d68b33a 100644 --- a/builtin/providers/circonus/resource_circonus_check_http.go +++ b/builtin/providers/circonus/resource_circonus_check_http.go @@ -372,6 +372,10 @@ func checkConfigToAPIHTTP(c *circonusCheck, l interfaceList) error { if len(c.Target) == 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 { diff --git a/builtin/providers/circonus/resource_circonus_check_json.go b/builtin/providers/circonus/resource_circonus_check_json.go index f319983cb5..e377d08f3b 100644 --- a/builtin/providers/circonus/resource_circonus_check_json.go +++ b/builtin/providers/circonus/resource_circonus_check_json.go @@ -355,6 +355,10 @@ func checkConfigToAPIJSON(c *circonusCheck, l interfaceList) error { if len(c.Target) == 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 { diff --git a/builtin/providers/circonus/validators.go b/builtin/providers/circonus/validators.go index dca2de36c8..c98ec2799f 100644 --- a/builtin/providers/circonus/validators.go +++ b/builtin/providers/circonus/validators.go @@ -314,6 +314,7 @@ type urlParseFlags int const ( urlIsAbs urlParseFlags = 1 << iota urlOptional + urlWithoutPath urlWithoutPort 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))) } + 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 { hostParts := strings.SplitN(u.Host, ":", 2) if len(hostParts) != 1 { diff --git a/builtin/providers/consul/config.go b/builtin/providers/consul/config.go index 959293f84f..99897505d9 100644 --- a/builtin/providers/consul/config.go +++ b/builtin/providers/consul/config.go @@ -52,7 +52,7 @@ func (c *Config) Client() (*consulapi.Client, error) { } else { username = c.HttpAuth } - config.HttpAuth = &consulapi.HttpBasicAuth{username, password} + config.HttpAuth = &consulapi.HttpBasicAuth{Username: username, Password: password} } if c.Token != "" { diff --git a/builtin/providers/digitalocean/import_digitalocean_ssh_key_test.go b/builtin/providers/digitalocean/import_digitalocean_ssh_key_test.go index f579d7ef5b..7c93f00769 100644 --- a/builtin/providers/digitalocean/import_digitalocean_ssh_key_test.go +++ b/builtin/providers/digitalocean/import_digitalocean_ssh_key_test.go @@ -3,11 +3,17 @@ package digitalocean import ( "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" ) func TestAccDigitalOceanSSHKey_importBasic(t *testing.T) { 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{ PreCheck: func() { testAccPreCheck(t) }, @@ -15,7 +21,7 @@ func TestAccDigitalOceanSSHKey_importBasic(t *testing.T) { CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckDigitalOceanSSHKeyConfig_basic(testAccValidImportPublicKey), + Config: testAccCheckDigitalOceanSSHKeyConfig_basic(rInt, publicKeyMaterial), }, { diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet.go b/builtin/providers/digitalocean/resource_digitalocean_droplet.go index ff57a63d9e..76212d579b 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet.go @@ -322,8 +322,9 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("invalid droplet id: %v", err) } - if d.HasChange("size") { - oldSize, newSize := d.GetChange("size") + resize_disk := d.Get("resize_disk").(bool) + if d.HasChange("size") || d.HasChange("resize_disk") && resize_disk { + newSize := d.Get("size") _, _, err = client.DropletActions.PowerOff(id) 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_disk := d.Get("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) - } + action, _, err := client.DropletActions.Resize(id, newSize.(string), resize_disk) if err != nil { newErr := powerOnAndWait(d, meta) if newErr != nil { @@ -356,11 +351,8 @@ func resourceDigitalOceanDropletUpdate(d *schema.ResourceData, meta interface{}) "Error resizing droplet (%s): %s", d.Id(), err) } - // Wait for the size to change - _, err = WaitForDropletAttribute( - d, newSize.(string), []string{"", oldSize.(string)}, "size", meta) - - if err != nil { + // Wait for the resize action to complete. + if err := waitForAction(client, action); err != nil { newErr := powerOnAndWait(d, meta) if newErr != nil { return fmt.Errorf( diff --git a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go index d68d7f96c7..1c3d5601cd 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_droplet_test.go @@ -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) { var afterCreate, afterUpdate godo.Droplet 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 { return func(s *terraform.State) error { @@ -492,6 +557,19 @@ resource "digitalocean_droplet" "foobar" { `, 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 func testAccCheckDigitalOceanDropletConfig_PrivateNetworkingIpv6(rInt int) string { return fmt.Sprintf(` @@ -505,3 +583,5 @@ resource "digitalocean_droplet" "foobar" { } `, rInt) } + +var testAccValidPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR` diff --git a/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go b/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go index b348a89d11..043f667b7d 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_ssh_key_test.go @@ -3,16 +3,21 @@ package digitalocean import ( "fmt" "strconv" - "strings" "testing" "github.com/digitalocean/godo" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) func TestAccDigitalOceanSSHKey_Basic(t *testing.T) { 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{ PreCheck: func() { testAccPreCheck(t) }, @@ -20,14 +25,13 @@ func TestAccDigitalOceanSSHKey_Basic(t *testing.T) { CheckDestroy: testAccCheckDigitalOceanSSHKeyDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckDigitalOceanSSHKeyConfig_basic(testAccValidPublicKey), + Config: testAccCheckDigitalOceanSSHKeyConfig_basic(rInt, publicKeyMaterial), Check: resource.ComposeTestCheckFunc( testAccCheckDigitalOceanSSHKeyExists("digitalocean_ssh_key.foobar", &key), - testAccCheckDigitalOceanSSHKeyAttributes(&key), resource.TestCheckResourceAttr( - "digitalocean_ssh_key.foobar", "name", "foobar"), + "digitalocean_ssh_key.foobar", "name", fmt.Sprintf("foobar-%d", rInt)), 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 } -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 { return func(s *terraform.State) error { 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(` resource "digitalocean_ssh_key" "foobar" { - name = "foobar" + name = "foobar-%d" 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/` diff --git a/builtin/providers/fastly/resource_fastly_service_v1.go b/builtin/providers/fastly/resource_fastly_service_v1.go index da734c9f0d..852af30ce5 100644 --- a/builtin/providers/fastly/resource_fastly_service_v1.go +++ b/builtin/providers/fastly/resource_fastly_service_v1.go @@ -1922,7 +1922,7 @@ func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} { nb := map[string]interface{}{ "name": b.Name, "address": b.Address, - "auto_loadbalance": gofastly.CBool(b.AutoLoadbalance), + "auto_loadbalance": b.AutoLoadbalance, "between_bytes_timeout": int(b.BetweenBytesTimeout), "connect_timeout": int(b.ConnectTimeout), "error_threshold": int(b.ErrorThreshold), @@ -1930,7 +1930,7 @@ func flattenBackends(backendList []*gofastly.Backend) []map[string]interface{} { "max_conn": int(b.MaxConn), "port": int(b.Port), "shield": b.Shield, - "ssl_check_cert": gofastly.CBool(b.SSLCheckCert), + "ssl_check_cert": b.SSLCheckCert, "ssl_hostname": b.SSLHostname, "ssl_cert_hostname": b.SSLCertHostname, "ssl_sni_hostname": b.SSLSNIHostname, diff --git a/builtin/providers/fastly/resource_fastly_service_v1_test.go b/builtin/providers/fastly/resource_fastly_service_v1_test.go index c05006138d..ba6ca55923 100644 --- a/builtin/providers/fastly/resource_fastly_service_v1_test.go +++ b/builtin/providers/fastly/resource_fastly_service_v1_test.go @@ -84,14 +84,14 @@ func TestResourceFastlyFlattenBackend(t *testing.T) { "name": "test.notexample.com", "address": "www.notexample.com", "port": 80, - "auto_loadbalance": gofastly.CBool(true), + "auto_loadbalance": true, "between_bytes_timeout": 10000, "connect_timeout": 1000, "error_threshold": 0, "first_byte_timeout": 15000, "max_conn": 200, "request_condition": "", - "ssl_check_cert": gofastly.CBool(true), + "ssl_check_cert": true, "ssl_hostname": "", "ssl_cert_hostname": "", "ssl_sni_hostname": "", diff --git a/builtin/providers/kubernetes/patch_operations.go b/builtin/providers/kubernetes/patch_operations.go new file mode 100644 index 0000000000..e794a1324e --- /dev/null +++ b/builtin/providers/kubernetes/patch_operations.go @@ -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) +} diff --git a/builtin/providers/kubernetes/patch_operations_test.go b/builtin/providers/kubernetes/patch_operations_test.go new file mode 100644 index 0000000000..c60a5e628c --- /dev/null +++ b/builtin/providers/kubernetes/patch_operations_test.go @@ -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) + } + }) + } + +} diff --git a/builtin/providers/kubernetes/resource_kubernetes_config_map.go b/builtin/providers/kubernetes/resource_kubernetes_config_map.go index 460ca638e7..d24fa14360 100644 --- a/builtin/providers/kubernetes/resource_kubernetes_config_map.go +++ b/builtin/providers/kubernetes/resource_kubernetes_config_map.go @@ -1,9 +1,11 @@ package kubernetes import ( + "fmt" "log" "github.com/hashicorp/terraform/helper/schema" + pkgApi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" api "k8s.io/kubernetes/pkg/api/v1" 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 { conn := meta.(*kubernetes.Clientset) - metadata := expandMetadata(d.Get("metadata").([]interface{})) namespace, name := idParts(d.Id()) - // This is necessary in case the name is generated - metadata.Name = name - cfgMap := api.ConfigMap{ - ObjectMeta: metadata, - Data: expandStringMap(d.Get("data").(map[string]interface{})), + ops := patchMetadata("metadata.0.", "/metadata/", d) + if d.HasChange("data") { + 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) - out, err := conn.CoreV1().ConfigMaps(namespace).Update(&cfgMap) + data, err := ops.MarshalJSON() 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) d.SetId(buildId(out.ObjectMeta)) diff --git a/builtin/providers/kubernetes/structures.go b/builtin/providers/kubernetes/structures.go index 8b98cee327..58bc49030c 100644 --- a/builtin/providers/kubernetes/structures.go +++ b/builtin/providers/kubernetes/structures.go @@ -2,8 +2,10 @@ package kubernetes import ( "fmt" + "net/url" "strings" + "github.com/hashicorp/terraform/helper/schema" api "k8s.io/kubernetes/pkg/api/v1" ) @@ -39,6 +41,21 @@ func expandMetadata(in []interface{}) api.ObjectMeta { 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 { result := make(map[string]string) 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{} { m := make(map[string]interface{}) - m["annotations"] = meta.Annotations + m["annotations"] = filterAnnotations(meta.Annotations) m["generate_name"] = meta.GenerateName m["labels"] = meta.Labels m["name"] = meta.Name @@ -64,3 +81,21 @@ func flattenMetadata(meta api.ObjectMeta) []map[string]interface{} { 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 +} diff --git a/builtin/providers/kubernetes/structures_test.go b/builtin/providers/kubernetes/structures_test.go new file mode 100644 index 0000000000..2a0b9003e8 --- /dev/null +++ b/builtin/providers/kubernetes/structures_test.go @@ -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) + } + }) + } +} diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 1fa514c29d..5a60b55c54 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -79,9 +79,10 @@ func resourceComputeInstanceV2() *schema.Resource { DefaultFunc: schema.EnvDefaultFunc("OS_FLAVOR_NAME", nil), }, "floating_ip": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: false, + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", }, "user_data": &schema.Schema{ Type: schema.TypeString, @@ -150,9 +151,10 @@ func resourceComputeInstanceV2() *schema.Resource { Computed: true, }, "floating_ip": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + Deprecated: "Use the openstack_compute_floatingip_associate_v2 resource instead", }, "mac": &schema.Schema{ Type: schema.TypeString, @@ -243,8 +245,9 @@ func resourceComputeInstanceV2() *schema.Resource { }, }, "volume": &schema.Schema{ - Type: schema.TypeSet, - Optional: true, + Type: schema.TypeSet, + Optional: true, + Deprecated: "Use block_device or openstack_compute_volume_attach_v2 instead", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": &schema.Schema{ @@ -335,6 +338,10 @@ func resourceComputeInstanceV2() *schema.Resource { Optional: true, 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{} for _, sg := range server.SecurityGroups { diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index cfe807e4f2..cf43ee4bb5 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -29,6 +29,8 @@ func TestAccComputeV2Instance_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeV2InstanceExists("openstack_compute_instance_v2.instance_1", &instance), testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), + resource.TestCheckResourceAttr( + "openstack_compute_instance_v2.instance_1", "all_metadata.foo", "bar"), resource.TestCheckResourceAttr( "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), testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), 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{ @@ -616,6 +622,10 @@ func TestAccComputeV2Instance_metadataRemove(t *testing.T) { testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), testAccCheckComputeV2InstanceMetadata(&instance, "ghi", "jkl"), 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"), ), }, }, diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index eb0436ddf6..7cf796c6f7 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -240,7 +240,7 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { } 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) monitorsToAdd := newMIDsSet.Difference(oldMIDsSet) monitorsToRemove := oldMIDsSet.Difference(newMIDsSet) diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go index c21a74b0d2..72e905406c 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go @@ -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 { config := testAccProvider.Meta().(*Config) 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}" +} +` diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v2.go b/builtin/providers/openstack/resource_openstack_lb_pool_v2.go index 73742c6686..d1a602f53f 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v2.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v2.go @@ -167,10 +167,33 @@ func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error { } 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 { return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err) } + log.Printf("[INFO] pool ID: %s", pool.ID) log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID) diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2.go b/builtin/providers/openstack/resource_openstack_networking_port_v2.go index aea9cb8ddb..508ebc8131 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2.go @@ -85,10 +85,9 @@ func resourceNetworkingPortV2() *schema.Resource { Computed: true, }, "fixed_ip": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, ForceNew: false, - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "subnet_id": &schema.Schema{ @@ -98,7 +97,6 @@ func resourceNetworkingPortV2() *schema.Resource { "ip_address": &schema.Schema{ Type: schema.TypeString, Optional: true, - Computed: true, }, }, }, @@ -128,6 +126,11 @@ func resourceNetworkingPortV2() *schema.Resource { Optional: 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("device_id", p.DeviceID) - // Convert FixedIPs to list of map - var ips []map[string]interface{} + // Create a slice of all returned Fixed IPs. + // This will be in the order returned by the API, + // which is usually alpha-numeric. + var ips []string for _, ipObject := range p.FixedIPs { - ip := make(map[string]interface{}) - ip["subnet_id"] = ipObject.SubnetID - ip["ip_address"] = ipObject.IPAddress - ips = append(ips, ip) + ips = append(ips, ipObject.IPAddress) } - d.Set("fixed_ip", ips) + d.Set("all_fixed_ips", ips) // Convert AllowedAddressPairs to list of map var pairs []map[string]interface{} @@ -309,7 +311,7 @@ func resourcePortSecurityGroupsV2(d *schema.ResourceData) []string { } func resourcePortFixedIpsV2(d *schema.ResourceData) interface{} { - rawIP := d.Get("fixed_ip").(*schema.Set).List() + rawIP := d.Get("fixed_ip").([]interface{}) if len(rawIP) == 0 { return nil diff --git a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go index ec731fe7ae..28e08bebd1 100644 --- a/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_port_v2_test.go @@ -50,6 +50,30 @@ func TestAccNetworkingV2Port_noip(t *testing.T) { testAccCheckNetworkingV2SubnetExists("openstack_networking_subnet_v2.subnet_1", &subnet), testAccCheckNetworkingV2NetworkExists("openstack_networking_network_v2.network_1", &network), testAccCheckNetworkingV2PortExists("openstack_networking_port_v2.port_1", &port), + testAccCheckNetworkingV2PortCountFixedIPs(&port, 1), + ), + }, + }, + }) +} + +func TestAccNetworkingV2Port_multipleNoIP(t *testing.T) { + var network networks.Network + var port ports.Port + var subnet subnets.Subnet + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2PortDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Port_multipleNoIP, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2SubnetExists("openstack_networking_subnet_v2.subnet_1", &subnet), + testAccCheckNetworkingV2NetworkExists("openstack_networking_network_v2.network_1", &network), + testAccCheckNetworkingV2PortExists("openstack_networking_port_v2.port_1", &port), + testAccCheckNetworkingV2PortCountFixedIPs(&port, 3), ), }, }, @@ -96,6 +120,7 @@ func TestAccNetworkingV2Port_multipleFixedIPs(t *testing.T) { testAccCheckNetworkingV2SubnetExists("openstack_networking_subnet_v2.subnet_1", &subnet), testAccCheckNetworkingV2NetworkExists("openstack_networking_network_v2.network_1", &network), testAccCheckNetworkingV2PortExists("openstack_networking_port_v2.port_1", &port), + testAccCheckNetworkingV2PortCountFixedIPs(&port, 3), ), }, }, @@ -124,6 +149,25 @@ func TestAccNetworkingV2Port_timeout(t *testing.T) { }) } +func TestAccNetworkingV2Port_fixedIPs(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2PortDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Port_fixedIPs, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "openstack_networking_port_v2.port_1", "all_fixed_ips.0", "192.168.199.23"), + resource.TestCheckResourceAttr( + "openstack_networking_port_v2.port_1", "all_fixed_ips.1", "192.168.199.24"), + ), + }, + }, + }) +} + func testAccCheckNetworkingV2PortDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.networkingV2Client(OS_REGION_NAME) @@ -177,6 +221,16 @@ func testAccCheckNetworkingV2PortExists(n string, port *ports.Port) resource.Tes } } +func testAccCheckNetworkingV2PortCountFixedIPs(port *ports.Port, expected int) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(port.FixedIPs) != expected { + return fmt.Errorf("Expected %d Fixed IPs, got %d", expected, len(port.FixedIPs)) + } + + return nil + } +} + const testAccNetworkingV2Port_basic = ` resource "openstack_networking_network_v2" "network_1" { name = "network_1" @@ -226,6 +280,38 @@ resource "openstack_networking_port_v2" "port_1" { } ` +const testAccNetworkingV2Port_multipleNoIP = ` +resource "openstack_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +resource "openstack_networking_subnet_v2" "subnet_1" { + name = "subnet_1" + cidr = "192.168.199.0/24" + ip_version = 4 + network_id = "${openstack_networking_network_v2.network_1.id}" +} + +resource "openstack_networking_port_v2" "port_1" { + name = "port_1" + admin_state_up = "true" + network_id = "${openstack_networking_network_v2.network_1.id}" + + fixed_ip { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + } + + fixed_ip { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + } + + fixed_ip { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + } +} +` + const testAccNetworkingV2Port_allowedAddressPairs = ` resource "openstack_networking_network_v2" "vrrp_network" { name = "vrrp_network" @@ -356,3 +442,33 @@ resource "openstack_networking_port_v2" "port_1" { } } ` + +const testAccNetworkingV2Port_fixedIPs = ` +resource "openstack_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} + +resource "openstack_networking_subnet_v2" "subnet_1" { + name = "subnet_1" + cidr = "192.168.199.0/24" + ip_version = 4 + network_id = "${openstack_networking_network_v2.network_1.id}" +} + +resource "openstack_networking_port_v2" "port_1" { + name = "port_1" + admin_state_up = "true" + network_id = "${openstack_networking_network_v2.network_1.id}" + + fixed_ip { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + ip_address = "192.168.199.24" + } + + fixed_ip { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + ip_address = "192.168.199.23" + } +} +` diff --git a/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go index dae6dc3f7f..7dd62e60be 100644 --- a/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_secgroup_rule_v2_test.go @@ -208,7 +208,7 @@ resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_1" { security_group_id = "${openstack_networking_secgroup_v2.secgroup_1.id}" timeouts { - create = "5m" + delete = "5m" } } diff --git a/builtin/providers/openstack/types.go b/builtin/providers/openstack/types.go index 7bcc755602..e2d19304c0 100644 --- a/builtin/providers/openstack/types.go +++ b/builtin/providers/openstack/types.go @@ -97,7 +97,9 @@ func (lrt *LogRoundTripper) logResponseBody(original io.ReadCloser, headers http return nil, err } debugInfo := lrt.formatJSON(bs.Bytes()) - log.Printf("[DEBUG] OpenStack Response Body: %s", debugInfo) + if debugInfo != "" { + log.Printf("[DEBUG] OpenStack Response Body: %s", debugInfo) + } return ioutil.NopCloser(strings.NewReader(bs.String())), nil } @@ -127,6 +129,13 @@ func (lrt *LogRoundTripper) formatJSON(raw []byte) string { } } + // Ignore the catalog + if v, ok := data["token"].(map[string]interface{}); ok { + if _, ok := v["catalog"]; ok { + return "" + } + } + pretty, err := json.MarshalIndent(data, "", " ") if err != nil { log.Printf("[DEBUG] Unable to re-marshal OpenStack JSON: %s", err) diff --git a/builtin/providers/packet/resource_packet_ssh_key_test.go b/builtin/providers/packet/resource_packet_ssh_key_test.go index cfd85ae1ab..5f019d4280 100644 --- a/builtin/providers/packet/resource_packet_ssh_key_test.go +++ b/builtin/providers/packet/resource_packet_ssh_key_test.go @@ -2,7 +2,6 @@ package packet import ( "fmt" - "strings" "testing" "github.com/hashicorp/terraform/helper/acctest" @@ -14,6 +13,10 @@ import ( func TestAccPacketSSHKey_Basic(t *testing.T) { var key packngo.SSHKey rInt := acctest.RandInt() + publicKeyMaterial, _, err := acctest.RandSSHKeyPair("") + if err != nil { + t.Fatalf("Cannot generate test SSH key pair: %s", err) + } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -21,13 +24,13 @@ func TestAccPacketSSHKey_Basic(t *testing.T) { CheckDestroy: testAccCheckPacketSSHKeyDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckPacketSSHKeyConfig_basic(rInt), + Config: testAccCheckPacketSSHKeyConfig_basic(rInt, publicKeyMaterial), Check: resource.ComposeTestCheckFunc( testAccCheckPacketSSHKeyExists("packet_ssh_key.foobar", &key), resource.TestCheckResourceAttr( "packet_ssh_key.foobar", "name", fmt.Sprintf("foobar-%d", rInt)), resource.TestCheckResourceAttr( - "packet_ssh_key.foobar", "public_key", testAccValidPublicKey), + "packet_ssh_key.foobar", "public_key", publicKeyMaterial), ), }, }, @@ -76,14 +79,10 @@ func testAccCheckPacketSSHKeyExists(n string, key *packngo.SSHKey) resource.Test } } -func testAccCheckPacketSSHKeyConfig_basic(rInt int) string { +func testAccCheckPacketSSHKeyConfig_basic(rInt int, publicSshKey string) string { return fmt.Sprintf(` resource "packet_ssh_key" "foobar" { name = "foobar-%d" public_key = "%s" -}`, rInt, testAccValidPublicKey) +}`, rInt, publicSshKey) } - -var testAccValidPublicKey = strings.TrimSpace(` -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR -`) diff --git a/builtin/providers/profitbricks/config.go b/builtin/providers/profitbricks/config.go index d3b74f2fe1..259616d5d3 100644 --- a/builtin/providers/profitbricks/config.go +++ b/builtin/providers/profitbricks/config.go @@ -11,7 +11,7 @@ type Config struct { Retries int } -// Client() returns a new client for accessing digital ocean. +// Client() returns a new client for accessing ProfitBricks. func (c *Config) Client() (*Config, error) { profitbricks.SetAuth(c.Username, c.Password) profitbricks.SetDepth("5") diff --git a/builtin/providers/profitbricks/provider.go b/builtin/providers/profitbricks/provider.go index dc0c782a0f..5db06b91ef 100644 --- a/builtin/providers/profitbricks/provider.go +++ b/builtin/providers/profitbricks/provider.go @@ -7,7 +7,7 @@ import ( "github.com/profitbricks/profitbricks-sdk-go" ) -// Provider returns a schema.Provider for DigitalOcean. +// Provider returns a schema.Provider for ProfitBricks. func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ @@ -15,19 +15,19 @@ func Provider() terraform.ResourceProvider { Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("PROFITBRICKS_USERNAME", nil), - Description: "Profitbricks username for API operations.", + Description: "ProfitBricks username for API operations.", }, "password": { Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("PROFITBRICKS_PASSWORD", nil), - Description: "Profitbricks password for API operations.", + Description: "ProfitBricks password for API operations.", }, "endpoint": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("PROFITBRICKS_API_URL", profitbricks.Endpoint), - Description: "Profitbricks REST API URL.", + Description: "ProfitBricks REST API URL.", }, "retries": { Type: schema.TypeInt, diff --git a/builtin/providers/profitbricks/resource_profitbricks_datacenter.go b/builtin/providers/profitbricks/resource_profitbricks_datacenter.go index d402b57dc4..fee3d03cf3 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_datacenter.go +++ b/builtin/providers/profitbricks/resource_profitbricks_datacenter.go @@ -69,6 +69,10 @@ func resourceProfitBricksDatacenterCreate(d *schema.ResourceData, meta interface func resourceProfitBricksDatacenterRead(d *schema.ResourceData, meta interface{}) error { datacenter := profitbricks.GetDatacenter(d.Id()) if datacenter.StatusCode > 299 { + if datacenter.StatusCode == 404 { + d.SetId("") + return nil + } return fmt.Errorf("Error while fetching a data center ID %s %s", d.Id(), datacenter.Response) } diff --git a/builtin/providers/profitbricks/resource_profitbricks_datacenter_test.go b/builtin/providers/profitbricks/resource_profitbricks_datacenter_test.go index 7ed87ed4b9..9d351b1ab5 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_datacenter_test.go +++ b/builtin/providers/profitbricks/resource_profitbricks_datacenter_test.go @@ -11,7 +11,7 @@ import ( func TestAccProfitBricksDataCenter_Basic(t *testing.T) { var datacenter profitbricks.Datacenter - lanName := "datacenter-test" + dc_name := "datacenter-test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -21,11 +21,11 @@ func TestAccProfitBricksDataCenter_Basic(t *testing.T) { CheckDestroy: testAccCheckDProfitBricksDatacenterDestroyCheck, Steps: []resource.TestStep{ resource.TestStep{ - Config: fmt.Sprintf(testAccCheckProfitBricksDatacenterConfig_basic, lanName), + Config: fmt.Sprintf(testAccCheckProfitBricksDatacenterConfig_basic, dc_name), Check: resource.ComposeTestCheckFunc( testAccCheckProfitBricksDatacenterExists("profitbricks_datacenter.foobar", &datacenter), - testAccCheckProfitBricksDatacenterAttributes("profitbricks_datacenter.foobar", lanName), - resource.TestCheckResourceAttr("profitbricks_datacenter.foobar", "name", lanName), + testAccCheckProfitBricksDatacenterAttributes("profitbricks_datacenter.foobar", dc_name), + resource.TestCheckResourceAttr("profitbricks_datacenter.foobar", "name", dc_name), ), }, resource.TestStep{ diff --git a/builtin/providers/profitbricks/resource_profitbricks_firewall.go b/builtin/providers/profitbricks/resource_profitbricks_firewall.go index 4559a0428a..12fb68c0c4 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_firewall.go +++ b/builtin/providers/profitbricks/resource_profitbricks_firewall.go @@ -131,6 +131,10 @@ func resourceProfitBricksFirewallRead(d *schema.ResourceData, meta interface{}) fw := profitbricks.GetFirewallRule(d.Get("datacenter_id").(string), d.Get("server_id").(string), d.Get("nic_id").(string), d.Id()) if fw.StatusCode > 299 { + if fw.StatusCode == 404 { + d.SetId("") + return nil + } return fmt.Errorf("An error occured while fetching a firewall rule dcId: %s server_id: %s nic_id: %s ID: %s %s", d.Get("datacenter_id").(string), d.Get("server_id").(string), d.Get("nic_id").(string), d.Id(), fw.Response) } diff --git a/builtin/providers/profitbricks/resource_profitbricks_ipblock.go b/builtin/providers/profitbricks/resource_profitbricks_ipblock.go index 7ba2fdab75..e11f60d10f 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_ipblock.go +++ b/builtin/providers/profitbricks/resource_profitbricks_ipblock.go @@ -59,6 +59,10 @@ func resourceProfitBricksIPBlockRead(d *schema.ResourceData, meta interface{}) e ipblock := profitbricks.GetIpBlock(d.Id()) if ipblock.StatusCode > 299 { + if ipblock.StatusCode == 404 { + d.SetId("") + return nil + } return fmt.Errorf("An error occured while fetching an ip block ID %s %s", d.Id(), ipblock.Response) } diff --git a/builtin/providers/profitbricks/resource_profitbricks_lan.go b/builtin/providers/profitbricks/resource_profitbricks_lan.go index 3a3725bd0d..725c25c089 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_lan.go +++ b/builtin/providers/profitbricks/resource_profitbricks_lan.go @@ -65,6 +65,10 @@ func resourceProfitBricksLanRead(d *schema.ResourceData, meta interface{}) error lan := profitbricks.GetLan(d.Get("datacenter_id").(string), d.Id()) if lan.StatusCode > 299 { + if lan.StatusCode == 404 { + d.SetId("") + return nil + } return fmt.Errorf("An error occured while fetching a lan ID %s %s", d.Id(), lan.Response) } diff --git a/builtin/providers/profitbricks/resource_profitbricks_loadbalancer.go b/builtin/providers/profitbricks/resource_profitbricks_loadbalancer.go index a905831c4c..a7ffd98f35 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_loadbalancer.go +++ b/builtin/providers/profitbricks/resource_profitbricks_loadbalancer.go @@ -75,6 +75,14 @@ func resourceProfitBricksLoadbalancerCreate(d *schema.ResourceData, meta interfa func resourceProfitBricksLoadbalancerRead(d *schema.ResourceData, meta interface{}) error { lb := profitbricks.GetLoadbalancer(d.Get("datacenter_id").(string), d.Id()) + if lb.StatusCode > 299 { + if lb.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("An error occured while fetching a lan ID %s %s", d.Id(), lb.Response) + } + d.Set("name", lb.Properties.Name) d.Set("ip", lb.Properties.Ip) d.Set("dhcp", lb.Properties.Dhcp) diff --git a/builtin/providers/profitbricks/resource_profitbricks_nic.go b/builtin/providers/profitbricks/resource_profitbricks_nic.go index a2f914cb01..084f02a9a9 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_nic.go +++ b/builtin/providers/profitbricks/resource_profitbricks_nic.go @@ -109,6 +109,10 @@ func resourceProfitBricksNicCreate(d *schema.ResourceData, meta interface{}) err func resourceProfitBricksNicRead(d *schema.ResourceData, meta interface{}) error { nic := profitbricks.GetNic(d.Get("datacenter_id").(string), d.Get("server_id").(string), d.Id()) if nic.StatusCode > 299 { + if nic.StatusCode == 404 { + d.SetId("") + return nil + } return fmt.Errorf("Error occured while fetching a nic ID %s %s", d.Id(), nic.Response) } log.Printf("[INFO] LAN ON NIC: %d", nic.Properties.Lan) diff --git a/builtin/providers/profitbricks/resource_profitbricks_server.go b/builtin/providers/profitbricks/resource_profitbricks_server.go index ff29aef035..bfcd1678a0 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_server.go +++ b/builtin/providers/profitbricks/resource_profitbricks_server.go @@ -449,7 +449,13 @@ func resourceProfitBricksServerRead(d *schema.ResourceData, meta interface{}) er serverId := d.Id() server := profitbricks.GetServer(dcId, serverId) - + if server.StatusCode > 299 { + if server.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error occured while fetching a server ID %s %s", d.Id(), server.Response) + } d.Set("name", server.Properties.Name) d.Set("cores", server.Properties.Cores) d.Set("ram", server.Properties.Ram) diff --git a/builtin/providers/profitbricks/resource_profitbricks_volume.go b/builtin/providers/profitbricks/resource_profitbricks_volume.go index 6efed8e845..8fca17854a 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_volume.go +++ b/builtin/providers/profitbricks/resource_profitbricks_volume.go @@ -163,6 +163,15 @@ func resourceProfitBricksVolumeRead(d *schema.ResourceData, meta interface{}) er dcId := d.Get("datacenter_id").(string) volume := profitbricks.GetVolume(dcId, d.Id()) + + if volume.StatusCode > 299 { + if volume.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error occured while fetching a volume ID %s %s", d.Id(), volume.Response) + } + if volume.StatusCode > 299 { return fmt.Errorf("An error occured while fetching a volume ID %s %s", d.Id(), volume.Response) diff --git a/builtin/providers/rancher/provider.go b/builtin/providers/rancher/provider.go index 9c176943f5..8df36778df 100644 --- a/builtin/providers/rancher/provider.go +++ b/builtin/providers/rancher/provider.go @@ -2,6 +2,7 @@ package rancher import ( "encoding/json" + "fmt" "io/ioutil" "net/url" "os" @@ -87,7 +88,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return config, err } - if apiURL == "" { + if apiURL == "" && config.URL != "" { u, err := url.Parse(config.URL) if err != nil { return config, err @@ -104,6 +105,10 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { } } + if apiURL == "" { + return &Config{}, fmt.Errorf("No api_url provided") + } + config := &Config{ APIURL: apiURL + "/v1", AccessKey: accessKey, diff --git a/builtin/providers/random/provider.go b/builtin/providers/random/provider.go index c0741dc3c4..15665f465e 100644 --- a/builtin/providers/random/provider.go +++ b/builtin/providers/random/provider.go @@ -13,6 +13,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "random_id": resourceId(), "random_shuffle": resourceShuffle(), + "random_pet": resourcePet(), }, } } diff --git a/builtin/providers/random/resource_pet.go b/builtin/providers/random/resource_pet.go new file mode 100644 index 0000000000..4c6f3e335e --- /dev/null +++ b/builtin/providers/random/resource_pet.go @@ -0,0 +1,66 @@ +package random + +import ( + "fmt" + "strings" + + "github.com/dustinkirkland/golang-petname" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourcePet() *schema.Resource { + return &schema.Resource{ + Create: CreatePet, + Read: ReadPet, + Delete: schema.RemoveFromState, + + Schema: map[string]*schema.Schema{ + "keepers": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + + "length": { + Type: schema.TypeInt, + Optional: true, + Default: 2, + ForceNew: true, + }, + + "prefix": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "separator": { + Type: schema.TypeString, + Optional: true, + Default: "-", + ForceNew: true, + }, + }, + } +} + +func CreatePet(d *schema.ResourceData, meta interface{}) error { + length := d.Get("length").(int) + separator := d.Get("separator").(string) + prefix := d.Get("prefix").(string) + + pet := strings.ToLower(petname.Generate(length, separator)) + + if prefix != "" { + pet = fmt.Sprintf("%s%s%s", prefix, separator, pet) + } + + d.SetId(pet) + + return nil +} + +func ReadPet(d *schema.ResourceData, meta interface{}) error { + return nil +} diff --git a/builtin/providers/random/resource_pet_test.go b/builtin/providers/random/resource_pet_test.go new file mode 100644 index 0000000000..64bebb6f6f --- /dev/null +++ b/builtin/providers/random/resource_pet_test.go @@ -0,0 +1,115 @@ +package random + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccResourcePet_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccResourcePet_basic, + Check: resource.ComposeTestCheckFunc( + testAccResourcePetLength("random_pet.pet_1", "-", 2), + ), + }, + }, + }) +} + +func TestAccResourcePet_length(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccResourcePet_length, + Check: resource.ComposeTestCheckFunc( + testAccResourcePetLength("random_pet.pet_1", "-", 4), + ), + }, + }, + }) +} + +func TestAccResourcePet_prefix(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccResourcePet_prefix, + Check: resource.ComposeTestCheckFunc( + testAccResourcePetLength("random_pet.pet_1", "-", 3), + resource.TestMatchResourceAttr( + "random_pet.pet_1", "id", regexp.MustCompile("^consul-")), + ), + }, + }, + }) +} + +func TestAccResourcePet_separator(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccResourcePet_separator, + Check: resource.ComposeTestCheckFunc( + testAccResourcePetLength("random_pet.pet_1", "_", 3), + ), + }, + }, + }) +} + +func testAccResourcePetLength(id string, separator string, length int) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[id] + if !ok { + return fmt.Errorf("Not found: %s", id) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + petParts := strings.Split(rs.Primary.ID, separator) + if len(petParts) != length { + return fmt.Errorf("Length does not match") + } + + return nil + } +} + +const testAccResourcePet_basic = ` +resource "random_pet" "pet_1" { +} +` + +const testAccResourcePet_length = ` +resource "random_pet" "pet_1" { + length = 4 +} +` +const testAccResourcePet_prefix = ` +resource "random_pet" "pet_1" { + prefix = "consul" +} +` + +const testAccResourcePet_separator = ` +resource "random_pet" "pet_1" { + length = 3 + separator = "_" +} +` diff --git a/builtin/providers/scaleway/provider.go b/builtin/providers/scaleway/provider.go index c05d6e9779..16069fe213 100644 --- a/builtin/providers/scaleway/provider.go +++ b/builtin/providers/scaleway/provider.go @@ -1,11 +1,15 @@ package scaleway import ( + "sync" + "github.com/hashicorp/terraform/helper/mutexkv" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) +var mu = sync.Mutex{} + // Provider returns a terraform.ResourceProvider. func Provider() terraform.ResourceProvider { return &schema.Provider{ diff --git a/builtin/providers/scaleway/resource_scaleway_ip.go b/builtin/providers/scaleway/resource_scaleway_ip.go index 27cb6fb47b..f388fe4e27 100644 --- a/builtin/providers/scaleway/resource_scaleway_ip.go +++ b/builtin/providers/scaleway/resource_scaleway_ip.go @@ -2,7 +2,6 @@ package scaleway import ( "log" - "sync" "github.com/hashicorp/terraform/helper/schema" "github.com/scaleway/scaleway-cli/pkg/api" @@ -31,13 +30,12 @@ func resourceScalewayIP() *schema.Resource { } } -var mu = sync.Mutex{} - func resourceScalewayIPCreate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() - defer mu.Unlock() resp, err := scaleway.NewIP() + mu.Unlock() if err != nil { return err } @@ -71,6 +69,10 @@ func resourceScalewayIPRead(d *schema.ResourceData, m interface{}) error { func resourceScalewayIPUpdate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + + mu.Lock() + defer mu.Unlock() + if d.HasChange("server") { if d.Get("server").(string) != "" { log.Printf("[DEBUG] Attaching IP %q to server %q\n", d.Id(), d.Get("server").(string)) @@ -88,6 +90,10 @@ func resourceScalewayIPUpdate(d *schema.ResourceData, m interface{}) error { func resourceScalewayIPDelete(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + + mu.Lock() + defer mu.Unlock() + err := scaleway.DeleteIP(d.Id()) if err != nil { return err diff --git a/builtin/providers/scaleway/resource_scaleway_security_group.go b/builtin/providers/scaleway/resource_scaleway_security_group.go index e30c086200..e2c44b6764 100644 --- a/builtin/providers/scaleway/resource_scaleway_security_group.go +++ b/builtin/providers/scaleway/resource_scaleway_security_group.go @@ -34,6 +34,9 @@ func resourceScalewaySecurityGroup() *schema.Resource { func resourceScalewaySecurityGroupCreate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + req := api.ScalewayNewSecurityGroup{ Name: d.Get("name").(string), Description: d.Get("description").(string), @@ -94,6 +97,9 @@ func resourceScalewaySecurityGroupRead(d *schema.ResourceData, m interface{}) er func resourceScalewaySecurityGroupUpdate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + var req = api.ScalewayUpdateSecurityGroup{ Organization: scaleway.Organization, Name: d.Get("name").(string), @@ -112,6 +118,9 @@ func resourceScalewaySecurityGroupUpdate(d *schema.ResourceData, m interface{}) func resourceScalewaySecurityGroupDelete(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + err := scaleway.DeleteSecurityGroup(d.Id()) if err != nil { if serr, ok := err.(api.ScalewayAPIError); ok { diff --git a/builtin/providers/scaleway/resource_scaleway_security_group_rule.go b/builtin/providers/scaleway/resource_scaleway_security_group_rule.go index 03d29a95fd..4359052b59 100644 --- a/builtin/providers/scaleway/resource_scaleway_security_group_rule.go +++ b/builtin/providers/scaleway/resource_scaleway_security_group_rule.go @@ -67,6 +67,9 @@ func resourceScalewaySecurityGroupRule() *schema.Resource { func resourceScalewaySecurityGroupRuleCreate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + req := api.ScalewayNewSecurityGroupRule{ Action: d.Get("action").(string), Direction: d.Get("direction").(string), @@ -140,6 +143,9 @@ func resourceScalewaySecurityGroupRuleRead(d *schema.ResourceData, m interface{} func resourceScalewaySecurityGroupRuleUpdate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + var req = api.ScalewayNewSecurityGroupRule{ Action: d.Get("action").(string), Direction: d.Get("direction").(string), @@ -160,6 +166,9 @@ func resourceScalewaySecurityGroupRuleUpdate(d *schema.ResourceData, m interface func resourceScalewaySecurityGroupRuleDelete(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + err := scaleway.DeleteSecurityGroupRule(d.Get("security_group").(string), d.Id()) if err != nil { if serr, ok := err.(api.ScalewayAPIError); ok { diff --git a/builtin/providers/scaleway/resource_scaleway_server.go b/builtin/providers/scaleway/resource_scaleway_server.go index 57183c152d..1d27c76eaf 100644 --- a/builtin/providers/scaleway/resource_scaleway_server.go +++ b/builtin/providers/scaleway/resource_scaleway_server.go @@ -108,6 +108,9 @@ func resourceScalewayServer() *schema.Resource { func resourceScalewayServerCreate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + image := d.Get("image").(string) var server = api.ScalewayServerDefinition{ Name: d.Get("name").(string), @@ -217,8 +220,10 @@ func resourceScalewayServerRead(d *schema.ResourceData, m interface{}) error { func resourceScalewayServerUpdate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway - var req api.ScalewayServerPatchDefinition + mu.Lock() + defer mu.Unlock() + var req api.ScalewayServerPatchDefinition if d.HasChange("name") { name := d.Get("name").(string) req.Name = &name @@ -258,6 +263,9 @@ func resourceScalewayServerUpdate(d *schema.ResourceData, m interface{}) error { func resourceScalewayServerDelete(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + s, err := scaleway.GetServer(d.Id()) if err != nil { return err diff --git a/builtin/providers/scaleway/resource_scaleway_volume.go b/builtin/providers/scaleway/resource_scaleway_volume.go index 6f72ff59ae..e36cf15c99 100644 --- a/builtin/providers/scaleway/resource_scaleway_volume.go +++ b/builtin/providers/scaleway/resource_scaleway_volume.go @@ -45,6 +45,10 @@ func resourceScalewayVolume() *schema.Resource { func resourceScalewayVolumeCreate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + + mu.Lock() + defer mu.Unlock() + size := uint64(d.Get("size_in_gb").(int)) * gb req := api.ScalewayVolumeDefinition{ Name: d.Get("name").(string), @@ -88,6 +92,9 @@ func resourceScalewayVolumeRead(d *schema.ResourceData, m interface{}) error { func resourceScalewayVolumeUpdate(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + mu.Lock() + defer mu.Unlock() + var req api.ScalewayVolumePutDefinition if d.HasChange("name") { req.Name = String(d.Get("name").(string)) @@ -104,6 +111,10 @@ func resourceScalewayVolumeUpdate(d *schema.ResourceData, m interface{}) error { func resourceScalewayVolumeDelete(d *schema.ResourceData, m interface{}) error { scaleway := m.(*Client).scaleway + + mu.Lock() + defer mu.Unlock() + err := scaleway.DeleteVolume(d.Id()) if err != nil { if serr, ok := err.(api.ScalewayAPIError); ok { diff --git a/builtin/providers/scaleway/resource_scaleway_volume_attachment.go b/builtin/providers/scaleway/resource_scaleway_volume_attachment.go index 518b4acfe5..532586c661 100644 --- a/builtin/providers/scaleway/resource_scaleway_volume_attachment.go +++ b/builtin/providers/scaleway/resource_scaleway_volume_attachment.go @@ -95,7 +95,9 @@ func resourceScalewayVolumeAttachmentCreate(d *schema.ResourceData, m interface{ var req = api.ScalewayServerPatchDefinition{ Volumes: &volumes, } + mu.Lock() err := scaleway.PatchServer(serverID, req) + mu.Unlock() if err == nil { return nil @@ -172,6 +174,9 @@ func resourceScalewayVolumeAttachmentDelete(d *schema.ResourceData, m interface{ scaleway := m.(*Client).scaleway scaleway.ClearCache() + mu.Lock() + defer mu.Unlock() + var startServerAgain = false // guard against server shutdown/ startup race conditiond @@ -221,7 +226,9 @@ func resourceScalewayVolumeAttachmentDelete(d *schema.ResourceData, m interface{ var req = api.ScalewayServerPatchDefinition{ Volumes: &volumes, } + mu.Lock() err := scaleway.PatchServer(serverID, req) + mu.Unlock() if err == nil { return nil diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_disk.go b/builtin/providers/vsphere/resource_vsphere_virtual_disk.go index 7b0eedccb2..a17d43d5c7 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_disk.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_disk.go @@ -4,12 +4,14 @@ import ( "fmt" "log" + "errors" "github.com/hashicorp/terraform/helper/schema" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/types" "golang.org/x/net/context" + "path" ) type virtualDisk struct { @@ -180,20 +182,66 @@ func resourceVSphereVirtualDiskRead(d *schema.ResourceData, meta interface{}) er return err } - fileInfo, err := ds.Stat(context.TODO(), vDisk.vmdkPath) + ctx := context.TODO() + b, err := ds.Browser(ctx) if err != nil { - log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - stat failed on: %v", vDisk.vmdkPath) - d.SetId("") + return err + } - _, ok := err.(object.DatastoreNoSuchFileError) - if !ok { - return err + // `Datastore.Stat` does not allow to query `VmDiskFileQuery`. Instead, we + // search the datastore manually. + spec := types.HostDatastoreBrowserSearchSpec{ + Query: []types.BaseFileQuery{&types.VmDiskFileQuery{Details: &types.VmDiskFileQueryFlags{ + CapacityKb: true, + DiskType: true, + }}}, + Details: &types.FileQueryFlags{ + FileSize: true, + FileType: true, + Modification: true, + FileOwner: types.NewBool(true), + }, + MatchPattern: []string{path.Base(vDisk.vmdkPath)}, + } + + dsPath := ds.Path(path.Dir(vDisk.vmdkPath)) + task, err := b.SearchDatastore(context.TODO(), dsPath, &spec) + + if err != nil { + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - could not search datastore for: %v", vDisk.vmdkPath) + return err + } + + info, err := task.WaitForResult(context.TODO(), nil) + if err != nil { + if info == nil || info.Error != nil { + _, ok := info.Error.Fault.(*types.FileNotFound) + if ok { + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - could not find: %v", vDisk.vmdkPath) + d.SetId("") + return nil + } } + + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - could not search datastore for: %v", vDisk.vmdkPath) + return err + } + + res := info.Result.(types.HostDatastoreBrowserSearchResults) + log.Printf("[DEBUG] num results: %d", len(res.File)) + if len(res.File) == 0 { + d.SetId("") + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - could not find: %v", vDisk.vmdkPath) return nil } - fileInfo = fileInfo.GetFileInfo() + + if len(res.File) != 1 { + return errors.New("Datastore search did not return exactly one result") + } + + fileInfo := res.File[0] log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - fileinfo: %#v", fileInfo) - size := fileInfo.(*types.FileInfo).FileSize / 1024 / 1024 / 1024 + size := fileInfo.(*types.VmDiskFileInfo).CapacityKb / 1024 / 1024 d.SetId(vDisk.vmdkPath) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go index c27426ad7e..d3fef92d00 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/vmware/govmomi" @@ -19,6 +20,8 @@ func TestAccVSphereVirtualDisk_basic(t *testing.T) { var initTypeOpt string var adapterTypeOpt string + rString := acctest.RandString(5) + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { datacenterOpt = v } @@ -27,6 +30,8 @@ func TestAccVSphereVirtualDisk_basic(t *testing.T) { } if v := os.Getenv("VSPHERE_INIT_TYPE"); v != "" { initTypeOpt += fmt.Sprintf(" type = \"%s\"\n", v) + } else { + initTypeOpt += fmt.Sprintf(" type = \"%s\"\n", "thin") } if v := os.Getenv("VSPHERE_ADAPTER_TYPE"); v != "" { adapterTypeOpt += fmt.Sprintf(" adapter_type = \"%s\"\n", v) @@ -37,14 +42,8 @@ func TestAccVSphereVirtualDisk_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckVSphereVirtualDiskDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: fmt.Sprintf( - testAccCheckVSphereVirtuaDiskConfig_basic, - initTypeOpt, - adapterTypeOpt, - datacenterOpt, - datastoreOpt, - ), + { + Config: testAccCheckVSphereVirtuaDiskConfig_basic(rString, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt), Check: resource.ComposeTestCheckFunc( testAccVSphereVirtualDiskExists("vsphere_virtual_disk.foo"), ), @@ -117,13 +116,15 @@ func testAccCheckVSphereVirtualDiskDestroy(s *terraform.State) error { return nil } -const testAccCheckVSphereVirtuaDiskConfig_basic = ` +func testAccCheckVSphereVirtuaDiskConfig_basic(rName, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt string) string { + return fmt.Sprintf(` resource "vsphere_virtual_disk" "foo" { size = 1 - vmdk_path = "tfTestDisk.vmdk" + vmdk_path = "tfTestDisk-%s.vmdk" %s %s datacenter = "%s" datastore = "%s" } -` +`, rName, initTypeOpt, adapterTypeOpt, datacenterOpt, datastoreOpt) +} diff --git a/command/apply_test.go b/command/apply_test.go index 01c230326e..661d88c76c 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -802,6 +802,39 @@ func TestApply_planVars(t *testing.T) { } } +// we should be able to apply a plan file with no other file dependencies +func TestApply_planNoModuleFiles(t *testing.T) { + // temprary data directory which we can remove between commands + td, err := ioutil.TempDir("", "tf") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) + + defer testChdir(t, td)() + + p := testProvider() + planFile := testPlanFile(t, &terraform.Plan{ + Module: testModule(t, "apply-plan-no-module"), + }) + + contextOpts := testCtxConfig(p) + + apply := &ApplyCommand{ + Meta: Meta{ + ContextOpts: contextOpts, + Ui: new(cli.MockUi), + }, + } + args := []string{ + planFile, + } + apply.Run(args) + if p.ValidateCalled { + t.Fatal("Validate should not be called with a plan") + } +} + func TestApply_refresh(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ diff --git a/command/env_command.go b/command/env_command.go index 481c0092c9..9548122a10 100644 --- a/command/env_command.go +++ b/command/env_command.go @@ -1,6 +1,9 @@ package command -import "strings" +import ( + "net/url" + "strings" +) // EnvCommand is a Command Implementation that manipulates local state // environments. @@ -39,6 +42,13 @@ func (c *EnvCommand) Synopsis() string { return "Environment management" } +// validEnvName returns true is this name is valid to use as an environment name. +// Since most named states are accessed via a filesystem path or URL, check if +// escaping the name would be required. +func validEnvName(name string) bool { + return name == url.PathEscape(name) +} + const ( envNotSupported = `Backend does not support environments` @@ -81,5 +91,10 @@ Environment %[1]q is your active environment! You cannot delete the currently active environment. Please switch to another environment and try again. +` + + envInvalidName = ` +The environment name %q is not allowed. The name must contain only URL safe +characters, and no path separators. ` ) diff --git a/command/env_command_test.go b/command/env_command_test.go index 0b28beb014..5a04d8db0f 100644 --- a/command/env_command_test.go +++ b/command/env_command_test.go @@ -103,6 +103,44 @@ func TestEnv_createAndList(t *testing.T) { } } +// Don't allow names that aren't URL safe +func TestEnv_createInvalid(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + os.MkdirAll(td, 0755) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + newCmd := &EnvNewCommand{} + + envs := []string{"test_a*", "test_b/foo", "../../../test_c", "好_d"} + + // create multiple envs + for _, env := range envs { + ui := new(cli.MockUi) + newCmd.Meta = Meta{Ui: ui} + if code := newCmd.Run([]string{env}); code == 0 { + t.Fatalf("expected failure: \n%s", ui.OutputWriter) + } + } + + // list envs to make sure none were created + listCmd := &EnvListCommand{} + ui := new(cli.MockUi) + listCmd.Meta = Meta{Ui: ui} + + if code := listCmd.Run(nil); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) + } + + actual := strings.TrimSpace(ui.OutputWriter.String()) + expected := "* default" + + if actual != expected { + t.Fatalf("\nexpected: %q\nactual: %q", expected, actual) + } +} + func TestEnv_createWithState(t *testing.T) { td := tempDir(t) os.MkdirAll(td, 0755) diff --git a/command/env_delete.go b/command/env_delete.go index a9958f8aae..be21b76edf 100644 --- a/command/env_delete.go +++ b/command/env_delete.go @@ -32,6 +32,11 @@ func (c *EnvDeleteCommand) Run(args []string) int { delEnv := args[0] + if !validEnvName(delEnv) { + c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv)) + return 1 + } + configPath, err := ModulePath(args[1:]) if err != nil { c.Ui.Error(err.Error()) diff --git a/command/env_new.go b/command/env_new.go index 5f4999e24c..8b0e8fcdb7 100644 --- a/command/env_new.go +++ b/command/env_new.go @@ -35,6 +35,11 @@ func (c *EnvNewCommand) Run(args []string) int { newEnv := args[0] + if !validEnvName(newEnv) { + c.Ui.Error(fmt.Sprintf(envInvalidName, newEnv)) + return 1 + } + configPath, err := ModulePath(args[1:]) if err != nil { c.Ui.Error(err.Error()) diff --git a/command/env_select.go b/command/env_select.go index 073f92ac53..e7bc8743e4 100644 --- a/command/env_select.go +++ b/command/env_select.go @@ -39,6 +39,10 @@ func (c *EnvSelectCommand) Run(args []string) int { } name := args[0] + if !validEnvName(name) { + c.Ui.Error(fmt.Sprintf(envInvalidName, name)) + return 1 + } states, err := b.States() if err != nil { diff --git a/command/meta_backend.go b/command/meta_backend.go index 5019c0242c..b30659dc0e 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -104,9 +104,13 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, error) { StateBackupPath: m.backupPath, ContextOpts: m.contextOpts(), Input: m.Input(), - Validation: true, } + // Don't validate if we have a plan. Validation is normally harmless here, + // but validation requires interpolation, and `file()` function calls may + // not have the original files in the current execution context. + cliOpts.Validation = opts.Plan == nil + // If the backend supports CLI initialization, do it. if cli, ok := b.(backend.CLI); ok { if err := cli.CLIInit(cliOpts); err != nil { diff --git a/command/test-fixtures/apply-plan-no-module/main.tf b/command/test-fixtures/apply-plan-no-module/main.tf new file mode 100644 index 0000000000..deea30d669 --- /dev/null +++ b/command/test-fixtures/apply-plan-no-module/main.tf @@ -0,0 +1,7 @@ +resource "test_instance" "tmpl" { + foo = "${file("${path.module}/template.txt")}" +} + +output "template" { + value = "${test_instance.tmpl.foo}" +} diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index e9c1ea4e24..cc09e384c2 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "math" "net" + "path/filepath" "regexp" "sort" "strconv" @@ -52,6 +53,7 @@ func listVariableValueToStringSlice(values []ast.Variable) ([]string, error) { // Funcs is the mapping of built-in functions for configuration. func Funcs() map[string]ast.Function { return map[string]ast.Function{ + "basename": interpolationFuncBasename(), "base64decode": interpolationFuncBase64Decode(), "base64encode": interpolationFuncBase64Encode(), "base64sha256": interpolationFuncBase64Sha256(), @@ -62,6 +64,7 @@ func Funcs() map[string]ast.Function { "coalesce": interpolationFuncCoalesce(), "compact": interpolationFuncCompact(), "concat": interpolationFuncConcat(), + "dirname": interpolationFuncDirname(), "distinct": interpolationFuncDistinct(), "element": interpolationFuncElement(), "file": interpolationFuncFile(), @@ -600,6 +603,17 @@ func interpolationFuncIndex() ast.Function { } } +// interpolationFuncBasename implements the "dirname" function. +func interpolationFuncDirname() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + return filepath.Dir(args[0].(string)), nil + }, + } +} + // interpolationFuncDistinct implements the "distinct" function that // removes duplicate elements from a list. func interpolationFuncDistinct() ast.Function { @@ -1006,6 +1020,17 @@ func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { } } +// interpolationFuncBasename implements the "basename" function. +func interpolationFuncBasename() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + return filepath.Base(args[0].(string)), nil + }, + } +} + // interpolationFuncBase64Encode implements the "base64encode" function that // allows Base64 encoding. func interpolationFuncBase64Encode() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index c5ef36da50..04e85a6d56 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -852,6 +852,18 @@ func TestInterpolateFuncMerge(t *testing.T) { } +func TestInterpolateFuncDirname(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${dirname("/foo/bar/baz")}`, + "/foo/bar", + false, + }, + }, + }) +} + func TestInterpolateFuncDistinct(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ @@ -1777,6 +1789,18 @@ func TestInterpolateFuncElement(t *testing.T) { }) } +func TestInterpolateFuncBasename(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${basename("/foo/bar/baz")}`, + "baz", + false, + }, + }, + }) +} + func TestInterpolateFuncBase64Encode(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/examples/alicloud-ecs-image/main.tf b/examples/alicloud-ecs-image/main.tf index 743e6b9e1d..04efe08ed2 100644 --- a/examples/alicloud-ecs-image/main.tf +++ b/examples/alicloud-ecs-image/main.tf @@ -9,6 +9,39 @@ resource "alicloud_security_group" "group" { description = "New security group" } +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.group.id}" + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "https-in" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "internet" + policy = "accept" + port_range = "443/443" + priority = 1 + security_group_id = "${alicloud_security_group.group.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.group.id}" + cidr_ip = "0.0.0.0/0" +} + resource "alicloud_disk" "disk" { availability_zone = "${var.availability_zones}" diff --git a/examples/alicloud-ecs-nat/README.md b/examples/alicloud-ecs-nat/README.md new file mode 100644 index 0000000000..123faebd01 --- /dev/null +++ b/examples/alicloud-ecs-nat/README.md @@ -0,0 +1,33 @@ +### Configure NAT instance Example + +In the Virtual Private Cloud(VPC) environment, to enable multiple back-end intranet hosts to provide services externally with a limited number of EIPs, map the ports on the EIP-bound host to the back-end intranet hosts. + +### Get up and running + +* Planning phase + + terraform plan + +* Apply phase + + terraform apply + + Get the outputs: + + nat_instance_eip_address = 123.56.19.238 + + nat_instance_private_ip = 10.1.1.57 + + worker_instance_private_ip = 10.1.1.56 + +* Apply phase + + + login the vm: ssh root@123.56.19.238|Test123456 + + Run the "iptables -t nat -nvL" command to check the result + + | prot | in | source | destination | | + | ---- | -- | ----------- | -------------- | ------------------------ | + | tcp | * | 0.0.0.0/0 | 10.1.1.57 | tcp dpt:80 to:10.1.1.56 + | all | * | 10.1.1.0/24 | 0.0.0.0/0 | to:10.1.1.57 + + +* Destroy + + terraform destroy \ No newline at end of file diff --git a/examples/alicloud-ecs-nat/main.tf b/examples/alicloud-ecs-nat/main.tf new file mode 100644 index 0000000000..300f097460 --- /dev/null +++ b/examples/alicloud-ecs-nat/main.tf @@ -0,0 +1,98 @@ +resource "alicloud_vpc" "main" { + cidr_block = "${var.vpc_cidr}" +} + +resource "alicloud_vswitch" "main" { + vpc_id = "${alicloud_vpc.main.id}" + cidr_block = "${var.vswitch_cidr}" + availability_zone = "${var.zone}" + depends_on = ["alicloud_vpc.main"] +} + +resource "alicloud_route_entry" "entry" { + router_id = "${alicloud_vpc.main.router_id}" + route_table_id = "${alicloud_vpc.main.router_table_id}" + destination_cidrblock = "0.0.0.0/0" + nexthop_type = "Instance" + nexthop_id = "${alicloud_instance.nat.id}" +} + +resource "alicloud_instance" "nat" { + image_id = "${var.image}" + instance_type = "${var.instance_nat_type}" + availability_zone = "${var.zone}" + security_groups = ["${alicloud_security_group.group.id}"] + vswitch_id = "${alicloud_vswitch.main.id}" + instance_name = "nat" + io_optimized = "optimized" + system_disk_category = "cloud_efficiency" + password= "${var.instance_pwd}" + + depends_on = ["alicloud_instance.worker"] + user_data = "${data.template_file.shell.rendered}" + + tags { + Name = "ecs-nat" + } +} + +data "template_file" "shell" { + template = "${file("userdata.sh")}" + + vars { + worker_private_ip = "${alicloud_instance.worker.private_ip}" + vswitch_cidr = "${var.vswitch_cidr}" + } +} + +resource "alicloud_instance" "worker" { + image_id = "${var.image}" + instance_type = "${var.instance_worker_type}" + availability_zone = "${var.zone}" + security_groups = ["${alicloud_security_group.group.id}"] + vswitch_id = "${alicloud_vswitch.main.id}" + instance_name = "worker" + io_optimized = "optimized" + system_disk_category = "cloud_efficiency" + password= "${var.instance_pwd}" + + tags { + Name = "ecs-worker" + } +} + +resource "alicloud_eip" "eip" { +} + +resource "alicloud_eip_association" "attach" { + allocation_id = "${alicloud_eip.eip.id}" + instance_id = "${alicloud_instance.nat.id}" +} + +resource "alicloud_security_group" "group" { + name = "terraform-test-group" + description = "New security group" + vpc_id = "${alicloud_vpc.main.id}" +} + +resource "alicloud_security_group_rule" "allow_in" { + security_group_id = "${alicloud_security_group.group.id}" + type = "ingress" + cidr_ip= "0.0.0.0/0" + policy = "accept" + ip_protocol= "all" + nic_type= "intranet" + port_range= "-1/-1" + priority= 1 +} + +resource "alicloud_security_group_rule" "allow_out" { + security_group_id = "${alicloud_security_group.group.id}" + type = "egress" + cidr_ip= "0.0.0.0/0" + policy = "accept" + ip_protocol= "all" + nic_type= "intranet" + port_range= "-1/-1" + priority= 1 +} \ No newline at end of file diff --git a/examples/alicloud-ecs-nat/outputs.tf b/examples/alicloud-ecs-nat/outputs.tf new file mode 100644 index 0000000000..46632334de --- /dev/null +++ b/examples/alicloud-ecs-nat/outputs.tf @@ -0,0 +1,19 @@ +output "nat_instance_id" { + value = "${alicloud_instance.nat.id}" +} + +output "nat_instance_private_ip" { + value = "${alicloud_instance.nat.private_ip}" +} + +output "nat_instance_eip_address" { + value = "${alicloud_eip.eip.ip_address}" +} + +output "worker_instance_id" { + value = "${alicloud_instance.worker.id}" +} + +output "worker_instance_private_ip" { + value = "${alicloud_instance.worker.private_ip}" +} \ No newline at end of file diff --git a/examples/alicloud-ecs-nat/userdata.sh b/examples/alicloud-ecs-nat/userdata.sh new file mode 100644 index 0000000000..6cf1f45360 --- /dev/null +++ b/examples/alicloud-ecs-nat/userdata.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +PostRouting=${vswitch_cidr} +SourceRouting=`ifconfig eth0|grep inet|awk '{print $2}'|tr -d 'addr:'` +echo ${worker_private_ip}>> /etc/sysctl.conf +echo 'net.ipv4.ip_forward=1'>> /etc/sysctl.conf +sysctl -p +iptables -t nat -I POSTROUTING -s $PostRouting -j SNAT --to-source $SourceRouting +iptables -t nat -I PREROUTING -d $SourceRouting -p tcp --dport 80 -j DNAT --to ${worker_private_ip} \ No newline at end of file diff --git a/examples/alicloud-ecs-nat/variables.tf b/examples/alicloud-ecs-nat/variables.tf new file mode 100644 index 0000000000..2ccec3d1af --- /dev/null +++ b/examples/alicloud-ecs-nat/variables.tf @@ -0,0 +1,27 @@ +variable "vpc_cidr" { + default = "10.1.0.0/21" +} + +variable "vswitch_cidr" { + default = "10.1.1.0/24" +} + +variable "zone" { + default = "cn-beijing-c" +} + +variable "image" { + default = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" +} + +variable "instance_nat_type" { + default = "ecs.n1.small" +} + +variable "instance_worker_type" { + default = "ecs.s2.large" +} + +variable "instance_pwd" { + default = "Test123456" +} \ No newline at end of file diff --git a/examples/alicloud-ecs-slb/README.md b/examples/alicloud-ecs-slb/README.md index db4b4631e3..91c5855512 100644 --- a/examples/alicloud-ecs-slb/README.md +++ b/examples/alicloud-ecs-slb/README.md @@ -15,4 +15,4 @@ The example launches ECS, disk, and attached the disk on ECS. It also creates an * Destroy - terraform destroy \ No newline at end of file + terraform destroy diff --git a/examples/alicloud-ecs-slb/main.tf b/examples/alicloud-ecs-slb/main.tf index fad5c77682..8e6b9a659f 100644 --- a/examples/alicloud-ecs-slb/main.tf +++ b/examples/alicloud-ecs-slb/main.tf @@ -3,33 +3,59 @@ resource "alicloud_security_group" "group" { description = "New security group" } +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.group.id}" + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "https-in" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "internet" + policy = "accept" + port_range = "443/443" + priority = 1 + security_group_id = "${alicloud_security_group.group.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.group.id}" + cidr_ip = "0.0.0.0/0" +} + resource "alicloud_instance" "instance" { instance_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" host_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" image_id = "${var.image_id}" instance_type = "${var.ecs_type}" count = "${var.count}" - availability_zone = "${var.availability_zones}" security_groups = ["${alicloud_security_group.group.*.id}"] - internet_charge_type = "${var.internet_charge_type}" internet_max_bandwidth_out = "${var.internet_max_bandwidth_out}" - io_optimized = "${var.io_optimized}" - password = "${var.ecs_password}" - allocate_public_ip = "${var.allocate_public_ip}" - + availability_zone = "" instance_charge_type = "PostPaid" system_disk_category = "cloud_efficiency" - tags { role = "${var.role}" dc = "${var.datacenter}" } - } resource "alicloud_slb" "instance" { diff --git a/examples/alicloud-ecs-userdata/main.tf b/examples/alicloud-ecs-userdata/main.tf index 99376325f3..b0eacf57dc 100644 --- a/examples/alicloud-ecs-userdata/main.tf +++ b/examples/alicloud-ecs-userdata/main.tf @@ -11,27 +11,38 @@ resource "alicloud_vswitch" "vsw" { } resource "alicloud_security_group" "sg" { - name = "tf-sg" - description = "sg" - vpc_id = "${alicloud_vpc.default.id}" + name = "tf-sg" + description = "sg" + vpc_id = "${alicloud_vpc.default.id}" +} + +resource "alicloud_security_group_rule" "allow_ssh" { + security_group_id = "${alicloud_security_group.sg.id}" + type = "ingress" + cidr_ip= "0.0.0.0/0" + policy = "accept" + ip_protocol= "tcp" + port_range= "22/22" + priority= 1 } resource "alicloud_instance" "website" { - # cn-beijing - availability_zone = "${var.zone}" - vswitch_id = "${alicloud_vswitch.vsw.id}" - image_id = "${var.image}" + # cn-beijing + availability_zone = "${var.zone}" + vswitch_id = "${alicloud_vswitch.vsw.id}" + image_id = "${var.image}" - # series II - instance_type = "${var.ecs_type}" - io_optimized = "optimized" - system_disk_category = "cloud_efficiency" + # series II + instance_type = "${var.ecs_type}" + io_optimized = "optimized" + system_disk_category = "cloud_efficiency" - internet_charge_type = "PayByTraffic" - internet_max_bandwidth_out = 5 - allocate_public_ip = true - security_groups = ["${alicloud_security_group.sg.id}"] - instance_name = "test_foo" + internet_charge_type = "PayByTraffic" + internet_max_bandwidth_out = 5 + allocate_public_ip = true + security_groups = ["${alicloud_security_group.sg.id}"] + instance_name = "tf_website" + password= "${var.password}" - user_data = "${file("userdata.sh")}" + user_data = "${file("userdata.sh")}" } diff --git a/examples/alicloud-ecs-userdata/outputs.tf b/examples/alicloud-ecs-userdata/outputs.tf index 7115e9247f..2034a70161 100644 --- a/examples/alicloud-ecs-userdata/outputs.tf +++ b/examples/alicloud-ecs-userdata/outputs.tf @@ -1,7 +1,8 @@ -output "hostname" { - value = "${alicloud_instance.website.instance_name}" -} output "ecs_id" { value = "${alicloud_instance.website.id}" +} + +output "ecs_public_ip" { + value = "${alicloud_instance.website.public_ip}" } \ No newline at end of file diff --git a/examples/alicloud-ecs-userdata/variables.tf b/examples/alicloud-ecs-userdata/variables.tf index d8809ad838..5c54758395 100644 --- a/examples/alicloud-ecs-userdata/variables.tf +++ b/examples/alicloud-ecs-userdata/variables.tf @@ -10,6 +10,10 @@ variable "zone" { default = "cn-beijing-b" } +variable "password" { + default = "Test123456" +} + variable "image" { default = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" } diff --git a/examples/alicloud-ecs-vpc-cluster/main.tf b/examples/alicloud-ecs-vpc-cluster/main.tf index 0ec8bf8a00..5d0ef7b819 100644 --- a/examples/alicloud-ecs-vpc-cluster/main.tf +++ b/examples/alicloud-ecs-vpc-cluster/main.tf @@ -1,6 +1,3 @@ -provider "alicloud" { - region = "${var.region}" -} module "vpc" { availability_zones = "${var.availability_zones}" @@ -21,14 +18,12 @@ module "control-nodes" { role = "control" datacenter = "${var.datacenter}" ecs_type = "${var.control_ecs_type}" - ecs_password = "${var.ecs_password}" disk_size = "${var.control_disk_size}" ssh_username = "${var.ssh_username}" short_name = "${var.short_name}" availability_zones = "${module.vpc.availability_zones}" security_groups = ["${module.security-groups.control_security_group}"] vswitch_id = "${module.vpc.vswitch_ids}" - internet_charge_type = "${var.internet_charge_type}" } module "edge-nodes" { @@ -37,13 +32,11 @@ module "edge-nodes" { role = "edge" datacenter = "${var.datacenter}" ecs_type = "${var.edge_ecs_type}" - ecs_password = "${var.ecs_password}" ssh_username = "${var.ssh_username}" short_name = "${var.short_name}" availability_zones = "${module.vpc.availability_zones}" security_groups = ["${module.security-groups.worker_security_group}"] vswitch_id = "${module.vpc.vswitch_ids}" - internet_charge_type = "${var.internet_charge_type}" } module "worker-nodes" { @@ -52,11 +45,9 @@ module "worker-nodes" { role = "worker" datacenter = "${var.datacenter}" ecs_type = "${var.worker_ecs_type}" - ecs_password = "${var.ecs_password}" ssh_username = "${var.ssh_username}" short_name = "${var.short_name}" availability_zones = "${module.vpc.availability_zones}" security_groups = ["${module.security-groups.worker_security_group}"] vswitch_id = "${module.vpc.vswitch_ids}" - internet_charge_type = "${var.internet_charge_type}" } \ No newline at end of file diff --git a/examples/alicloud-ecs-vpc-cluster/variables.tf b/examples/alicloud-ecs-vpc-cluster/variables.tf index 7af6118624..4df8f7b4df 100644 --- a/examples/alicloud-ecs-vpc-cluster/variables.tf +++ b/examples/alicloud-ecs-vpc-cluster/variables.tf @@ -50,10 +50,6 @@ variable "availability_zones" { default = "cn-beijing-c" } -variable "internet_charge_type" { - default = "" -} - variable "datacenter" { default = "beijing" } \ No newline at end of file diff --git a/examples/alicloud-ecs-vpc/variables.tf b/examples/alicloud-ecs-vpc/variables.tf index 67664e4255..e3064c15f1 100644 --- a/examples/alicloud-ecs-vpc/variables.tf +++ b/examples/alicloud-ecs-vpc/variables.tf @@ -18,6 +18,7 @@ variable "short_name" { variable "ecs_type" { } variable "ecs_password" { + default = "Test12345" } variable "availability_zones" { } diff --git a/examples/alicloud-ecs-zone-type/main.tf b/examples/alicloud-ecs-zone-type/main.tf index c3c21bc9ae..1817781bce 100644 --- a/examples/alicloud-ecs-zone-type/main.tf +++ b/examples/alicloud-ecs-zone-type/main.tf @@ -5,7 +5,7 @@ data "alicloud_instance_types" "1c2g" { } data "alicloud_zones" "default" { - "available_instance_type"= "${data.alicloud_instance_types.4c8g.instance_types.0.id}" + "available_instance_type"= "${data.alicloud_instance_types.1c2g.instance_types.0.id}" "available_disk_category"= "${var.disk_category}" } @@ -14,6 +14,39 @@ resource "alicloud_security_group" "group" { description = "New security group" } +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.group.id}" + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "https-in" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "internet" + policy = "accept" + port_range = "443/443" + priority = 1 + security_group_id = "${alicloud_security_group.group.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.group.id}" + cidr_ip = "0.0.0.0/0" +} + resource "alicloud_instance" "instance" { instance_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" host_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" diff --git a/examples/alicloud-ecs/main.tf b/examples/alicloud-ecs/main.tf index a6d39a059e..0ed0779400 100644 --- a/examples/alicloud-ecs/main.tf +++ b/examples/alicloud-ecs/main.tf @@ -1,11 +1,39 @@ +data "alicloud_instance_types" "instance_type" { + instance_type_family = "ecs.n1" + cpu_core_count = "1" + memory_size = "2" +} + resource "alicloud_security_group" "group" { name = "${var.short_name}" description = "New security group" } +resource "alicloud_security_group_rule" "allow_http_80" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "${var.nic_type}" + policy = "accept" + port_range = "80/80" + priority = 1 + security_group_id = "${alicloud_security_group.group.id}" + cidr_ip = "0.0.0.0/0" +} + + +resource "alicloud_security_group_rule" "allow_https_443" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "${var.nic_type}" + policy = "accept" + port_range = "443/443" + priority = 1 + security_group_id = "${alicloud_security_group.group.id}" + cidr_ip = "0.0.0.0/0" +} resource "alicloud_disk" "disk" { - availability_zone = "${var.availability_zones}" + availability_zone = "${alicloud_instance.instance.0.availability_zone}" category = "${var.disk_category}" size = "${var.disk_size}" count = "${var.count}" @@ -15,7 +43,7 @@ resource "alicloud_instance" "instance" { instance_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" host_name = "${var.short_name}-${var.role}-${format(var.count_format, count.index+1)}" image_id = "${var.image_id}" - instance_type = "${var.ecs_type}" + instance_type = "${data.alicloud_instance_types.instance_type.instance_types.0.id}" count = "${var.count}" availability_zone = "${var.availability_zones}" security_groups = ["${alicloud_security_group.group.*.id}"] @@ -45,5 +73,4 @@ resource "alicloud_disk_attachment" "instance-attachment" { disk_id = "${element(alicloud_disk.disk.*.id, count.index)}" instance_id = "${element(alicloud_instance.instance.*.id, count.index)}" device_name = "${var.device_name}" -} - +} \ No newline at end of file diff --git a/examples/alicloud-ecs/variables.tf b/examples/alicloud-ecs/variables.tf index c663f8dd3e..dcf479d30d 100644 --- a/examples/alicloud-ecs/variables.tf +++ b/examples/alicloud-ecs/variables.tf @@ -8,6 +8,10 @@ variable "image_id" { default = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" } +variable "availability_zones" { + default = "" +} + variable "role" { default = "work" } @@ -23,9 +27,6 @@ variable "ecs_type" { variable "ecs_password" { default = "Test12345" } -variable "availability_zones" { - default = "cn-beijing-b" -} variable "allocate_public_ip" { default = true } @@ -41,11 +42,15 @@ variable "io_optimized" { } variable "disk_category" { - default = "cloud_ssd" + default = "cloud_efficiency" } variable "disk_size" { default = "40" } variable "device_name" { default = "/dev/xvdb" +} + +variable "nic_type" { + default = "internet" } \ No newline at end of file diff --git a/examples/alicloud-rds/README.md b/examples/alicloud-rds/README.md new file mode 100644 index 0000000000..c7b2d09d4d --- /dev/null +++ b/examples/alicloud-rds/README.md @@ -0,0 +1,17 @@ +### RDS Example + +The example launches RDS instance, database, account and grant the database readwrite privilege to the account. + +### Get up and running + +* Planning phase + + terraform plan + +* Apply phase + + terraform apply + +* Destroy + + terraform destroy \ No newline at end of file diff --git a/examples/alicloud-rds/main.tf b/examples/alicloud-rds/main.tf new file mode 100644 index 0000000000..582bd9739c --- /dev/null +++ b/examples/alicloud-rds/main.tf @@ -0,0 +1,17 @@ + +resource "alicloud_db_instance" "dc" { + engine = "${var.engine}" + engine_version = "${var.engine_version}" + db_instance_class = "${var.instance_class}" + db_instance_storage = "${var.storage}" + db_instance_net_type = "${var.net_type}" + + master_user_name = "${var.user_name}" + master_user_password = "${var.password}" + + db_mappings = [{ + db_name = "${var.database_name}" + character_set_name = "${var.database_character}" + db_description = "tf" + }] +} \ No newline at end of file diff --git a/examples/alicloud-rds/outputs.tf b/examples/alicloud-rds/outputs.tf new file mode 100644 index 0000000000..26c452f924 --- /dev/null +++ b/examples/alicloud-rds/outputs.tf @@ -0,0 +1,11 @@ +output "port" { + value = "${alicloud_db_instance.dc.port}" +} + +output "connections" { + value = "${alicloud_db_instance.dc.connections}" +} + +output "security_ips" { + value = "${alicloud_db_instance.dc.security_ips}" +} \ No newline at end of file diff --git a/examples/alicloud-rds/variables.tf b/examples/alicloud-rds/variables.tf new file mode 100644 index 0000000000..81491c4567 --- /dev/null +++ b/examples/alicloud-rds/variables.tf @@ -0,0 +1,29 @@ +variable "engine" { + default = "MySQL" +} +variable "engine_version" { + default = "5.6" +} +variable "instance_class" { + default = "rds.mysql.t1.small" +} +variable "storage" { + default = "10" +} +variable "net_type" { + default = "Intranet" +} + +variable "user_name" { + default = "tf_tester" +} +variable "password" { + default = "Test12345" +} + +variable "database_name" { + default = "bookstore" +} +variable "database_character" { + default = "utf8" +} \ No newline at end of file diff --git a/examples/alicloud-security-group-rule/main.tf b/examples/alicloud-security-group-rule/main.tf index 706ee08630..0f4d1bd0e9 100644 --- a/examples/alicloud-security-group-rule/main.tf +++ b/examples/alicloud-security-group-rule/main.tf @@ -2,12 +2,23 @@ resource "alicloud_security_group" "default" { name = "${var.security_group_name}" } -resource "alicloud_security_group_rule" "allow_all_tcp" { +resource "alicloud_security_group_rule" "http-in" { type = "ingress" ip_protocol = "tcp" - nic_type = "${var.nic_type}" + nic_type = "internet" policy = "accept" - port_range = "1/65535" + port_range = "80/80" + priority = 1 + security_group_id = "${alicloud_security_group.default.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.default.id}" cidr_ip = "0.0.0.0/0" diff --git a/examples/alicloud-slb/README.md b/examples/alicloud-slb/README.md index 370476f13d..50289d1d63 100644 Binary files a/examples/alicloud-slb/README.md and b/examples/alicloud-slb/README.md differ diff --git a/examples/alicloud-slb/main.tf b/examples/alicloud-slb/main.tf index 67f5e0cf60..a4c179f69e 100644 --- a/examples/alicloud-slb/main.tf +++ b/examples/alicloud-slb/main.tf @@ -5,21 +5,50 @@ resource "alicloud_slb" "instance" { listener = [ { - "instance_port" = "2111" - "lb_port" = "21" - "lb_protocol" = "tcp" - "bandwidth" = "5" + "instance_port" = "22" + "lb_port" = "22" + "lb_protocol" = "tcp" + "bandwidth" = "10" + "health_check_type" = "http" + "persistence_timeout" = 3600 + "healthy_threshold" = 8 + "unhealthy_threshold" = 8 + "health_check_timeout" = 8 + "health_check_interval" = 5 + "health_check_http_code" = "http_2xx,http_3xx" + "health_check_timeout" = 8 }, + { - "instance_port" = "8000" - "lb_port" = "80" - "lb_protocol" = "http" - "bandwidth" = "5" - }, - { - "instance_port" = "1611" - "lb_port" = "161" + "instance_port" = "2001" + "lb_port" = "2001" "lb_protocol" = "udp" - "bandwidth" = "5" - }] + "bandwidth" = "10" + "persistence_timeout" = 3600 + "healthy_threshold" = 8 + "unhealthy_threshold" = 8 + "health_check_timeout" = 8 + "health_check_interval" = 4 + "health_check_timeout" = 8 + }, + + { + "instance_port" = "80" + "lb_port" = "80" + "lb_protocol" = "http" + "sticky_session" = "on" + "sticky_session_type" = "server" + "cookie" = "testslblistenercookie" + "cookie_timeout" = 86400 + "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" = 5 + "health_check_http_code" = "http_2xx,http_3xx" + "bandwidth" = 10 + }] } diff --git a/examples/alicloud-vpc-route-entry/main.tf b/examples/alicloud-vpc-route-entry/main.tf index 9f6876b29b..00540f88b5 100644 --- a/examples/alicloud-vpc-route-entry/main.tf +++ b/examples/alicloud-vpc-route-entry/main.tf @@ -23,9 +23,9 @@ resource "alicloud_security_group" "sg" { vpc_id = "${alicloud_vpc.default.id}" } -resource "alicloud_security_group_rule" "ssh" { +resource "alicloud_security_group_rule" "ssh-in" { type = "ingress" - ip_protocol = "tcp" + ip_protocol = "tcp" nic_type = "intranet" policy = "${var.rule_policy}" port_range = "22/22" @@ -34,6 +34,28 @@ resource "alicloud_security_group_rule" "ssh" { cidr_ip = "0.0.0.0/0" } +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.sg.id}" + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "https-in" { + type = "ingress" + ip_protocol = "tcp" + nic_type = "internet" + policy = "accept" + port_range = "443/443" + priority = 1 + security_group_id = "${alicloud_security_group.sg.id}" + cidr_ip = "0.0.0.0/0" +} + resource "alicloud_instance" "snat" { # cn-beijing availability_zone = "${var.zone_id}" diff --git a/helper/acctest/random.go b/helper/acctest/random.go index 1a6fc8d199..637b118651 100644 --- a/helper/acctest/random.go +++ b/helper/acctest/random.go @@ -24,6 +24,14 @@ func RandInt() int { return rand.New(rand.NewSource(time.Now().UnixNano())).Int() } +func RandIntRange(min int, max int) int { + reseed() + source := rand.New(rand.NewSource(time.Now().UnixNano())) + rangeMax := max - min + + return int(source.Int31n(int32(rangeMax))) +} + // RandString generates a random alphanumeric string of the length specified func RandString(strlen int) string { return RandStringFromCharSet(strlen, CharSetAlphaNum) diff --git a/helper/resource/state.go b/helper/resource/state.go index aafa7f3bc6..7473a105e8 100644 --- a/helper/resource/state.go +++ b/helper/resource/state.go @@ -141,7 +141,7 @@ func (conf *StateChangeConf) WaitForState() (interface{}, error) { } } - if !found { + if !found && len(conf.Pending) > 0 { result.Error = &UnexpectedStateError{ LastError: err, State: result.State, diff --git a/helper/resource/state_test.go b/helper/resource/state_test.go index dcbb3ce676..4b4731351e 100644 --- a/helper/resource/state_test.go +++ b/helper/resource/state_test.go @@ -70,6 +70,23 @@ func InconsistentStateRefreshFunc() StateRefreshFunc { } } +func UnknownPendingStateRefreshFunc() StateRefreshFunc { + sequence := []string{ + "unknown1", "unknown2", "done", + } + + r := NewStateGenerator(sequence) + + return func() (interface{}, string, error) { + idx, s, err := r.NextState() + if err != nil { + return nil, "", err + } + + return idx, s, nil + } +} + func TestWaitForState_inconsistent_positive(t *testing.T) { conf := &StateChangeConf{ Pending: []string{"replicating"}, @@ -154,6 +171,22 @@ func TestWaitForState_success(t *testing.T) { } } +func TestWaitForState_successUnknownPending(t *testing.T) { + conf := &StateChangeConf{ + Target: []string{"done"}, + Refresh: UnknownPendingStateRefreshFunc(), + Timeout: 200 * time.Second, + } + + obj, err := conf.WaitForState() + if err != nil { + t.Fatalf("err: %s", err) + } + if obj == nil { + t.Fatalf("should return obj") + } +} + func TestWaitForState_successEmpty(t *testing.T) { conf := &StateChangeConf{ Pending: []string{"pending", "incomplete"}, diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 9f103d1857..f62c4d1284 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -1219,6 +1219,13 @@ func (m schemaMap) validateList( for i, raw := range raws { key := fmt.Sprintf("%s.%d", k, i) + // Reify the key value from the ResourceConfig. + // If the list was computed we have all raw values, but some of these + // may be known in the config, and aren't individually marked as Computed. + if r, ok := c.Get(key); ok { + raw = r + } + var ws2 []string var es2 []error switch t := schema.Elem.(type) { diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 2d79341b30..4d93ffd171 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -7,6 +7,7 @@ import ( "reflect" "sort" "strconv" + "strings" "testing" "github.com/hashicorp/hil" @@ -4924,6 +4925,47 @@ func TestSchemaMap_Validate(t *testing.T) { }, Err: true, }, + + // The Validation function should not see interpolation strings from + // non-computed values. + "set with partially computed list and map": { + Schema: map[string]*Schema{ + "outer": &Schema{ + Type: TypeSet, + Optional: true, + Computed: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "list": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Schema{ + Type: TypeString, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + if strings.HasPrefix(v.(string), "${") { + es = append(es, fmt.Errorf("should not have interpolations")) + } + return + }, + }, + }, + }, + }, + }, + }, + Config: map[string]interface{}{ + "outer": []map[string]interface{}{ + { + "list": []interface{}{"${var.a}", "${var.b}", "c"}, + }, + }, + }, + Vars: map[string]string{ + "var.a": "A", + "var.b": config.UnknownVariableValue, + }, + Err: false, + }, } for tn, tc := range cases { diff --git a/terraform/version.go b/terraform/version.go index 193e93cd85..ada5dcc38b 100644 --- a/terraform/version.go +++ b/terraform/version.go @@ -7,7 +7,7 @@ import ( ) // The main version number that is being run at the moment. -const Version = "0.9.2" +const Version = "0.9.3" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/vendor/github.com/denverdino/aliyungo/common/client.go b/vendor/github.com/denverdino/aliyungo/common/client.go index 1fa43afaeb..69a9c3d1e4 100755 --- a/vendor/github.com/denverdino/aliyungo/common/client.go +++ b/vendor/github.com/denverdino/aliyungo/common/client.go @@ -7,8 +7,8 @@ import ( "log" "net/http" "net/url" - "time" "strings" + "time" "github.com/denverdino/aliyungo/util" ) @@ -21,6 +21,9 @@ type Client struct { httpClient *http.Client endpoint string version string + serviceCode string + regionID Region + businessInfo string } // NewClient creates a new instance of ECS client @@ -33,6 +36,26 @@ func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret strin client.version = version } +func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region) { + client.Init(endpoint, version, accessKeyId, accessKeySecret) + client.serviceCode = serviceCode + client.regionID = regionID + client.setEndpointByLocation(regionID, serviceCode, accessKeyId, accessKeySecret) +} + +//NewClient using location service +func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret string) { + locationClient := NewLocationClient(accessKeyId, accessKeySecret) + ep := locationClient.DescribeOpenAPIEndpoint(region, serviceCode) + if ep == "" { + ep = loadEndpointFromFile(region, serviceCode) + } + + if ep != "" { + client.endpoint = ep + } +} + // SetEndpoint sets custom endpoint func (client *Client) SetEndpoint(endpoint string) { client.endpoint = endpoint @@ -43,6 +66,15 @@ func (client *Client) SetVersion(version string) { client.version = version } +func (client *Client) SetRegionID(regionID Region) { + client.regionID = regionID +} + +//SetServiceCode sets serviceCode +func (client *Client) SetServiceCode(serviceCode string) { + client.serviceCode = serviceCode +} + // SetAccessKeyId sets new AccessKeyId func (client *Client) SetAccessKeyId(id string) { client.AccessKeyId = id @@ -58,6 +90,15 @@ func (client *Client) SetDebug(debug bool) { client.debug = debug } +// SetBusinessInfo sets business info to log the request/response message +func (client *Client) SetBusinessInfo(businessInfo string) { + if strings.HasPrefix(businessInfo, "/") { + client.businessInfo = businessInfo + } else if businessInfo != "" { + client.businessInfo = "/" + businessInfo + } +} + // Invoke sends the raw HTTP request for ECS services func (client *Client) Invoke(action string, args interface{}, response interface{}) error { @@ -80,7 +121,7 @@ func (client *Client) Invoke(action string, args interface{}, response interface } // TODO move to util and add build val flag - httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version) + httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) t0 := time.Now() httpResp, err := client.httpClient.Do(httpReq) @@ -128,7 +169,8 @@ func (client *Client) Invoke(action string, args interface{}, response interface // Invoke sends the raw HTTP request for ECS services //改进了一下上面那个方法,可以使用各种Http方法 -func (client *Client) InvokeByAnyMethod(method, action string, args interface{}, response interface{}) error { +//2017.1.30 增加了一个path参数,用来拓展访问的地址 +func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error { request := Request{} request.init(client.version, action, client.AccessKeyId) @@ -140,17 +182,18 @@ func (client *Client) InvokeByAnyMethod(method, action string, args interface{}, signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret) data.Add("Signature", signature) - // Generate the request URL var ( httpReq *http.Request - err error + err error ) if method == http.MethodGet { - requestURL := client.endpoint + "?" + data.Encode() + requestURL := client.endpoint + path + "?" + data.Encode() + //fmt.Println(requestURL) httpReq, err = http.NewRequest(method, requestURL, nil) } else { - httpReq, err = http.NewRequest(method, client.endpoint, strings.NewReader(data.Encode())) + //fmt.Println(client.endpoint + path) + httpReq, err = http.NewRequest(method, client.endpoint+path, strings.NewReader(data.Encode())) httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") } @@ -159,7 +202,7 @@ func (client *Client) InvokeByAnyMethod(method, action string, args interface{}, } // TODO move to util and add build val flag - httpReq.Header.Set("X-SDK-Client", `AliyunGO/` + Version) + httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) t0 := time.Now() httpResp, err := client.httpClient.Do(httpReq) diff --git a/vendor/github.com/denverdino/aliyungo/common/endpoint.go b/vendor/github.com/denverdino/aliyungo/common/endpoint.go new file mode 100644 index 0000000000..16bcbf9d62 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/common/endpoint.go @@ -0,0 +1,118 @@ +package common + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" + "strings" +) + +const ( + // LocationDefaultEndpoint is the default API endpoint of Location services + locationDefaultEndpoint = "https://location.aliyuncs.com" + locationAPIVersion = "2015-06-12" + HTTP_PROTOCOL = "http" + HTTPS_PROTOCOL = "https" +) + +var ( + endpoints = make(map[Region]map[string]string) +) + +//init endpoints from file +func init() { + +} + +func NewLocationClient(accessKeyId, accessKeySecret string) *Client { + endpoint := os.Getenv("LOCATION_ENDPOINT") + if endpoint == "" { + endpoint = locationDefaultEndpoint + } + + client := &Client{} + client.Init(endpoint, locationAPIVersion, accessKeyId, accessKeySecret) + return client +} + +func (client *Client) DescribeEndpoint(args *DescribeEndpointArgs) (*DescribeEndpointResponse, error) { + response := &DescribeEndpointResponse{} + err := client.Invoke("DescribeEndpoint", args, response) + if err != nil { + return nil, err + } + return response, err +} + +func getProductRegionEndpoint(region Region, serviceCode string) string { + if sp, ok := endpoints[region]; ok { + if endpoint, ok := sp[serviceCode]; ok { + return endpoint + } + } + + return "" +} + +func setProductRegionEndpoint(region Region, serviceCode string, endpoint string) { + endpoints[region] = map[string]string{ + serviceCode: endpoint, + } +} + +func (client *Client) DescribeOpenAPIEndpoint(region Region, serviceCode string) string { + if endpoint := getProductRegionEndpoint(region, serviceCode); endpoint != "" { + return endpoint + } + + defaultProtocols := HTTP_PROTOCOL + + args := &DescribeEndpointArgs{ + Id: region, + ServiceCode: serviceCode, + Type: "openAPI", + } + + endpoint, err := client.DescribeEndpoint(args) + if err != nil || endpoint.Endpoint == "" { + return "" + } + + for _, protocol := range endpoint.Protocols.Protocols { + if strings.ToLower(protocol) == HTTPS_PROTOCOL { + defaultProtocols = HTTPS_PROTOCOL + break + } + } + + ep := fmt.Sprintf("%s://%s", defaultProtocols, endpoint.Endpoint) + + setProductRegionEndpoint(region, serviceCode, ep) + return ep +} + +func loadEndpointFromFile(region Region, serviceCode string) string { + data, err := ioutil.ReadFile("./endpoints.xml") + if err != nil { + return "" + } + + var endpoints Endpoints + err = xml.Unmarshal(data, &endpoints) + if err != nil { + return "" + } + + for _, endpoint := range endpoints.Endpoint { + if endpoint.RegionIds.RegionId == string(region) { + for _, product := range endpoint.Products.Product { + if strings.ToLower(product.ProductName) == serviceCode { + return fmt.Sprintf("%s://%s", HTTPS_PROTOCOL, product.DomainName) + } + } + } + } + + return "" +} diff --git a/vendor/github.com/denverdino/aliyungo/common/endpoints.xml b/vendor/github.com/denverdino/aliyungo/common/endpoints.xml new file mode 100644 index 0000000000..8e781ac468 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/common/endpoints.xml @@ -0,0 +1,1351 @@ + + + + jp-fudao-1 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + me-east-1 + + Rdsrds.me-east-1.aliyuncs.com + Ecsecs.me-east-1.aliyuncs.com + Vpcvpc.me-east-1.aliyuncs.com + Kmskms.me-east-1.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.me-east-1.aliyuncs.com + + + + us-east-1 + + CScs.aliyuncs.com + Pushcloudpush.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + HPChpc.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Msgmsg-inner.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Bssbss.aliyuncs.com + Workorderworkorder.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ubsmsubsms.aliyuncs.com + Vpcvpc.aliyuncs.com + Alertalert.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + CFcf.aliyuncs.com + Drdsdrds.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Stssts.aliyuncs.com + Dtsdts.aliyuncs.com + Drcdrc.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Alidnsalidns.aliyuncs.com + Onsons.aliyuncs.com + Cdncdn.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + ap-northeast-1 + + Rdsrds.ap-northeast-1.aliyuncs.com + Kmskms.ap-northeast-1.aliyuncs.com + Vpcvpc.ap-northeast-1.aliyuncs.com + Ecsecs.ap-northeast-1.aliyuncs.com + Cmsmetrics.ap-northeast-1.aliyuncs.com + Kvstorer-kvstore.ap-northeast-1.aliyuncs.com + Slbslb.ap-northeast-1.aliyuncs.com + + + + cn-hangzhou-bj-b01 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-hongkong + + Pushcloudpush.aliyuncs.com + COScos.aliyuncs.com + Onsons.aliyuncs.com + Essess.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + CScs.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Drcdrc.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Dmdm.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + HPChpc.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Vpcvpc.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Bssbss.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Stssts.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Greengreen.aliyuncs.com + Aasaas.aliyuncs.com + Alidnsalidns.aliyuncs.com + Dtsdts.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Rdsrds.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + Ossoss-cn-hongkong.aliyuncs.com + + + + cn-beijing-nu16-b01 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-beijing-am13-c01 + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + in-west-antgroup-1 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-guizhou-gov + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + in-west-antgroup-2 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-qingdao-cm9 + + CScs.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Drdsdrds.aliyuncs.com + HPChpc.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Alertalert.aliyuncs.com + Mscmsc-inner.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + AMSams.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Bssbss.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Stssts.aliyuncs.com + Dtsdts.aliyuncs.com + Emremr.aliyuncs.com + Drcdrc.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + ROSros.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Cdncdn.aliyuncs.com + + + + tw-snowcloud-kaohsiung + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-shanghai-finance-1 + + Kmskms.cn-shanghai-finance-1.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + Rdsrds.aliyuncs.com + + + + cn-guizhou + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + cn-qingdao-finance + + Ossoss-cn-qdjbp-a.aliyuncs.com + + + + cn-beijing-gov-1 + + Ossoss-cn-haidian-a.aliyuncs.com + Rdsrds.aliyuncs.com + + + + cn-shanghai + + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + HPChpc.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Drcdrc.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Drdsdrds.aliyuncs.com + CScs.aliyuncs.com + Kmskms.cn-shanghai.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Alertalert.aliyuncs.com + Mscmsc-inner.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.cn-shanghai.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + Bssbss.aliyuncs.com + Omsoms.aliyuncs.com + Ubsmsubsms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Apigatewayapigateway.cn-shanghai.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Stssts.aliyuncs.com + Vpcvpc.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Ddsmongodb.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Pushcloudpush.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.aliyuncs.com + Dtsdts.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Jaqjaq.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + jaqjaq.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Rdsrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Essess.aliyuncs.com + Ossoss-cn-shanghai.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + cn-shenzhen-inner + + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + Onsons.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + HPChpc.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + jaqjaq.aliyuncs.com + Mscmsc-inner.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Bssbss.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + Alertalert.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + AMSams.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Stssts.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Greengreen.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.aliyuncs.com + CScs.aliyuncs.com + Drcdrc.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + Dtsdts.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + ROSros.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Rdsrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + + + + cn-fujian + + Ecsecs-cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + + + + in-mumbai-alipay + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + us-west-1 + + CScs.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Stssts.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + Pushcloudpush.aliyuncs.com + Alidnsalidns.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Bssbss.aliyuncs.com + Mscmsc-inner.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + Alertalert.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Vpcvpc.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.aliyuncs.com + HPChpc.aliyuncs.com + Drcdrc.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Dtsdts.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + jaqjaq.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Onsons.aliyuncs.com + Ossoss-us-west-1.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + cn-shanghai-inner + + CScs.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.aliyuncs.com + Smssms.aliyuncs.com + Drdsdrds.aliyuncs.com + HPChpc.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Msgmsg-inner.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + jaqjaq.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + Bssbss.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Alertalert.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Stssts.aliyuncs.com + Dtsdts.aliyuncs.com + Drcdrc.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Alidnsalidns.aliyuncs.com + Onsons.aliyuncs.com + Cdncdn.aliyuncs.com + + + + cn-anhui-gov-1 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-hangzhou-finance + + Ossoss-cn-hzjbp-b-console.aliyuncs.com + + + + cn-hangzhou + + CScs.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Stssts.aliyuncs.com + Smssms.aliyuncs.com + Msgmsg-inner.aliyuncs.com + Jaqjaq.aliyuncs.com + Pushcloudpush.aliyuncs.com + Livelive.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Hpchpc.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Drcdrc.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.cn-hangzhou.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Vpcvpc.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + Domaindomain.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Ubsmsubsms.aliyuncs.com + Apigatewayapigateway.cn-hangzhou.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Oascn-hangzhou.oas.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Alidnsalidns.aliyuncs.com + HPChpc.aliyuncs.com + Emremr.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + Dtsdts.aliyuncs.com + Bssbss.aliyuncs.com + Otsots-pop.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Rdsrds.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Onsons.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + cn-beijing-inner + + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + HPChpc.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.aliyuncs.com + Smssms.aliyuncs.com + Drdsdrds.aliyuncs.com + CScs.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Msgmsg-inner.aliyuncs.com + Essess.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Bssbss.aliyuncs.com + Workorderworkorder.aliyuncs.com + Drcdrc.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Dmdm.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + Otsots-pop.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Stssts.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + CFcf.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Greengreen.aliyuncs.com + Aasaas.aliyuncs.com + Alidnsalidns.aliyuncs.com + Pushcloudpush.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + Dtsdts.aliyuncs.com + Domaindomain.aliyuncs.com + ROSros.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Cdncdn.aliyuncs.com + + + + cn-haidian-cm12-c01 + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + Rdsrds.aliyuncs.com + + + + cn-anhui-gov + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + cn-shenzhen + + CScs.aliyuncs.com + COScos.aliyuncs.com + Onsons.aliyuncs.com + Essess.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + Pushcloudpush.aliyuncs.com + Kmskms.cn-shenzhen.aliyuncs.com + Locationlocation.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Alertalert.aliyuncs.com + Drcdrc.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Iotiot.aliyuncs.com + HPChpc.aliyuncs.com + Bssbss.aliyuncs.com + Omsoms.aliyuncs.com + Ubsmsubsms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + BatchComputebatchcompute.cn-shenzhen.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Apigatewayapigateway.cn-shenzhen.aliyuncs.com + CloudAPIapigateway.cn-shenzhen.aliyuncs.com + Stssts.aliyuncs.com + Vpcvpc.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Oascn-shenzhen.oas.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Crmcrm-cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.aliyuncs.com + Dtsdts.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + jaqjaq.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Greengreen.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Ossoss-cn-shenzhen.aliyuncs.com + + + + ap-southeast-2 + + Rdsrds.ap-southeast-2.aliyuncs.com + Kmskms.ap-southeast-2.aliyuncs.com + Vpcvpc.ap-southeast-2.aliyuncs.com + Ecsecs.ap-southeast-2.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.ap-southeast-2.aliyuncs.com + + + + cn-qingdao + + CScs.aliyuncs.com + COScos.aliyuncs.com + HPChpc.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.cn-qingdao.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + Dtsdts.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Essess.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + Drcdrc.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.cn-qingdao.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Dmdm.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Iotiot.aliyuncs.com + Bssbss.aliyuncs.com + Omsoms.aliyuncs.com + Ubsmsubsms.cn-qingdao.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + BatchComputebatchcompute.cn-qingdao.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Otsots-pop.aliyuncs.com + PTSpts.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Apigatewayapigateway.cn-qingdao.aliyuncs.com + CloudAPIapigateway.cn-qingdao.aliyuncs.com + Stssts.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Greengreen.aliyuncs.com + Aasaas.aliyuncs.com + Alidnsalidns.aliyuncs.com + Pushcloudpush.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + Domaindomain.aliyuncs.com + ROSros.aliyuncs.com + jaqjaq.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Ossoss-cn-qingdao.aliyuncs.com + + + + cn-shenzhen-su18-b02 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-shenzhen-su18-b03 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + cn-shenzhen-su18-b01 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + ap-southeast-antgroup-1 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + oss-cn-bjzwy + + Ossoss-cn-bjzwy.aliyuncs.com + + + + cn-henan-am12001 + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + cn-beijing + + CScs.aliyuncs.com + COScos.aliyuncs.com + Jaqjaq.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Stssts.aliyuncs.com + Smssms.aliyuncs.com + Msgmsg-inner.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + HPChpc.aliyuncs.com + Oascn-beijing.oas.aliyuncs.com + Locationlocation.aliyuncs.com + Onsons.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Hpchpc.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + jaqjaq.aliyuncs.com + Workorderworkorder.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Bssbss.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + Alertalert.aliyuncs.com + Omsoms.aliyuncs.com + Ubsmsubsms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Apigatewayapigateway.cn-beijing.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Kmskms.cn-beijing.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.aliyuncs.com + Dtsdts.aliyuncs.com + Drcdrc.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Ossoss-cn-beijing.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + Rdsrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Alidnsalidns.aliyuncs.com + Greengreen.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Cdncdn.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + cn-hangzhou-d + + CScs.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Emremr.aliyuncs.com + Smssms.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Dtsdts.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Bssbss.aliyuncs.com + Mscmsc-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Alidnsalidns.aliyuncs.com + Iotiot.aliyuncs.com + HPChpc.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Alertalert.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + Otsots-pop.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Ubsmsubsms.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + CFcf.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Greengreen.aliyuncs.com + Aasaas.aliyuncs.com + Stssts.aliyuncs.com + Pushcloudpush.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + ROSros.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Drcdrc.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + + + + cn-gansu-am6 + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + Rdsrds.aliyuncs.com + + + + cn-ningxiazhongwei + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + cn-shanghai-et2-b01 + + CScs.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + Onsons.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Jaqjaq.aliyuncs.com + Dtsdts.aliyuncs.com + Kmskms.cn-hangzhou.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Bssbss.aliyuncs.com + Mscmsc-inner.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Dmdm.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + Ubsmsubsms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Ace-opsace-ops.cn-hangzhou.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + AMSams.aliyuncs.com + Otsots-pop.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Acsacs.aliyun-inc.com + Httpdnshttpdns-api.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Stssts.aliyuncs.com + HPChpc.aliyuncs.com + Emremr.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.aliyuncs.com + Slbslb.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + Domaindomain.aliyuncs.com + ROSros.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Drdsdrds.aliyuncs.com + Vpc-innervpc-inner.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Greengreen.aliyuncs.com + Drcdrc.aliyuncs.com + Ossoss-cn-hangzhou.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + + + + cn-ningxia-am7-c01 + + Ecsecs-cn-hangzhou.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + cn-shenzhen-finance-1 + + Kmskms.cn-shenzhen-finance-1.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + Vpcvpc.aliyuncs.com + + + + ap-southeast-1 + + CScs.aliyuncs.com + Riskrisk-cn-hangzhou.aliyuncs.com + COScos.aliyuncs.com + Essess.aliyuncs.com + Billingbilling.aliyuncs.com + Dqsdqs.aliyuncs.com + Ddsmongodb.aliyuncs.com + Alidnsalidns.aliyuncs.com + Smssms.aliyuncs.com + Drdsdrds.aliyuncs.com + Dtsdts.aliyuncs.com + Kmskms.ap-southeast-1.aliyuncs.com + Locationlocation.aliyuncs.com + Msgmsg-inner.aliyuncs.com + ChargingServicechargingservice.aliyuncs.com + R-kvstorer-kvstore-cn-hangzhou.aliyuncs.com + Alertalert.aliyuncs.com + Mscmsc-inner.aliyuncs.com + HighDDosyd-highddos-cn-hangzhou.aliyuncs.com + Yundunyundun-cn-hangzhou.aliyuncs.com + Ubsms-innerubsms-inner.aliyuncs.com + Ocsm-kvstore.aliyuncs.com + Dmdm.aliyuncs.com + Greengreen.aliyuncs.com + Commondrivercommon.driver.aliyuncs.com + oceanbaseoceanbase.aliyuncs.com + Workorderworkorder.aliyuncs.com + Yundunhsmyundunhsm.aliyuncs.com + Iotiot.aliyuncs.com + HPChpc.aliyuncs.com + jaqjaq.aliyuncs.com + Omsoms.aliyuncs.com + livelive.aliyuncs.com + Ecsecs-cn-hangzhou.aliyuncs.com + M-kvstorem-kvstore.aliyuncs.com + Vpcvpc.aliyuncs.com + BatchComputebatchCompute.aliyuncs.com + AMSams.aliyuncs.com + ROSros.aliyuncs.com + PTSpts.aliyuncs.com + Qualitycheckqualitycheck.aliyuncs.com + Bssbss.aliyuncs.com + Ubsmsubsms.aliyuncs.com + Apigatewayapigateway.ap-southeast-1.aliyuncs.com + CloudAPIapigateway.cn-hangzhou.aliyuncs.com + Stssts.aliyuncs.com + CmsSiteMonitorsitemonitor.aliyuncs.com + Aceace.cn-hangzhou.aliyuncs.com + Mtsmts.cn-hangzhou.aliyuncs.com + CFcf.aliyuncs.com + Crmcrm-cn-hangzhou.aliyuncs.com + Location-innerlocation-inner.aliyuncs.com + Aasaas.aliyuncs.com + Emremr.ap-southeast-1.aliyuncs.com + Httpdnshttpdns-api.aliyuncs.com + Drcdrc.aliyuncs.com + Pushcloudpush.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.aliyuncs.com + YundunDdosinner-yundun-ddos.cn-hangzhou.aliyuncs.com + Domaindomain.aliyuncs.com + Otsots-pop.aliyuncs.com + Cdncdn.aliyuncs.com + Ramram.aliyuncs.com + Salessales.cn-hangzhou.aliyuncs.com + Rdsrds.aliyuncs.com + OssAdminoss-admin.aliyuncs.com + Onsons.aliyuncs.com + Ossoss-ap-southeast-1.aliyuncs.com + + + + cn-shenzhen-st4-d01 + + Ecsecs-cn-hangzhou.aliyuncs.com + + + + eu-central-1 + + Rdsrds.eu-central-1.aliyuncs.com + Ecsecs.eu-central-1.aliyuncs.com + Vpcvpc.eu-central-1.aliyuncs.com + Kmskms.eu-central-1.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.eu-central-1.aliyuncs.com + + + \ No newline at end of file diff --git a/vendor/github.com/denverdino/aliyungo/common/regions.go b/vendor/github.com/denverdino/aliyungo/common/regions.go index 781a727bc1..62e6e9d814 100644 --- a/vendor/github.com/denverdino/aliyungo/common/regions.go +++ b/vendor/github.com/denverdino/aliyungo/common/regions.go @@ -5,23 +5,28 @@ type Region string // Constants of region definition const ( - Hangzhou = Region("cn-hangzhou") - Qingdao = Region("cn-qingdao") - Beijing = Region("cn-beijing") - Hongkong = Region("cn-hongkong") - Shenzhen = Region("cn-shenzhen") - USWest1 = Region("us-west-1") - USEast1 = Region("us-east-1") + Hangzhou = Region("cn-hangzhou") + Qingdao = Region("cn-qingdao") + Beijing = Region("cn-beijing") + Hongkong = Region("cn-hongkong") + Shenzhen = Region("cn-shenzhen") + Shanghai = Region("cn-shanghai") + Zhangjiakou = Region("cn-zhangjiakou") + APSouthEast1 = Region("ap-southeast-1") - Shanghai = Region("cn-shanghai") - MEEast1 = Region("me-east-1") APNorthEast1 = Region("ap-northeast-1") APSouthEast2 = Region("ap-southeast-2") - EUCentral1 = Region("eu-central-1") + + USWest1 = Region("us-west-1") + USEast1 = Region("us-east-1") + + MEEast1 = Region("me-east-1") + + EUCentral1 = Region("eu-central-1") ) var ValidRegions = []Region{ - Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, + Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, Zhangjiakou, USWest1, USEast1, APNorthEast1, APSouthEast1, APSouthEast2, MEEast1, diff --git a/vendor/github.com/denverdino/aliyungo/common/types.go b/vendor/github.com/denverdino/aliyungo/common/types.go index c562aedfc2..a74e150e9f 100644 --- a/vendor/github.com/denverdino/aliyungo/common/types.go +++ b/vendor/github.com/denverdino/aliyungo/common/types.go @@ -13,3 +13,77 @@ const ( PrePaid = InstanceChargeType("PrePaid") PostPaid = InstanceChargeType("PostPaid") ) + +type DescribeEndpointArgs struct { + Id Region + ServiceCode string + Type string +} + +type EndpointItem struct { + Protocols struct { + Protocols []string + } + Type string + Namespace string + Id Region + SerivceCode string + Endpoint string +} + +type DescribeEndpointResponse struct { + Response + EndpointItem +} + +type NetType string + +const ( + Internet = NetType("Internet") + Intranet = NetType("Intranet") +) + +type TimeType string + +const ( + Hour = TimeType("Hour") + Day = TimeType("Day") + Month = TimeType("Month") + Year = TimeType("Year") +) + +type NetworkType string + +const ( + Classic = NetworkType("Classic") + VPC = NetworkType("VPC") +) + +type BusinessInfo struct { + Pack string `json:"pack,omitempty"` + ActivityId string `json:"activityId,omitempty"` +} + +//xml +type Endpoints struct { + Endpoint []Endpoint `xml:"Endpoint"` +} + +type Endpoint struct { + Name string `xml:"name,attr"` + RegionIds RegionIds `xml:"RegionIds"` + Products Products `xml:"Products"` +} + +type RegionIds struct { + RegionId string `xml:"RegionId"` +} + +type Products struct { + Product []Product `xml:"Product"` +} + +type Product struct { + ProductName string `xml:"ProductName"` + DomainName string `xml:"DomainName"` +} diff --git a/vendor/github.com/denverdino/aliyungo/ecs/client.go b/vendor/github.com/denverdino/aliyungo/ecs/client.go index 063c0738c0..d70a1554eb 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/client.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/client.go @@ -1,8 +1,9 @@ package ecs import ( - "github.com/denverdino/aliyungo/common" "os" + + "github.com/denverdino/aliyungo/common" ) // Interval for checking status in WaitForXXX method @@ -19,6 +20,12 @@ const ( // ECSDefaultEndpoint is the default API endpoint of ECS services ECSDefaultEndpoint = "https://ecs-cn-hangzhou.aliyuncs.com" ECSAPIVersion = "2014-05-26" + + ECSServiceCode = "ecs" + + VPCDefaultEndpoint = "https://vpc.aliyuncs.com" + VPCAPIVersion = "2016-04-28" + VPCServiceCode = "vpc" ) // NewClient creates a new instance of ECS client @@ -30,8 +37,38 @@ func NewClient(accessKeyId, accessKeySecret string) *Client { return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) } +func NewECSClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { + endpoint := os.Getenv("ECS_ENDPOINT") + if endpoint == "" { + endpoint = ECSDefaultEndpoint + } + + return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) +} + +func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret, ECSServiceCode, regionID) + return client +} + func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client { client := &Client{} client.Init(endpoint, ECSAPIVersion, accessKeyId, accessKeySecret) return client } + +func NewVPCClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { + endpoint := os.Getenv("VPC_ENDPOINT") + if endpoint == "" { + endpoint = VPCDefaultEndpoint + } + + return NewVPCClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) +} + +func NewVPCClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, VPCAPIVersion, accessKeyId, accessKeySecret, VPCServiceCode, regionID) + return client +} diff --git a/vendor/github.com/denverdino/aliyungo/ecs/images.go b/vendor/github.com/denverdino/aliyungo/ecs/images.go index c623caf9c3..0a4e1e2c09 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/images.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/images.go @@ -63,8 +63,12 @@ type DescribeImagesResponse struct { type DiskDeviceMapping struct { SnapshotId string //Why Size Field is string-type. - Size string - Device string + Size string + Device string + //For import images + Format string + OSSBucket string + OSSObject string } // @@ -112,6 +116,7 @@ func (client *Client) DescribeImages(args *DescribeImagesArgs) (images []ImageTy type CreateImageArgs struct { RegionId common.Region SnapshotId string + InstanceId string ImageName string ImageVersion string Description string @@ -227,6 +232,38 @@ func (client *Client) CopyImage(args *CopyImageArgs) (string, error) { return response.ImageId, nil } + +// ImportImageArgs repsents arguements to import image from oss +type ImportImageArgs struct { + RegionId common.Region + ImageName string + ImageVersion string + Description string + ClientToken string + Architecture string + OSType string + Platform string + DiskDeviceMappings struct { + DiskDeviceMapping []DiskDeviceMapping + } +} + +func (client *Client) ImportImage(args *ImportImageArgs) (string, error) { + response := &CopyImageResponse{} + err := client.Invoke("ImportImage", args, &response) + if err != nil { + return "", err + } + return response.ImageId, nil +} + +type ImportImageResponse struct { + common.Response + RegionId common.Region + ImageId string + ImportTaskId string +} + // Default timeout value for WaitForImageReady method const ImageDefaultTimeout = 120 diff --git a/vendor/github.com/denverdino/aliyungo/ecs/instances.go b/vendor/github.com/denverdino/aliyungo/ecs/instances.go index cce16dff48..f2fe74e98f 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/instances.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/instances.go @@ -15,12 +15,14 @@ type InstanceStatus string // Constants of InstanceStatus const ( - Creating = InstanceStatus("Creating") + Creating = InstanceStatus("Creating") // For backward compatability + Pending = InstanceStatus("Pending") Running = InstanceStatus("Running") Starting = InstanceStatus("Starting") Stopped = InstanceStatus("Stopped") Stopping = InstanceStatus("Stopping") + Deleted = InstanceStatus("Deleted") ) type LockReason string @@ -279,6 +281,7 @@ type ModifyInstanceAttributeArgs struct { Description string Password string HostName string + UserData string } type ModifyInstanceAttributeResponse struct { @@ -323,6 +326,38 @@ func (client *Client) WaitForInstance(instanceId string, status InstanceStatus, return nil } +// WaitForInstance waits for instance to given status +// when instance.NotFound wait until timeout +func (client *Client) WaitForInstanceAsyn(instanceId string, status InstanceStatus, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + instance, err := client.DescribeInstanceAttribute(instanceId) + if err != nil { + e, _ := err.(*common.Error) + if e.ErrorResponse.Code != "InvalidInstanceId.NotFound" { + return err + } + time.Sleep(DefaultWaitForInterval * time.Second) + continue + } + if instance.Status == status { + //TODO + //Sleep one more time for timing issues + time.Sleep(DefaultWaitForInterval * time.Second) + break + } + timeout = timeout - DefaultWaitForInterval + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + time.Sleep(DefaultWaitForInterval * time.Second) + + } + return nil +} + type DescribeInstanceVncUrlArgs struct { RegionId common.Region InstanceId string @@ -510,6 +545,43 @@ func (client *Client) CreateInstance(args *CreateInstanceArgs) (instanceId strin return response.InstanceId, err } +type RunInstanceArgs struct { + CreateInstanceArgs + MinAmount int + MaxAmount int + AutoReleaseTime string + NetworkType string + InnerIpAddress string + BusinessInfo string +} + +type RunInstanceResponse struct { + common.Response + InstanceIdSets InstanceIdSets +} + +type InstanceIdSets struct { + InstanceIdSet []string +} + +type BusinessInfo struct { + Pack string `json:"pack,omitempty"` + ActivityId string `json:"activityId,omitempty"` +} + +func (client *Client) RunInstances(args *RunInstanceArgs) (instanceIdSet []string, err error) { + if args.UserData != "" { + // Encode to base64 string + args.UserData = base64.StdEncoding.EncodeToString([]byte(args.UserData)) + } + response := RunInstanceResponse{} + err = client.Invoke("RunInstances", args, &response) + if err != nil { + return nil, err + } + return response.InstanceIdSets.InstanceIdSet, err +} + type SecurityGroupArgs struct { InstanceId string SecurityGroupId string diff --git a/builtin/providers/alicloud/extension_nat_gateway.go b/vendor/github.com/denverdino/aliyungo/ecs/nat_gateway.go similarity index 80% rename from builtin/providers/alicloud/extension_nat_gateway.go rename to vendor/github.com/denverdino/aliyungo/ecs/nat_gateway.go index 3dac446a3f..dfcb74d32d 100644 --- a/builtin/providers/alicloud/extension_nat_gateway.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/nat_gateway.go @@ -1,8 +1,7 @@ -package alicloud +package ecs import ( "github.com/denverdino/aliyungo/common" - "github.com/denverdino/aliyungo/ecs" ) type BandwidthPackageType struct { @@ -25,6 +24,10 @@ type ForwardTableIdType struct { ForwardTableId []string } +type SnatTableIdType struct { + SnatTableId []string +} + type BandwidthPackageIdType struct { BandwidthPackageId []string } @@ -39,7 +42,7 @@ type CreateNatGatewayResponse struct { // CreateNatGateway creates Virtual Private Cloud // // You can read doc at http://docs.aliyun.com/#/pub/ecs/open-api/vpc&createvpc -func CreateNatGateway(client *ecs.Client, args *CreateNatGatewayArgs) (resp *CreateNatGatewayResponse, err error) { +func (client *Client) CreateNatGateway(args *CreateNatGatewayArgs) (resp *CreateNatGatewayResponse, err error) { response := CreateNatGatewayResponse{} err = client.Invoke("CreateNatGateway", args, &response) if err != nil { @@ -53,6 +56,7 @@ type NatGatewaySetType struct { Description string BandwidthPackageIds BandwidthPackageIdType ForwardTableIds ForwardTableIdType + SnatTableIds SnatTableIdType InstanceChargeType string Name string NatGatewayId string @@ -77,7 +81,7 @@ type DescribeNatGatewaysArgs struct { common.Pagination } -func DescribeNatGateways(client *ecs.Client, args *DescribeNatGatewaysArgs) (natGateways []NatGatewaySetType, +func (client *Client) DescribeNatGateways(args *DescribeNatGatewaysArgs) (natGateways []NatGatewaySetType, pagination *common.PaginationResult, err error) { args.Validate() @@ -103,7 +107,7 @@ type ModifyNatGatewayAttributeResponse struct { common.Response } -func ModifyNatGatewayAttribute(client *ecs.Client, args *ModifyNatGatewayAttributeArgs) error { +func (client *Client) ModifyNatGatewayAttribute(args *ModifyNatGatewayAttributeArgs) error { response := ModifyNatGatewayAttributeResponse{} return client.Invoke("ModifyNatGatewayAttribute", args, &response) } @@ -114,7 +118,7 @@ type ModifyNatGatewaySpecArgs struct { Spec NatGatewaySpec } -func ModifyNatGatewaySpec(client *ecs.Client, args *ModifyNatGatewaySpecArgs) error { +func (client *Client) ModifyNatGatewaySpec(args *ModifyNatGatewaySpecArgs) error { response := ModifyNatGatewayAttributeResponse{} return client.Invoke("ModifyNatGatewaySpec", args, &response) } @@ -128,7 +132,7 @@ type DeleteNatGatewayResponse struct { common.Response } -func DeleteNatGateway(client *ecs.Client, args *DeleteNatGatewayArgs) error { +func (client *Client) DeleteNatGateway(args *DeleteNatGatewayArgs) error { response := DeleteNatGatewayResponse{} err := client.Invoke("DeleteNatGateway", args, &response) return err @@ -140,10 +144,20 @@ type DescribeBandwidthPackagesArgs struct { NatGatewayId string } +type PublicIpAddresseType struct { + AllocationId string + IpAddress string +} + type DescribeBandwidthPackageType struct { Bandwidth string BandwidthPackageId string IpCount string + PublicIpAddresses struct { + PublicIpAddresse []PublicIpAddresseType + } + + ZoneId string } type DescribeBandwidthPackagesResponse struct { @@ -153,12 +167,14 @@ type DescribeBandwidthPackagesResponse struct { } } -func DescribeBandwidthPackages(client *ecs.Client, args *DescribeBandwidthPackagesArgs) ([]DescribeBandwidthPackageType, error) { +func (client *Client) DescribeBandwidthPackages(args *DescribeBandwidthPackagesArgs) ([]DescribeBandwidthPackageType, error) { response := &DescribeBandwidthPackagesResponse{} + err := client.Invoke("DescribeBandwidthPackages", args, response) if err != nil { return nil, err } + return response.BandwidthPackages.BandwidthPackage, err } @@ -171,20 +187,12 @@ type DeleteBandwidthPackageResponse struct { common.Response } -func DeleteBandwidthPackage(client *ecs.Client, args *DeleteBandwidthPackageArgs) error { +func (client *Client) DeleteBandwidthPackage(args *DeleteBandwidthPackageArgs) error { response := DeleteBandwidthPackageResponse{} err := client.Invoke("DeleteBandwidthPackage", args, &response) return err } -type DescribeSnatTableEntriesArgs struct { - RegionId common.Region -} - -func DescribeSnatTableEntries(client *ecs.Client, args *DescribeSnatTableEntriesArgs) { - -} - type NatGatewaySpec string const ( diff --git a/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go b/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go index ef057393f6..eaec701dea 100644 --- a/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go +++ b/vendor/github.com/denverdino/aliyungo/ecs/security_groups.go @@ -33,6 +33,7 @@ type DescribeSecurityGroupAttributeArgs struct { SecurityGroupId string RegionId common.Region NicType NicType //enum for internet (default) |intranet + Direction string // enum ingress egress } // diff --git a/vendor/github.com/denverdino/aliyungo/ecs/snat_entry.go b/vendor/github.com/denverdino/aliyungo/ecs/snat_entry.go new file mode 100644 index 0000000000..aa75574c37 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/ecs/snat_entry.go @@ -0,0 +1,95 @@ +package ecs + +import "github.com/denverdino/aliyungo/common" + +type CreateSnatEntryArgs struct { + RegionId common.Region + SnatTableId string + SourceVSwitchId string + SnatIp string +} + +type CreateSnatEntryResponse struct { + common.Response + SnatEntryId string +} + +type SnatEntrySetType struct { + RegionId common.Region + SnatEntryId string + SnatIp string + SnatTableId string + SourceCIDR string + SourceVSwitchId string + Status string +} + +type DescribeSnatTableEntriesArgs struct { + RegionId common.Region + SnatTableId string + common.Pagination +} + +type DescribeSnatTableEntriesResponse struct { + common.Response + common.PaginationResult + SnatTableEntries struct { + SnatTableEntry []SnatEntrySetType + } +} + +type ModifySnatEntryArgs struct { + RegionId common.Region + SnatTableId string + SnatEntryId string + SnatIp string +} + +type ModifySnatEntryResponse struct { + common.Response +} + +type DeleteSnatEntryArgs struct { + RegionId common.Region + SnatTableId string + SnatEntryId string +} + +type DeleteSnatEntryResponse struct { + common.Response +} + +func (client *Client) CreateSnatEntry(args *CreateSnatEntryArgs) (resp *CreateSnatEntryResponse, err error) { + response := CreateSnatEntryResponse{} + err = client.Invoke("CreateSnatEntry", args, &response) + if err != nil { + return nil, err + } + return &response, err +} + +func (client *Client) DescribeSnatTableEntries(args *DescribeSnatTableEntriesArgs) (snatTableEntries []SnatEntrySetType, + pagination *common.PaginationResult, err error) { + + args.Validate() + response := DescribeSnatTableEntriesResponse{} + + err = client.Invoke("DescribeSnatTableEntries", args, &response) + + if err != nil { + return nil, nil, err + } + + return response.SnatTableEntries.SnatTableEntry, &response.PaginationResult, nil +} + +func (client *Client) ModifySnatEntry(args *ModifySnatEntryArgs) error { + response := ModifySnatEntryResponse{} + return client.Invoke("ModifySnatEntry", args, &response) +} + +func (client *Client) DeleteSnatEntry(args *DeleteSnatEntryArgs) error { + response := DeleteSnatEntryResponse{} + err := client.Invoke("DeleteSnatEntry", args, &response) + return err +} diff --git a/vendor/github.com/denverdino/aliyungo/rds/client.go b/vendor/github.com/denverdino/aliyungo/rds/client.go new file mode 100644 index 0000000000..3701e0aa26 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/rds/client.go @@ -0,0 +1,48 @@ +package rds + +import ( + "github.com/denverdino/aliyungo/common" + + "os" +) + +type Client struct { + common.Client +} + +const ( + // ECSDefaultEndpoint is the default API endpoint of RDS services + RDSDefaultEndpoint = "https://rds.aliyuncs.com" + RDSAPIVersion = "2014-08-15" + RDSServiceCode = "rds" +) + +// NewClient creates a new instance of RDS client +func NewClient(accessKeyId, accessKeySecret string) *Client { + endpoint := os.Getenv("RDS_ENDPOINT") + if endpoint == "" { + endpoint = RDSDefaultEndpoint + } + return NewClientWithEndpoint(endpoint, accessKeyId, accessKeySecret) +} + +func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) *Client { + client := &Client{} + client.Init(endpoint, RDSAPIVersion, accessKeyId, accessKeySecret) + return client +} + +func NewRDSClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { + endpoint := os.Getenv("RDS_ENDPOINT") + if endpoint == "" { + endpoint = RDSDefaultEndpoint + } + + return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) +} + +func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, RDSAPIVersion, accessKeyId, accessKeySecret, RDSServiceCode, regionID) + return client +} diff --git a/vendor/github.com/denverdino/aliyungo/rds/instances.go b/vendor/github.com/denverdino/aliyungo/rds/instances.go new file mode 100644 index 0000000000..8204ddf17d --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/rds/instances.go @@ -0,0 +1,843 @@ +package rds + +import ( + "github.com/denverdino/aliyungo/common" + "time" +) + +type DBInstanceIPArray struct { + SecurityIps string + DBInstanceIPArrayName string + DBInstanceIPArrayAttribute string +} + +// ref: https://help.aliyun.com/document_detail/26242.html +type ModifySecurityIpsArgs struct { + DBInstanceId string + DBInstanceIPArray +} + +func (client *Client) ModifySecurityIps(args *ModifySecurityIpsArgs) (resp common.Response, err error) { + response := common.Response{} + err = client.Invoke("ModifySecurityIps", args, &response) + return response, err +} + +type DescribeDBInstanceIPsArgs struct { + DBInstanceId string +} + +type DBInstanceIPList struct { + DBInstanceIPArrayName string + DBInstanceIPArrayAttribute string + SecurityIPList string +} + +type DescribeDBInstanceIPsResponse struct { + common.Response + Items struct { + DBInstanceIPArray []DBInstanceIPList + } +} + +// DescribeDBInstanceIPArrayList describe security ips +// +// You can read doc at https://help.aliyun.com/document_detail/26241.html?spm=5176.doc26242.6.715.d9pxvr +func (client *Client) DescribeDBInstanceIPs(args *DescribeDBInstanceIPsArgs) (resp *DescribeDBInstanceIPsResponse, err error) { + response := DescribeDBInstanceIPsResponse{} + err = client.Invoke("DescribeDBInstanceIPArrayList", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +// InstanceStatus represents instance status +type InstanceStatus string + +// Constants of InstanceStatus +const ( + Creating = InstanceStatus("Creating") // For backward compatability + Running = InstanceStatus("Running") + Deleting = InstanceStatus("Deleting") + Rebooting = InstanceStatus("Rebooting") + + Restoring = InstanceStatus("Restoring") + Importing = InstanceStatus("Importing") + DBInstanceNetTypeChanging = InstanceStatus("DBInstanceNetTypeChanging") +) + +type DBPayType string + +const ( + Prepaid = DBPayType("Prepaid") + Postpaid = DBPayType("Postpaid") +) + +type CommodityCode string + +const ( + Rds = CommodityCode("rds") + Bards = CommodityCode("bards") + Rords = CommodityCode("rords") +) + +type Engine string + +const ( + MySQL = Engine("MySQL") + SQLServer = Engine("SQLServer") + PPAS = Engine("PPAS") + PG = Engine("PG") +) + +type ConnectionMode string + +const ( + Performance = ConnectionMode("Performance") + Safty = ConnectionMode("Safty") +) + +// default resource value for create order +const DefaultResource = "buy" + +type CreateOrderArgs struct { + CommodityCode CommodityCode + RegionId common.Region + ZoneId string + Engine Engine + EngineVersion string + PayType DBPayType + DBInstanceClass string + DBInstanceStorage int + DBInstanceNetType common.NetType + InstanceNetworkType common.NetworkType + VPCId string + VSwitchId string + UsedTime int + TimeType common.TimeType + Quantity int + InstanceUsedType string + Resource string + AutoPay string + AutoRenew string + BackupId string + RestoreTime string + SecurityIPList string + BusinessInfo string +} + +type CreateOrderResponse struct { + common.Response + DBInstanceId string + OrderId int +} + +// CreateOrder create db instance order +// you can read doc at http://docs.alibaba-inc.com/pages/viewpage.action?pageId=259349053 +func (client *Client) CreateOrder(args *CreateOrderArgs) (resp CreateOrderResponse, err error) { + response := CreateOrderResponse{} + err = client.Invoke("CreateOrder", args, &response) + return response, err +} + +type DescribeDBInstancesArgs struct { + DBInstanceId string +} + +type DescribeDBInstanceAttributeResponse struct { + common.Response + Items struct { + DBInstanceAttribute []DBInstanceAttribute + } +} + +type DBInstanceAttribute struct { + DBInstanceId string + PayType DBPayType + DBInstanceType string + InstanceNetworkType string + ConnectionMode string + RegionId string + ZoneId string + ConnectionString string + Port string + Engine Engine + EngineVersion string + DBInstanceClass string + DBInstanceMemory int64 + DBInstanceStorage int + DBInstanceNetType string + DBInstanceStatus InstanceStatus + DBInstanceDescription string + LockMode string + LockReason string + DBMaxQuantity int + AccountMaxQuantity int + CreationTime string + ExpireTime string + MaintainTime string + AvailabilityValue string + MaxIOPS int + MaxConnections int + MasterInstanceId string + IncrementSourceDBInstanceId string + GuardDBInstanceId string + TempDBInstanceId string + ReadOnlyDBInstanceIds ReadOnlyDBInstanceIds + SecurityIPList string +} + +type ReadOnlyDBInstanceIds struct { + ReadOnlyDBInstanceId []string +} + +// DescribeDBInstanceAttribute describes db instance +// +// You can read doc at https://help.aliyun.com/document_detail/26231.html?spm=5176.doc26228.6.702.uhzm31 +func (client *Client) DescribeDBInstanceAttribute(args *DescribeDBInstancesArgs) (resp *DescribeDBInstanceAttributeResponse, err error) { + + response := DescribeDBInstanceAttributeResponse{} + + err = client.Invoke("DescribeDBInstanceAttribute", args, &response) + + if err == nil { + return &response, nil + } + + return nil, err +} + +type DescribeDatabasesArgs struct { + DBInstanceId string + DBName string + DBStatus InstanceStatus +} + +type DescribeDatabasesResponse struct { + common.Response + Databases struct { + Database []Database + } +} + +type Database struct { + DBName string + DBInstanceId string + Engine string + DBStatus InstanceStatus + CharacterSetName InstanceStatus + DBDescription InstanceStatus + Account InstanceStatus + AccountPrivilege InstanceStatus + Accounts struct { + AccountPrivilegeInfo []AccountPrivilegeInfo + } +} + +type AccountPrivilegeInfo struct { + Account string + AccountPrivilege string +} + +// DescribeDatabases describes db database +// +// You can read doc at https://help.aliyun.com/document_detail/26260.html?spm=5176.doc26258.6.732.gCx1a3 +func (client *Client) DescribeDatabases(args *DescribeDatabasesArgs) (resp *DescribeDatabasesResponse, err error) { + + response := DescribeDatabasesResponse{} + + err = client.Invoke("DescribeDatabases", args, &response) + + if err == nil { + return &response, nil + } + + return nil, err +} + +type DescribeAccountsArgs struct { + DBInstanceId string + AccountName string +} + +type DescribeAccountsResponse struct { + common.Response + Accounts struct { + DBInstanceAccount []DBInstanceAccount + } +} + +type DBInstanceAccount struct { + DBInstanceId string + AccountName string + AccountStatus AccountStatus + AccountDescription string + DatabasePrivileges struct { + DatabasePrivilege []DatabasePrivilege + } +} + +type AccountStatus string + +const ( + Unavailable = AccountStatus("Unavailable") + Available = AccountStatus("Available") +) + +type DatabasePrivilege struct { + DBName string + AccountPrivilege AccountPrivilege +} + +// DescribeAccounts describes db accounts +// +// You can read doc at https://help.aliyun.com/document_detail/26265.html?spm=5176.doc26266.6.739.UjtjaI +func (client *Client) DescribeAccounts(args *DescribeAccountsArgs) (resp *DescribeAccountsResponse, err error) { + + response := DescribeAccountsResponse{} + + err = client.Invoke("DescribeAccounts", args, &response) + + if err == nil { + return &response, nil + } + + return nil, err +} + +// Default timeout value for WaitForInstance method +const InstanceDefaultTimeout = 120 +const DefaultWaitForInterval = 10 + +// WaitForInstance waits for instance to given status +func (client *Client) WaitForInstance(instanceId string, status InstanceStatus, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + args := DescribeDBInstancesArgs{ + DBInstanceId: instanceId, + } + + resp, err := client.DescribeDBInstanceAttribute(&args) + if err != nil { + return err + } + + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + + timeout = timeout - DefaultWaitForInterval + time.Sleep(DefaultWaitForInterval * time.Second) + + if len(resp.Items.DBInstanceAttribute) < 1 { + continue + } + instance := resp.Items.DBInstanceAttribute[0] + if instance.DBInstanceStatus == status { + break + } + + } + return nil +} + +func (client *Client) WaitForAllDatabase(instanceId string, databaseNames []string, status InstanceStatus, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + args := DescribeDatabasesArgs{ + DBInstanceId: instanceId, + } + + resp, err := client.DescribeDatabases(&args) + if err != nil { + return err + } + + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + + timeout = timeout - DefaultWaitForInterval + time.Sleep(DefaultWaitForInterval * time.Second) + + ready := 0 + + for _, nm := range databaseNames { + for _, db := range resp.Databases.Database { + if db.DBName == nm { + if db.DBStatus == status { + ready++ + break + } + } + } + } + + if ready == len(databaseNames) { + break + } + + } + return nil +} + +func (client *Client) WaitForAccount(instanceId string, accountName string, status AccountStatus, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + args := DescribeAccountsArgs{ + DBInstanceId: instanceId, + AccountName: accountName, + } + + resp, err := client.DescribeAccounts(&args) + if err != nil { + return err + } + + accs := resp.Accounts.DBInstanceAccount + + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + + timeout = timeout - DefaultWaitForInterval + time.Sleep(DefaultWaitForInterval * time.Second) + + if len(accs) < 1 { + continue + } + + acc := accs[0] + + if acc.AccountStatus == status { + break + } + + } + return nil +} + +func (client *Client) WaitForPublicConnection(instanceId string, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + args := DescribeDBInstanceNetInfoArgs{ + DBInstanceId: instanceId, + } + + resp, err := client.DescribeDBInstanceNetInfo(&args) + if err != nil { + return err + } + + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + + timeout = timeout - DefaultWaitForInterval + time.Sleep(DefaultWaitForInterval * time.Second) + + ready := false + for _, info := range resp.DBInstanceNetInfos.DBInstanceNetInfo { + if info.IPType == Public { + ready = true + } + } + + if ready { + break + } + + } + return nil +} + +func (client *Client) WaitForAccountPrivilege(instanceId, accountName, dbName string, privilege AccountPrivilege, timeout int) error { + if timeout <= 0 { + timeout = InstanceDefaultTimeout + } + for { + args := DescribeAccountsArgs{ + DBInstanceId: instanceId, + AccountName: accountName, + } + + resp, err := client.DescribeAccounts(&args) + if err != nil { + return err + } + + accs := resp.Accounts.DBInstanceAccount + + if timeout <= 0 { + return common.GetClientErrorFromString("Timeout") + } + + timeout = timeout - DefaultWaitForInterval + time.Sleep(DefaultWaitForInterval * time.Second) + + if len(accs) < 1 { + continue + } + + acc := accs[0] + + ready := false + for _, dp := range acc.DatabasePrivileges.DatabasePrivilege { + if dp.DBName == dbName && dp.AccountPrivilege == privilege { + ready = true + } + } + + if ready { + break + } + + } + return nil +} + +type DeleteDBInstanceArgs struct { + DBInstanceId string +} + +type DeleteDBInstanceResponse struct { + common.Response +} + +// DeleteInstance deletes db instance +// +// You can read doc at https://help.aliyun.com/document_detail/26229.html?spm=5176.doc26315.6.700.7SmyAT +func (client *Client) DeleteInstance(instanceId string) error { + args := DeleteDBInstanceArgs{DBInstanceId: instanceId} + response := DeleteDBInstanceResponse{} + err := client.Invoke("DeleteDBInstance", &args, &response) + return err +} + +type DeleteDatabaseArgs struct { + DBInstanceId string + DBName string +} + +type DeleteDatabaseResponse struct { + common.Response +} + +// DeleteInstance deletes database +// +// You can read doc at https://help.aliyun.com/document_detail/26259.html?spm=5176.doc26260.6.731.Abjwne +func (client *Client) DeleteDatabase(instanceId, dbName string) error { + args := DeleteDatabaseArgs{ + DBInstanceId: instanceId, + DBName: dbName, + } + response := DeleteDatabaseResponse{} + err := client.Invoke("DeleteDatabase", &args, &response) + return err +} + +type DescribeRegionsArgs struct { +} + +type DescribeRegionsResponse struct { + Regions struct { + RDSRegion []RDSRegion + } +} + +type RDSRegion struct { + RegionId string + ZoneId string +} + +// DescribeRegions describe rds regions +// +// You can read doc at https://help.aliyun.com/document_detail/26243.html?spm=5176.doc26244.6.715.OSNUa8 +func (client *Client) DescribeRegions() (resp *DescribeRegionsResponse, err error) { + args := DescribeRegionsArgs{} + response := DescribeRegionsResponse{} + err = client.Invoke("DescribeRegions", &args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type CreateDatabaseResponse struct { + common.Response +} + +type CreateDatabaseArgs struct { + DBInstanceId string + DBName string + CharacterSetName string + DBDescription string +} + +// CreateDatabase create rds database +// +// You can read doc at https://help.aliyun.com/document_detail/26243.html?spm=5176.doc26244.6.715.OSNUa8 +func (client *Client) CreateDatabase(args *CreateDatabaseArgs) (resp *CreateDatabaseResponse, err error) { + response := CreateDatabaseResponse{} + err = client.Invoke("CreateDatabase", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type CreateAccountResponse struct { + common.Response +} + +type AccountType struct { + Normal string + Super string +} + +type CreateAccountArgs struct { + DBInstanceId string + AccountName string + AccountPassword string + AccountType AccountType + AccountDescription string +} + +// CreateAccount create rds account +// +// You can read doc at https://help.aliyun.com/document_detail/26263.html?spm=5176.doc26240.6.736.ZDihok +func (client *Client) CreateAccount(args *CreateAccountArgs) (resp *CreateAccountResponse, err error) { + response := CreateAccountResponse{} + err = client.Invoke("CreateAccount", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type DeleteAccountResponse struct { + common.Response +} + +type DeleteAccountArgs struct { + DBInstanceId string + AccountName string +} + +// DeleteAccount delete account +// +// You can read doc at https://help.aliyun.com/document_detail/26264.html?spm=5176.doc26269.6.737.CvlZp6 +func (client *Client) DeleteAccount(instanceId, accountName string) (resp *DeleteAccountResponse, err error) { + args := DeleteAccountArgs{ + DBInstanceId: instanceId, + AccountName: accountName, + } + + response := DeleteAccountResponse{} + err = client.Invoke("DeleteAccount", &args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type GrantAccountPrivilegeResponse struct { + common.Response +} + +type GrantAccountPrivilegeArgs struct { + DBInstanceId string + AccountName string + DBName string + AccountPrivilege AccountPrivilege +} + +type AccountPrivilege string + +const ( + ReadOnly = AccountPrivilege("ReadOnly") + ReadWrite = AccountPrivilege("ReadWrite") +) + +// GrantAccountPrivilege grant database privilege to account +// +// You can read doc at https://help.aliyun.com/document_detail/26266.html?spm=5176.doc26264.6.739.o2y01n +func (client *Client) GrantAccountPrivilege(args *GrantAccountPrivilegeArgs) (resp *GrantAccountPrivilegeResponse, err error) { + response := GrantAccountPrivilegeResponse{} + err = client.Invoke("GrantAccountPrivilege", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type AllocateInstancePublicConnectionResponse struct { + common.Response +} + +type AllocateInstancePublicConnectionArgs struct { + DBInstanceId string + ConnectionStringPrefix string + Port string +} + +// AllocateInstancePublicConnection allocate public connection +// +// You can read doc at https://help.aliyun.com/document_detail/26234.html?spm=5176.doc26265.6.708.PdsJnL +func (client *Client) AllocateInstancePublicConnection(args *AllocateInstancePublicConnectionArgs) (resp *AllocateInstancePublicConnectionResponse, err error) { + response := AllocateInstancePublicConnectionResponse{} + err = client.Invoke("AllocateInstancePublicConnection", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type DescribeDBInstanceNetInfoArgs struct { + DBInstanceId string +} + +type DescribeDBInstanceNetInfoResponse struct { + common.Response + InstanceNetworkType string + DBInstanceNetInfos struct { + DBInstanceNetInfo []DBInstanceNetInfo + } +} + +type DBInstanceNetInfo struct { + ConnectionString string + IPAddress string + Port string + VPCId string + VSwitchId string + IPType IPType +} + +type IPType string + +const ( + Inner = IPType("Inner") + Private = IPType("Private") + Public = IPType("Public") +) + +// DescribeDBInstanceNetInfo describe rds net info +// +// You can read doc at https://help.aliyun.com/document_detail/26237.html?spm=5176.doc26234.6.711.vHOktx +func (client *Client) DescribeDBInstanceNetInfo(args *DescribeDBInstanceNetInfoArgs) (resp *DescribeDBInstanceNetInfoResponse, err error) { + response := DescribeDBInstanceNetInfoResponse{} + err = client.Invoke("DescribeDBInstanceNetInfo", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type BackupPolicy struct { + PreferredBackupTime string // HH:mmZ - HH:mm Z + PreferredBackupPeriod string // Monday - Sunday + BackupRetentionPeriod int // 7 - 730 + BackupLog string // enum Enable | Disabled + LogBackupRetentionPeriod string +} + +type ModifyBackupPolicyArgs struct { + DBInstanceId string + BackupPolicy +} + +type ModifyBackupPolicyResponse struct { + common.Response +} + +// ModifyBackupPolicy modify backup policy +// +// You can read doc at https://help.aliyun.com/document_detail/26276.html?spm=5176.doc26250.6.751.KOew21 +func (client *Client) ModifyBackupPolicy(args *ModifyBackupPolicyArgs) (resp *ModifyBackupPolicyResponse, err error) { + response := ModifyBackupPolicyResponse{} + err = client.Invoke("ModifyBackupPolicy", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type DescribeBackupPolicyArgs struct { + DBInstanceId string +} + +type DescribeBackupPolicyResponse struct { + common.Response + BackupPolicy +} + +// DescribeBackupPolicy describe backup policy +// +// You can read doc at https://help.aliyun.com/document_detail/26275.html?spm=5176.doc26276.6.750.CUqjDn +func (client *Client) DescribeBackupPolicy(args *DescribeBackupPolicyArgs) (resp *DescribeBackupPolicyResponse, err error) { + response := DescribeBackupPolicyResponse{} + err = client.Invoke("DescribeBackupPolicy", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +type ModifyDBInstanceSpecArgs struct { + DBInstanceId string + PayType DBPayType + DBInstanceClass string + DBInstanceStorage string +} + +type ModifyDBInstanceSpecResponse struct { + common.Response +} + +// ModifyDBInstanceSpec modify db instance spec +// +// You can read doc at https://help.aliyun.com/document_detail/26233.html?spm=5176.doc26258.6.707.2QOLrM +func (client *Client) ModifyDBInstanceSpec(args *ModifyDBInstanceSpecArgs) (resp *ModifyDBInstanceSpecResponse, err error) { + response := ModifyDBInstanceSpecResponse{} + err = client.Invoke("ModifyDBInstanceSpec", args, &response) + + if err != nil { + return nil, err + } + return &response, nil +} + +var WEEK_ENUM = []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"} + +var BACKUP_TIME = []string{ + "00:00Z-01:00Z", "01:00Z-02:00Z", "02:00Z-03:00Z", "03:00Z-04:00Z", "04:00Z-05:00Z", + "05:00Z-06:00Z", "06:00Z-07:00Z", "07:00Z-08:00Z", "08:00Z-09:00Z", "09:00Z-10:00Z", + "10:00Z-11:00Z", "11:00Z-12:00Z", "12:00Z-13:00Z", "13:00Z-14:00Z", "14:00Z-15:00Z", + "15:00Z-16:00Z", "16:00Z-17:00Z", "17:00Z-18:00Z", "18:00Z-19:00Z", "19:00Z-20:00Z", + "20:00Z-21:00Z", "21:00Z-22:00Z", "22:00Z-23:00Z", "23:00Z-24:00Z", +} + +var CHARACTER_SET_NAME = []string{ + "utf8", "gbk", "latin1", "utf8mb4", + "Chinese_PRC_CI_AS", "Chinese_PRC_CS_AS", "SQL_Latin1_General_CP1_CI_AS", "SQL_Latin1_General_CP1_CS_AS", "Chinese_PRC_BIN", +} diff --git a/vendor/github.com/denverdino/aliyungo/rds/monitoring.go b/vendor/github.com/denverdino/aliyungo/rds/monitoring.go new file mode 100644 index 0000000000..70a7eb6ff4 --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/rds/monitoring.go @@ -0,0 +1,50 @@ +package rds + +import ( + "github.com/denverdino/aliyungo/common" + "github.com/denverdino/aliyungo/util" +) + +type DescribeDBInstancePerformanceArgs struct { + DBInstanceId string + key string + StartTime string + EndTime string +} + +type PerformanceValueType struct { + Value string + Date util.ISO6801Time +} + +type PerformanceKeyType struct { + Key string + Unit string + ValueFormat string + Values struct { + PerformanceValue []PerformanceValueType + } +} + +type DescribeDBInstancePerformanceResponse struct { + common.Response + DBInstanceId string + Engine string + StartTime util.ISO6801Time + EndTime util.ISO6801Time + PerformanceKeys struct { + PerformanceKey []PerformanceKeyType + } +} + +func (client *DescribeDBInstancePerformanceArgs) Setkey(key string) { + client.key = key +} + +func (client *Client) DescribeDBInstancePerformance(args *DescribeDBInstancePerformanceArgs) (resp DescribeDBInstancePerformanceResponse, err error) { + + response := DescribeDBInstancePerformanceResponse{} + err = client.Invoke("DescribeDBInstancePerformance", args, &response) + return response, err + +} diff --git a/vendor/github.com/denverdino/aliyungo/slb/client.go b/vendor/github.com/denverdino/aliyungo/slb/client.go index 8dd26b6913..0f9b705e4a 100644 --- a/vendor/github.com/denverdino/aliyungo/slb/client.go +++ b/vendor/github.com/denverdino/aliyungo/slb/client.go @@ -1,8 +1,9 @@ package slb import ( - "github.com/denverdino/aliyungo/common" "os" + + "github.com/denverdino/aliyungo/common" ) type Client struct { @@ -13,6 +14,8 @@ const ( // SLBDefaultEndpoint is the default API endpoint of SLB services SLBDefaultEndpoint = "https://slb.aliyuncs.com" SLBAPIVersion = "2014-05-15" + + SLBServiceCode = "slb" ) // NewClient creates a new instance of ECS client @@ -29,3 +32,18 @@ func NewClientWithEndpoint(endpoint string, accessKeyId, accessKeySecret string) client.Init(endpoint, SLBAPIVersion, accessKeyId, accessKeySecret) return client } + +func NewSLBClient(accessKeyId, accessKeySecret string, regionID common.Region) *Client { + endpoint := os.Getenv("SLB_ENDPOINT") + if endpoint == "" { + endpoint = SLBDefaultEndpoint + } + + return NewClientWithRegion(endpoint, accessKeyId, accessKeySecret, regionID) +} + +func NewClientWithRegion(endpoint string, accessKeyId, accessKeySecret string, regionID common.Region) *Client { + client := &Client{} + client.NewInit(endpoint, SLBAPIVersion, accessKeyId, accessKeySecret, SLBServiceCode, regionID) + return client +} diff --git a/vendor/github.com/denverdino/aliyungo/slb/listeners.go b/vendor/github.com/denverdino/aliyungo/slb/listeners.go index d435576190..50f90f7f28 100644 --- a/vendor/github.com/denverdino/aliyungo/slb/listeners.go +++ b/vendor/github.com/denverdino/aliyungo/slb/listeners.go @@ -138,22 +138,22 @@ const ( ) type TCPListenerType struct { - LoadBalancerId string - ListenerPort int - BackendServerPort int - Bandwidth int - Scheduler SchedulerType - PersistenceTimeout int - HealthCheckType HealthCheckType - HealthCheckDomain string - HealthCheckURI string - HealthCheckConnectPort int - HealthyThreshold int - UnhealthyThreshold int - HealthCheckTimeout int - HealthCheckInterval int - HealthCheckHttpCode HealthCheckHttpCodeType - VServerGroupId string + LoadBalancerId string + ListenerPort int + BackendServerPort int + Bandwidth int + Scheduler SchedulerType + PersistenceTimeout int + HealthCheckType HealthCheckType + HealthCheckDomain string + HealthCheckURI string + HealthCheckConnectPort int + HealthyThreshold int + UnhealthyThreshold int + HealthCheckConnectTimeout int + HealthCheckInterval int + HealthCheckHttpCode HealthCheckHttpCodeType + VServerGroupId string } type CreateLoadBalancerTCPListenerArgs TCPListenerType @@ -168,18 +168,18 @@ func (client *Client) CreateLoadBalancerTCPListener(args *CreateLoadBalancerTCPL } type UDPListenerType struct { - LoadBalancerId string - ListenerPort int - BackendServerPort int - Bandwidth int - Scheduler SchedulerType - PersistenceTimeout int - HealthCheckConnectPort int - HealthyThreshold int - UnhealthyThreshold int - HealthCheckTimeout int - HealthCheckInterval int - VServerGroupId string + LoadBalancerId string + ListenerPort int + BackendServerPort int + Bandwidth int + Scheduler SchedulerType + PersistenceTimeout int + HealthCheckConnectPort int + HealthyThreshold int + UnhealthyThreshold int + HealthCheckConnectTimeout int + HealthCheckInterval int + VServerGroupId string } type CreateLoadBalancerUDPListenerArgs UDPListenerType diff --git a/vendor/github.com/denverdino/aliyungo/slb/rules.go b/vendor/github.com/denverdino/aliyungo/slb/rules.go new file mode 100644 index 0000000000..94eb402b6b --- /dev/null +++ b/vendor/github.com/denverdino/aliyungo/slb/rules.go @@ -0,0 +1,126 @@ +package slb + +import "github.com/denverdino/aliyungo/common" + +type CreateRulesResponse struct { + common.Response +} + +type CreateRulesArgs struct { + RegionId common.Region + LoadBalancerId string + ListenerPort int + RuleList string +} + +type Rule struct { + RuleId string + RuleName string + Domain string + Url string `json:",omitempty"` + VServerGroupId string +} + +// Create forward rules +// +// You can read doc at https://help.aliyun.com/document_detail/35226.html?spm=5176.doc35226.6.671.625Omh +func (client *Client) CreateRules(args *CreateRulesArgs) error { + response := CreateRulesResponse{} + err := client.Invoke("CreateRules", args, &response) + if err != nil { + return err + } + return err +} + +type DeleteRulesArgs struct { + RegionId common.Region + RuleIds string +} + +type DeleteRulesResponse struct { + common.Response +} + +// Delete forward rules +// +// You can read doc at https://help.aliyun.com/document_detail/35227.html?spm=5176.doc35226.6.672.6iNBtR +func (client *Client) DeleteRules(args *DeleteRulesArgs) error { + response := DeleteRulesResponse{} + err := client.Invoke("DeleteRules", args, &response) + if err != nil { + return err + } + return err +} + +type SetRuleArgs struct { + RegionId common.Region + RuleId string + VServerGroupId string +} + +type SetRuleResponse struct { + common.Response +} + +// Modify forward rules +// +// You can read doc at https://help.aliyun.com/document_detail/35228.html?spm=5176.doc35227.6.673.rq40a9 +func (client *Client) SetRule(args *SetRuleArgs) error { + response := SetRuleResponse{} + err := client.Invoke("SetRule", args, &response) + if err != nil { + return err + } + return err +} + +type DescribeRuleAttributeArgs struct { + RegionId common.Region + RuleId string +} + +type DescribeRuleAttributeResponse struct { + common.Response + LoadBalancerId string + ListenerPort int + Rule +} + +// Describe rule +// +// You can read doc at https://help.aliyun.com/document_detail/35229.html?spm=5176.doc35226.6.674.DRJeKJ +func (client *Client) DescribeRuleAttribute(args *DescribeRuleAttributeArgs) (*DescribeRuleAttributeResponse, error) { + response := &DescribeRuleAttributeResponse{} + err := client.Invoke("DescribeRuleAttribute", args, response) + if err != nil { + return nil, err + } + return response, nil +} + +type DescribeRulesArgs struct { + RegionId common.Region + LoadBalancerId string + ListenerPort int +} + +type DescribeRulesResponse struct { + common.Response + Rules struct { + Rule []Rule + } +} + +// Describe rule +// +// You can read doc at https://help.aliyun.com/document_detail/35229.html?spm=5176.doc35226.6.674.DRJeKJ +func (client *Client) DescribeRules(args *DescribeRulesArgs) (*DescribeRulesResponse, error) { + response := &DescribeRulesResponse{} + err := client.Invoke("DescribeRules", args, response) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/vendor/github.com/denverdino/aliyungo/slb/servers.go b/vendor/github.com/denverdino/aliyungo/slb/servers.go index a3fb2a406c..18be45891a 100644 --- a/vendor/github.com/denverdino/aliyungo/slb/servers.go +++ b/vendor/github.com/denverdino/aliyungo/slb/servers.go @@ -23,7 +23,6 @@ type AddBackendServersResponse struct { type SetBackendServersResponse AddBackendServersResponse - // SetBackendServers set weight of backend servers func (client *Client) SetBackendServers(loadBalancerId string, backendServers []BackendServerType) (result []BackendServerType, err error) { @@ -42,7 +41,6 @@ func (client *Client) SetBackendServers(loadBalancerId string, backendServers [] return response.BackendServers.BackendServer, err } - // AddBackendServers Add backend servers // // You can read doc at http://docs.aliyun.com/#/pub/slb/api-reference/api-related-backendserver&AddBackendServers diff --git a/vendor/github.com/dustinkirkland/golang-petname/LICENSE b/vendor/github.com/dustinkirkland/golang-petname/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/github.com/dustinkirkland/golang-petname/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/dustinkirkland/golang-petname/README.md b/vendor/github.com/dustinkirkland/golang-petname/README.md new file mode 100644 index 0000000000..13374040f3 --- /dev/null +++ b/vendor/github.com/dustinkirkland/golang-petname/README.md @@ -0,0 +1,107 @@ +#petname + +##NAME []() + +**petname** − a utility to generate "pet names", consisting of a random combination of adverbs, an adjective, and an animal name + +##SYNOPSIS []() + +**petname** \[-w|--words INT\] \[-l|--letters INT\] \[-s|--separator STR\] \[-d|--dir STR\] \[-c|--complexity INT\] \[-u|--ubuntu\] + +##OPTIONS []() +- -w|--words number of words in the name, default is 2 +- -l|--letters maximum number of letters in each word, default is unlimited +- -s|--separator string used to separate name words, default is ’-’ +- -d|--dir directory containing adverbs.txt, adjectives.txt, names.txt, default is */usr/share/petname/* +- -c|--complexity \[0, 1, 2\]; 0 = easy words, 1 = standard words, 2 = complex words, default=1 +- -u|--ubuntu generate ubuntu-style names, alliteration of first character of each word + +##DESCRIPTION []() + +This utility will generate "pet names", consisting of a random combination of an adverb, adjective, and an animal name. These are useful for unique hostnames or container names, for instance. + +As such, PetName tries to follow the tenets of Zooko’s triangle. Names are: + +- human meaningful +- decentralized +- secure + +##EXAMPLES []() + +``` +$ petname +wiggly-yellowtail + +$ petname --words 1 +robin + +$ petname --words 3 +primly-lasting-toucan + +$ petname --words 4 +angrily-impatiently-sage-longhorn + +$ petname --separator ":" +cool:gobbler + +$ petname --separator "" --words 3 +comparablyheartylionfish + +$ petname --ubuntu +amazed-asp + +$ petname --complexity 0 +massive-colt +``` + +##CODE []() + +Besides this shell utility, there are also native libraries: python-petname, python3-petname, and golang-petname. Here are some programmatic examples in code: + +**Golang Example** +```golang +package main + +import ( + "flag" + "fmt" + "github.com/dustinkirkland/golang-petname" +) + +var ( + words = flag.Int("words", 2, "The number of words in the pet name") + separator = flag.String("separator", "-", "The separator between words in the pet name") +) + +func main() { + flag.Parse() + fmt.Println(petname.Generate(\*words, \*separator)) +} +``` + +**Python Example** +See: https://pypi.golang.org/pypi/petname + +$ pip install petname +$ sudo apt-get install golang-petname + +```python +#!/usr/bin/python +import argparse +import petname + +parser = argparse.ArgumentParser(description="Generate human readable random names") +parser.add_argument("-w", "--words", help="Number of words in name, default=2", default=2) +parser.add_argument("-s", "--separator", help="Separator between words, default='-'", default="-") +parser.options = parser.parse_args() + +print petname.Generate(int(parser.options.words), parser.options.separator) +``` + +##AUTHOR []() + +This manpage and the utility were written by Dustin Kirkland <dustin.kirkland@gmail.com> for Ubuntu systems (but may be used by others). Permission is granted to copy, distribute and/or modify this document and the utility under the terms of the Apache2 License. + +The complete text of the Apache2 License can be found in */usr/share/common-licenses/Apache-2.0* on Debian/Ubuntu systems. + +------------------------------------------------------------------------ diff --git a/vendor/github.com/dustinkirkland/golang-petname/golang-petname.1 b/vendor/github.com/dustinkirkland/golang-petname/golang-petname.1 new file mode 100644 index 0000000000..f9fdc74272 --- /dev/null +++ b/vendor/github.com/dustinkirkland/golang-petname/golang-petname.1 @@ -0,0 +1,51 @@ +.TH golang-petname 1 "15 December 2014" golang-petname "golang-petname" +.SH NAME +golang-petname \- utility to generate "pet names", consisting of a random combination of adverbs, an adjective, and a proper name + +.SH SYNOPSIS +\fBgolang-petname\fP [-w|--words INT] [-s|--separator STR] + +.SH OPTIONS + + --words number of words in the name, default is 2 + --separator string used to separate name words, default is '-' + +.SH DESCRIPTION + +This utility will generate "pet names", consisting of a random combination of an adverb, adjective, and proper name. These are useful for unique hostnames, for instance. + +The default packaging contains about 2000 names, 1300 adjectives, and 4000 adverbs, yielding nearly 10 billion unique combinations, covering over 32 bits of unique namespace. + +As such, PetName tries to follow the tenets of Zooko's triangle. Names are: + + - human meaningful + - decentralized + - secure + +.SH EXAMPLES + + $ golang-petname + wiggly-Anna + + $ golang-petname --words 1 + Marco + + $ golang-petname --words 3 + quickly-scornful-Johnathan + + $ golang-petname --words 4 + dolorously-leisurely-wee-Susan + + $ golang-petname --separator ":" + hospitable:Isla + + $ golang-petname --separator "" --words 3 + adeptlystaticNicole + +.SH SEE ALSO +\fIpetname\fP(1) + +.SH AUTHOR +This manpage and the utility were written by Dustin Kirkland for Ubuntu systems (but may be used by others). Permission is granted to copy, distribute and/or modify this document and the utility under the terms of the Apache2 License. + +The complete text of the Apache2 License can be found in \fI/usr/share/common-licenses/Apache-2.0\fP on Debian/Ubuntu systems. diff --git a/vendor/github.com/dustinkirkland/golang-petname/petname.go b/vendor/github.com/dustinkirkland/golang-petname/petname.go new file mode 100644 index 0000000000..a66b7ebe9c --- /dev/null +++ b/vendor/github.com/dustinkirkland/golang-petname/petname.go @@ -0,0 +1,78 @@ +/* + petname: library for generating human-readable, random names + for objects (e.g. hostnames, containers, blobs) + + Copyright 2014 Dustin Kirkland + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package petname is a library for generating human-readable, random +// names for objects (e.g. hostnames, containers, blobs). +package petname + +import ( + "math/rand" + "strings" + "time" +) + +// These lists are autogenerated from the master lists in the project: +// - https://github.com/dustinkirkland/petname +// These lists only get modified after updating that branch, and then +// automatically updated by ./debian/update-wordlists.sh as part of +// my release process +var ( + adjectives = [...]string{"able", "above", "absolute", "accepted", "accurate", "ace", "active", "actual", "adapted", "adapting", "adequate", "adjusted", "advanced", "alert", "alive", "allowed", "allowing", "amazed", "amazing", "ample", "amused", "amusing", "apparent", "apt", "arriving", "artistic", "assured", "assuring", "awaited", "awake", "aware", "balanced", "becoming", "beloved", "better", "big", "blessed", "bold", "boss", "brave", "brief", "bright", "bursting", "busy", "calm", "capable", "capital", "careful", "caring", "casual", "causal", "central", "certain", "champion", "charmed", "charming", "cheerful", "chief", "choice", "civil", "classic", "clean", "clear", "clever", "climbing", "close", "closing", "coherent", "comic", "communal", "complete", "composed", "concise", "concrete", "content", "cool", "correct", "cosmic", "crack", "creative", "credible", "crisp", "crucial", "cuddly", "cunning", "curious", "current", "cute", "daring", "darling", "dashing", "dear", "decent", "deciding", "deep", "definite", "delicate", "desired", "destined", "devoted", "direct", "discrete", "distinct", "diverse", "divine", "dominant", "driven", "driving", "dynamic", "eager", "easy", "electric", "elegant", "emerging", "eminent", "enabled", "enabling", "endless", "engaged", "engaging", "enhanced", "enjoyed", "enormous", "enough", "epic", "equal", "equipped", "eternal", "ethical", "evident", "evolved", "evolving", "exact", "excited", "exciting", "exotic", "expert", "factual", "fair", "faithful", "famous", "fancy", "fast", "feasible", "fine", "finer", "firm", "first", "fit", "fitting", "fleet", "flexible", "flowing", "fluent", "flying", "fond", "frank", "free", "fresh", "full", "fun", "funny", "game", "generous", "gentle", "genuine", "giving", "glad", "glorious", "glowing", "golden", "good", "gorgeous", "grand", "grateful", "great", "growing", "grown", "guided", "guiding", "handy", "happy", "hardy", "harmless", "healthy", "helped", "helpful", "helping", "heroic", "hip", "holy", "honest", "hopeful", "hot", "huge", "humane", "humble", "humorous", "ideal", "immense", "immortal", "immune", "improved", "in", "included", "infinite", "informed", "innocent", "inspired", "integral", "intense", "intent", "internal", "intimate", "inviting", "joint", "just", "keen", "key", "kind", "knowing", "known", "large", "lasting", "leading", "learning", "legal", "legible", "lenient", "liberal", "light", "liked", "literate", "live", "living", "logical", "loved", "loving", "loyal", "lucky", "magical", "magnetic", "main", "major", "many", "massive", "master", "mature", "maximum", "measured", "meet", "merry", "mighty", "mint", "model", "modern", "modest", "moral", "more", "moved", "moving", "musical", "mutual", "national", "native", "natural", "nearby", "neat", "needed", "neutral", "new", "next", "nice", "noble", "normal", "notable", "noted", "novel", "obliging", "on", "one", "open", "optimal", "optimum", "organic", "oriented", "outgoing", "patient", "peaceful", "perfect", "pet", "picked", "pleasant", "pleased", "pleasing", "poetic", "polished", "polite", "popular", "positive", "possible", "powerful", "precious", "precise", "premium", "prepared", "present", "pretty", "primary", "prime", "pro", "probable", "profound", "promoted", "prompt", "proper", "proud", "proven", "pumped", "pure", "quality", "quick", "quiet", "rapid", "rare", "rational", "ready", "real", "refined", "regular", "related", "relative", "relaxed", "relaxing", "relevant", "relieved", "renewed", "renewing", "resolved", "rested", "rich", "right", "robust", "romantic", "ruling", "sacred", "safe", "saved", "saving", "secure", "select", "selected", "sensible", "set", "settled", "settling", "sharing", "sharp", "shining", "simple", "sincere", "singular", "skilled", "smart", "smashing", "smiling", "smooth", "social", "solid", "sought", "sound", "special", "splendid", "square", "stable", "star", "steady", "sterling", "still", "stirred", "stirring", "striking", "strong", "stunning", "subtle", "suitable", "suited", "summary", "sunny", "super", "superb", "supreme", "sure", "sweeping", "sweet", "talented", "teaching", "tender", "thankful", "thorough", "tidy", "tight", "together", "tolerant", "top", "topical", "tops", "touched", "touching", "tough", "true", "trusted", "trusting", "trusty", "ultimate", "unbiased", "uncommon", "unified", "unique", "united", "up", "upright", "upward", "usable", "useful", "valid", "valued", "vast", "verified", "viable", "vital", "vocal", "wanted", "warm", "wealthy", "welcome", "welcomed", "well", "whole", "willing", "winning", "wired", "wise", "witty", "wondrous", "workable", "working", "worthy"} + adverbs = [...]string{"abnormally", "absolutely", "accurately", "actively", "actually", "adequately", "admittedly", "adversely", "allegedly", "amazingly", "annually", "apparently", "arguably", "awfully", "badly", "barely", "basically", "blatantly", "blindly", "briefly", "brightly", "broadly", "carefully", "centrally", "certainly", "cheaply", "cleanly", "clearly", "closely", "commonly", "completely", "constantly", "conversely", "correctly", "curiously", "currently", "daily", "deadly", "deeply", "definitely", "directly", "distinctly", "duly", "eagerly", "early", "easily", "eminently", "endlessly", "enormously", "entirely", "equally", "especially", "evenly", "evidently", "exactly", "explicitly", "externally", "extremely", "factually", "fairly", "finally", "firmly", "firstly", "forcibly", "formally", "formerly", "frankly", "freely", "frequently", "friendly", "fully", "generally", "gently", "genuinely", "ghastly", "gladly", "globally", "gradually", "gratefully", "greatly", "grossly", "happily", "hardly", "heartily", "heavily", "hideously", "highly", "honestly", "hopefully", "hopelessly", "horribly", "hugely", "humbly", "ideally", "illegally", "immensely", "implicitly", "incredibly", "indirectly", "infinitely", "informally", "inherently", "initially", "instantly", "intensely", "internally", "jointly", "jolly", "kindly", "largely", "lately", "legally", "lightly", "likely", "literally", "lively", "locally", "logically", "loosely", "loudly", "lovely", "luckily", "mainly", "manually", "marginally", "mentally", "merely", "mildly", "miserably", "mistakenly", "moderately", "monthly", "morally", "mostly", "multiply", "mutually", "namely", "nationally", "naturally", "nearly", "neatly", "needlessly", "newly", "nicely", "nominally", "normally", "notably", "noticeably", "obviously", "oddly", "officially", "only", "openly", "optionally", "overly", "painfully", "partially", "partly", "perfectly", "personally", "physically", "plainly", "pleasantly", "poorly", "positively", "possibly", "precisely", "preferably", "presently", "presumably", "previously", "primarily", "privately", "probably", "promptly", "properly", "publicly", "purely", "quickly", "quietly", "radically", "randomly", "rapidly", "rarely", "rationally", "readily", "really", "reasonably", "recently", "regularly", "reliably", "remarkably", "remotely", "repeatedly", "rightly", "roughly", "routinely", "sadly", "safely", "scarcely", "secondly", "secretly", "seemingly", "sensibly", "separately", "seriously", "severely", "sharply", "shortly", "similarly", "simply", "sincerely", "singularly", "slightly", "slowly", "smoothly", "socially", "solely", "specially", "steadily", "strangely", "strictly", "strongly", "subtly", "suddenly", "suitably", "supposedly", "surely", "terminally", "terribly", "thankfully", "thoroughly", "tightly", "totally", "trivially", "truly", "typically", "ultimately", "unduly", "uniformly", "uniquely", "unlikely", "urgently", "usefully", "usually", "utterly", "vaguely", "vastly", "verbally", "vertically", "vigorously", "violently", "virtually", "visually", "weekly", "wholly", "widely", "wildly", "willingly", "wrongly", "yearly"} + names = [...]string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "gryphon", "haddock", "hagfish", "halibut", "hamster", "herring", "jackass", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crawfish", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "woodcock"} +) + +// Adverb returns a random adverb from a list of petname adverbs. +func Adverb() string { + return adverbs[rand.Intn(len(adverbs))] +} + +// Adjective returns a random adjective from a list of petname adjectives. +func Adjective() string { + return adjectives[rand.Intn(len(adjectives))] +} + +// Name returns a random name from a list of petname names. +func Name() string { + return names[rand.Intn(len(names))] +} + +// Generate generates and returns a random pet name. +// It takes two parameters: the number of words in the name, and a separator token. +// If a single word is requested, simply a Name() is returned. +// If two words are requested, a Adjective() and a Name() are returned. +// If three or more words are requested, a variable number of Adverb() and a Adjective and a Name() is returned. +// The separator can be any charater, string, or the empty string. +func Generate(words int, separator string) string { + if words == 1 { + return Name() + } else if words == 2 { + return Adjective() + separator + Name() + } + var petname []string + for i := 0; i < words-2; i++ { + petname = append(petname, Adverb()) + } + petname = append(petname, Adjective(), Name()) + return strings.Join(petname, separator) +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 9ff83c8187..adb1a873f1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1300,28 +1300,34 @@ "revisionTime": "2016-10-29T20:57:26Z" }, { - "checksumSHA1": "ADySw3nBHyzEHB6afBSeVRN2A4g=", + "checksumSHA1": "SdiAYZOqWQ60ifRUHLwLiDMKMYA=", "path": "github.com/denverdino/aliyungo/common", - "revision": "d123f5d1fa71b211b70b2e9b56a62da21076884a", - "revisionTime": "2017-01-17T10:57:15Z" + "revision": "c4c75afbf7ea86e66672c1b6ed981385b4ad5ec2", + "revisionTime": "2017-03-21T07:55:32Z" }, { - "checksumSHA1": "9ZY3RlumKp5DAMfL08YwMoOOT2o=", + "checksumSHA1": "UVYu5rvfoXgJnIpUyGcaovMvpms=", "path": "github.com/denverdino/aliyungo/ecs", - "revision": "d123f5d1fa71b211b70b2e9b56a62da21076884a", - "revisionTime": "2017-01-17T10:57:15Z" + "revision": "c4c75afbf7ea86e66672c1b6ed981385b4ad5ec2", + "revisionTime": "2017-03-21T07:55:32Z" }, { - "checksumSHA1": "QlA7zv05k7HWeR3tg4uHqIlFcg8=", + "checksumSHA1": "riQMe2AR7qkLRkQ/MSr8gQp3zL4=", + "path": "github.com/denverdino/aliyungo/rds", + "revision": "c4c75afbf7ea86e66672c1b6ed981385b4ad5ec2", + "revisionTime": "2017-03-21T07:55:32Z" + }, + { + "checksumSHA1": "2g6VZONB51rul5YuSBvngH6u4A0=", "path": "github.com/denverdino/aliyungo/slb", - "revision": "d123f5d1fa71b211b70b2e9b56a62da21076884a", - "revisionTime": "2017-01-17T10:57:15Z" + "revision": "c4c75afbf7ea86e66672c1b6ed981385b4ad5ec2", + "revisionTime": "2017-03-21T07:55:32Z" }, { "checksumSHA1": "Lp0KtT7ycgq31ox3Uzhpxyw0U+Y=", "path": "github.com/denverdino/aliyungo/util", - "revision": "d123f5d1fa71b211b70b2e9b56a62da21076884a", - "revisionTime": "2017-01-17T10:57:15Z" + "revision": "c4c75afbf7ea86e66672c1b6ed981385b4ad5ec2", + "revisionTime": "2017-03-21T07:55:32Z" }, { "checksumSHA1": "yDQQpeUxwqB3C+4opweg6znWJQk=", @@ -1397,6 +1403,12 @@ "revision": "7a41df006ff9af79a29f0ffa9c5f21fbe6314a2d", "revisionTime": "2017-01-10T07:11:07Z" }, + { + "checksumSHA1": "3Ue4yQFsolS+6tKtrSTtph7GJ74=", + "path": "github.com/dustinkirkland/golang-petname", + "revision": "242afa0b4f8af1fa581e7ea7f4b6d51735fa3fef", + "revisionTime": "2017-01-05T21:50:08Z" + }, { "checksumSHA1": "GCskdwYAPW2S34918Z5CgNMJ2Wc=", "path": "github.com/dylanmei/iso8601", diff --git a/website/config.rb b/website/config.rb index 6810a2abfc..6e2d25511f 100644 --- a/website/config.rb +++ b/website/config.rb @@ -2,7 +2,7 @@ set :base_url, "https://www.terraform.io/" activate :hashicorp do |h| h.name = "terraform" - h.version = "0.9.1" + h.version = "0.9.2" h.github_slug = "hashicorp/terraform" end diff --git a/website/source/assets/images/webinar-Terraform-4-4-2017.jpg b/website/source/assets/images/webinar-Terraform-4-4-2017.jpg new file mode 100644 index 0000000000..1bc50497c9 Binary files /dev/null and b/website/source/assets/images/webinar-Terraform-4-4-2017.jpg differ diff --git a/website/source/assets/images/webinar-Terraform-4-4-2017@2x.jpg b/website/source/assets/images/webinar-Terraform-4-4-2017@2x.jpg new file mode 100644 index 0000000000..17b3aa7250 Binary files /dev/null and b/website/source/assets/images/webinar-Terraform-4-4-2017@2x.jpg differ diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index f6bc3e3c8b..b101730a39 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -140,6 +140,8 @@ syntax `name(arg, arg2, ...)`. For example, to read a file: The supported built-in functions are: + * `basename(path)` - Returns the last element of a path. + * `base64decode(string)` - Given a base64-encoded string, decodes it and returns the original string. @@ -183,6 +185,8 @@ The supported built-in functions are: * `concat(list1, list2, ...)` - Combines two or more lists into a single list. Example: `concat(aws_instance.db.*.tags.Name, aws_instance.web.*.tags.Name)` + * `dirname(path)` - Returns all but the last element of path, typically the path's directory. + * `distinct(list)` - Removes duplicate items from a list. Keeps the first occurrence of each element, and removes subsequent occurrences. This function is only valid for flat lists. Example: `distinct(var.usernames)` diff --git a/website/source/docs/import/importability.html.md b/website/source/docs/import/importability.html.md index e1949f83f0..e4c50e2edb 100644 --- a/website/source/docs/import/importability.html.md +++ b/website/source/docs/import/importability.html.md @@ -65,6 +65,7 @@ To make a resource importable, please see the * aws_iam_instance_profile * aws_iam_role * aws_iam_saml_provider +* aws_iam_server_certificate * aws_iam_user * aws_instance * aws_internet_gateway diff --git a/website/source/docs/modules/create.html.markdown b/website/source/docs/modules/create.html.markdown index 231ad1c123..8320268d2f 100644 --- a/website/source/docs/modules/create.html.markdown +++ b/website/source/docs/modules/create.html.markdown @@ -83,7 +83,7 @@ Here we use `${path.module}` to get a module-relative path. ## Nested Modules -You can nest a module within another module. This module will be hidden from your root configuration, so you'll have re-expose any +You can nest a module within another module. This module will be hidden from your root configuration, so you'll have to re-expose any variables and outputs you require. The [get command](/docs/commands/get.html) will automatically get all nested modules. diff --git a/website/source/docs/providers/alicloud/r/db_instance.html.markdown b/website/source/docs/providers/alicloud/r/db_instance.html.markdown new file mode 100644 index 0000000000..7580f61e68 --- /dev/null +++ b/website/source/docs/providers/alicloud/r/db_instance.html.markdown @@ -0,0 +1,106 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_db_instance" +sidebar_current: "docs-alicloud-resource-db-instance" +description: |- + Provides an RDS instance resource. +--- + +# alicloud\_db\_instance + +Provides an RDS instance resource. A DB instance is an isolated database +environment in the cloud. A DB instance can contain multiple user-created +databases. + +## Example Usage + +``` +resource "alicloud_db_instance" "default" { + commodity_code = "rds" + engine = "MySQL" + engine_version = "5.6" + db_instance_class = "rds.mysql.t1.small" + db_instance_storage = "10" + db_instance_net_type = "Intranet" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `engine` - (Required) Database type. Value options: MySQL, SQLServer, PostgreSQL, and PPAS. +* `engine_version` - (Required) Database version. Value options: + - 5.5/5.6/5.7 for MySQL + - 2008r2/2012 for SQLServer + - 9.4 for PostgreSQL + - 9.3 for PPAS +* `db_instance_class` - (Required) Instance type. For details, see [Instance type table](https://intl.aliyun.com/help/doc-detail/26312.htm?spm=a3c0i.o26228en.a3.2.bRUHF3). +* `db_instance_storage` - (Required) User-defined storage space. Value range: + - [5, 2000] for MySQL/PostgreSQL/PPAS HA dual node edition; + - [20,1000] for MySQL 5.7 basic single node edition; + - [10, 2000] for SQL Server 2008R2; + - [20,2000] for SQL Server 2012 basic single node edition + Increase progressively at a rate of 5 GB. The unit is GB. For details, see [Instance type table](https://intl.aliyun.com/help/doc-detail/26312.htm?spm=a3c0i.o26228en.a3.3.bRUHF3). +* `instance_charge_type` - (Optional) Valid values are `Prepaid`, `Postpaid`, The default is `Postpaid`. +* `period` - (Optional) The time that you have bought the resource, in month. Only valid when instance_charge_type is set as `PrePaid`. Value range [1, 12]. +* `zone_id` - (Optional) Selected zone to create database instance. You cannot set the ZoneId parameter if the MultiAZ parameter is set to true. +* `multi_az` - (Optional) Specifies if the database instance is a multiple Availability Zone deployment. +* `db_instance_net_type` - (Optional) Network connection type of an instance. Internet: public network; Intranet: private network +* `allocate_public_connection` - (Optional) If set to true will applies for an Internet connection string of an instance. +* `instance_network_type` - (Optional) VPC: VPC instance; Classic: classic instance. If no value is specified, a classic instance will be created by default. +* `vswitch_id` - (Optional) The virtual switch ID to launch in VPC. If you want to create instances in VPC network, this parameter must be set. +* `master_user_name` - (Optional) The master user name for the database instance. Operation account requiring a uniqueness check. It may consist of lower case letters, numbers and underlines, and must start with a letter and have no more than 16 characters. +* `master_user_password` - (Optional) The master password for the database instance. Operation password. It may consist of letters, digits, or underlines, with a length of 6 to 32 characters. +* `preferred_backup_period` - (Optional) Backup period. Values: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, and Sunday. +* `preferred_backup_time` - (Optional) Backup time, in the format ofHH:mmZ- HH:mm Z. +* `backup_retention_period` - (Optional) Retention days of the backup (7 to 730 days). The default value is 7 days. +* `security_ips` - (Optional) List of IP addresses under the IP address white list array. The list contains up to 1,000 IP addresses, separated by commas. Supported formats include 0.0.0.0/0, 10.23.12.24 (IP), and 10.23.12.24/24 (Classless Inter-Domain Routing (CIDR) mode. /24 represents the length of the prefix in an IP address. The range of the prefix length is [1,32]). +* `db_mappings` - (Optional) Database mappings to attach to db instance. See [Block database](#block-database) below for details. + + +## Block database + +The database mapping supports the following: + +* `db_name` - (Required) Name of the database requiring a uniqueness check. It may consist of lower case letters, numbers and underlines, and must start with a letter and have no more than 64 characters. +* `character_set_name` - (Required) Character set. The value range is limited to the following: + - MySQL type: + + utf8 + + gbk + + latin1 + + utf8mb4 (included in versions 5.5 and 5.6). + - SQLServer type: + + Chinese_PRC_CI_AS + + Chinese_PRC_CS_AS + + SQL_Latin1_General_CP1_CI_AS + + SQL_Latin1_General_CP1_CS_AS + + Chinese_PRC_BIN +* `db_description` - (Optional) Database description, which cannot exceed 256 characters. NOTE: It cannot begin with https://. + + +~> **NOTE:** We neither support modify any of database attribute, nor insert/remove item at the same time. +We recommend split to two separate operations. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The RDS instance ID. +* `instance_charge_type` - The instance charge type. +* `period` - The time that you have bought the resource. +* `engine` - Database type. +* `engine_version` - The database engine version. +* `db_instance_class` - The RDS instance class. +* `db_instance_storage` - The amount of allocated storage. +* `port` - The database port. +* `zone_id` - The zone ID of the DB instance. +* `db_instance_net_type` - Network connection type of an instance, `Internet` or `Intranet`. +* `instance_network_type` - The instance network type and it has two values: `vpc` and `classic`. +* `db_mappings` - Database mappings attached to db instance. +* `preferred_backup_period` - Backup period. +* `preferred_backup_time` - Backup time. +* `backup_retention_period` - Retention days of the backup. +* `security_ips` - Security ips of instance whitelist. +* `connections` - Views all the connection information of a specified instance. + diff --git a/website/source/docs/providers/alicloud/r/instance.html.markdown b/website/source/docs/providers/alicloud/r/instance.html.markdown index 2d94fff929..f01b42964f 100644 --- a/website/source/docs/providers/alicloud/r/instance.html.markdown +++ b/website/source/docs/providers/alicloud/r/instance.html.markdown @@ -54,22 +54,20 @@ resource "alicloud_slb" "vpc" { The following arguments are supported: -* `availability_zone` - (Required) The Zone to start the instance in. * `image_id` - (Required) The Image to use for the instance. * `instance_type` - (Required) The type of instance to start. -* `security_group_ids` - (Required) A list of security group ids to associate with. If you are creating Instances in a VPC, use `vpc_security_group_ids` instead. -`security_group_ids` instead. -* `instance_name` - (Optional) The name of the ECS. This instance_name can have a string of 2 to 128 characters, must contain only alphanumeric characters or hyphens, such as "-",".","_", and must not begin or end with a hyphen, and must not begin with http:// or https://. If not specified, -Terraform will autogenerate a name beginning with `tf-ecs`. +* `io_optimized` - (Required) Valid values are `none`, `optimized`, If `optimized`, the launched ECS instance will be I/O optimized. +* `security_group_ids` - (Optional) A list of security group ids to associate with. +* `availability_zone` - (Optional) The Zone to start the instance in. +* `instance_name` - (Optional) The name of the ECS. This instance_name can have a string of 2 to 128 characters, must contain only alphanumeric characters or hyphens, such as "-",".","_", and must not begin or end with a hyphen, and must not begin with http:// or https://. If not specified, +Terraform will autogenerate a default name is `ECS-Instance`. * `allocate_public_ip` - (Optional) Associate a public ip address with an instance in a VPC or Classic. Boolean value, Default is false. -* `io_optimized` - (Optional) Valid - values are `none`, `optimized`, If `optimized`, the launched ECS instance will be I/O optimized. Default is `optimized`. -* `system_disk_category` - (Optional) Valid values are `cloud`, `cloud_efficiency`, `cloud_ssd`, For I/O optimized instance type, `cloud_ssd` and `cloud_efficiency` disks are supported. For non I/O Optimized instance type, `cloud` disk are supported. +* `system_disk_category` - (Optional) Valid values are `cloud`, `cloud_efficiency`, `cloud_ssd`, For I/O optimized instance type, `cloud_ssd` and `cloud_efficiency` disks are supported. For non I/O Optimized instance type, `cloud` disk are supported. * `system_disk_size` - (Optional) Size of the system disk, value range: 40GB ~ 500GB. Default is 40GB. * `description` - (Optional) Description of the instance, This description can have a string of 2 to 256 characters, It cannot begin with http:// or https://. Default value is null. * `internet_charge_type` - (Optional) Internet charge type of the instance, Valid values are `PayByBandwidth`, `PayByTraffic`. Default is `PayByBandwidth`. * `internet_max_bandwidth_in` - (Optional) Maximum incoming bandwidth from the public network, measured in Mbps (Mega bit per second). Value range: [1, 200]. If this value is not specified, then automatically sets it to 200 Mbps. -* `internet_max_bandwidth_out` - (Optional) Maximum outgoing bandwidth to the public network, measured in Mbps (Mega bit per second). Value range: +* `internet_max_bandwidth_out` - (Optional) Maximum outgoing bandwidth to the public network, measured in Mbps (Mega bit per second). Value range: `internet_charge_type` is `PayByBandwidth`: this value range [0, 100], If this value is not specified, then automatically sets it to 0 Mbps; If `internet_charge_type` is `PayByTraffic`: this value range [1, 100]. this value must be set value, such as 5. * `host_name` - (Optional) Host name of the ECS, which is a string of at least two characters. “hostname” cannot start or end with “.” or “-“. In addition, two or more consecutive “.” or “-“ symbols are not allowed. On Windows, the host name can contain a maximum of 15 characters, which can be a combination of uppercase/lowercase letters, numerals, and “-“. The host name cannot contain dots (“.”) or contain only numeric characters. On other OSs such as Linux, the host name can contain a maximum of 30 characters, which can be segments separated by dots (“.”), where each segment can contain uppercase/lowercase letters, numerals, or “_“. @@ -77,7 +75,6 @@ On other OSs such as Linux, the host name can contain a maximum of 30 characters * `vswitch_id` - (Optional) The virtual switch ID to launch in VPC. If you want to create instances in VPC network, this parameter must be set. * `instance_charge_type` - (Optional) Valid values are `PrePaid`, `PostPaid`, The default is `PostPaid`. * `period` - (Optional) The time that you have bought the resource, in month. Only valid when instance_charge_type is set as `PrePaid`. Value range [1, 12]. -* `private_ip` - (Optional) Private IP address to associate with the instance in a VPC. * `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference diff --git a/website/source/docs/providers/aws/d/cloudformation_stack.html.markdown b/website/source/docs/providers/aws/d/cloudformation_stack.html.markdown index 6dd36f379c..2073abbb0a 100644 --- a/website/source/docs/providers/aws/d/cloudformation_stack.html.markdown +++ b/website/source/docs/providers/aws/d/cloudformation_stack.html.markdown @@ -47,4 +47,5 @@ The following attributes are exported: * `parameters` - A map of parameters that specify input parameters for the stack. * `tags` - A map of tags associated with this stack. * `template_body` - Structure containing the template body. +* `iam_role_arn` - The ARN of the IAM role used to create the stack. * `timeout_in_minutes` - The amount of time that can pass before the stack status becomes `CREATE_FAILED` diff --git a/website/source/docs/providers/aws/d/iam_server_certificate.html.markdown b/website/source/docs/providers/aws/d/iam_server_certificate.html.markdown index 0c8f8828ba..ab690dcf46 100644 --- a/website/source/docs/providers/aws/d/iam_server_certificate.html.markdown +++ b/website/source/docs/providers/aws/d/iam_server_certificate.html.markdown @@ -42,3 +42,10 @@ resource "aws_elb" "elb" { `arn` is set to the ARN of the IAM Server Certificate `path` is set to the path of the IAM Server Certificate `expiration_date` is set to the expiration date of the IAM Server Certificate + +## Import + +The terraform import function will read in certificate body, certificate chain (if it exists), id, name, path, and arn. +It will not retrieve the private key which is not available through the AWS API. + + diff --git a/website/source/docs/providers/aws/r/alb_listener_rule.html.markdown b/website/source/docs/providers/aws/r/alb_listener_rule.html.markdown index 6cf918e52f..c2393e671d 100644 --- a/website/source/docs/providers/aws/r/alb_listener_rule.html.markdown +++ b/website/source/docs/providers/aws/r/alb_listener_rule.html.markdown @@ -55,7 +55,7 @@ Action Blocks (for `default_action`) support the following: Condition Blocks (for `default_condition`) support the following: * `field` - (Required) The name of the field. The only valid value is `path-pattern`. -* `values` - (Required) The path patterns to match. +* `values` - (Required) The path patterns to match. A maximum of 1 can be defined. ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/cloudformation_stack.html.markdown b/website/source/docs/providers/aws/r/cloudformation_stack.html.markdown index 6251a8cea5..9ab1a8a197 100644 --- a/website/source/docs/providers/aws/r/cloudformation_stack.html.markdown +++ b/website/source/docs/providers/aws/r/cloudformation_stack.html.markdown @@ -65,6 +65,7 @@ The following arguments are supported: * `policy_url` - (Optional) Location of a file containing the stack policy. Conflicts w/ `policy_body`. * `tags` - (Optional) A list of tags to associate with this stack. +* `iam_role_arn` - (Optional) The ARN of an IAM role that AWS CloudFormation assumes to create the stack. If you don't specify a value, AWS CloudFormation uses the role that was previously associated with the stack. If no role is available, AWS CloudFormation uses a temporary session that is generated from your user credentials. * `timeout_in_minutes` - (Optional) The amount of time that can pass before the stack status becomes `CREATE_FAILED`. ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown b/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown index 73b66e6dfd..9db9be4851 100644 --- a/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown +++ b/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown @@ -10,6 +10,8 @@ description: |- Provides an IAM instance profile. +~> **NOTE:** Either `roles` or `role` must be specified in the IAM Instance Profile. + ## Example Usage ``` @@ -47,7 +49,8 @@ The following arguments are supported: * `name` - (Optional, Forces new resource) The profile's name. If omitted, Terraform will assign a random, unique name. * `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`. * `path` - (Optional, default "/") Path in which to create the profile. -* `roles` - (Required) A list of role names to include in the profile. The current default is 1. If you see an error message similar to `Cannot exceed quota for InstanceSessionsPerInstanceProfile: 1`, then you must contact AWS support and ask for a limit increase. +* `roles` - (Optional) A list of role names to include in the profile. The current default is 1. If you see an error message similar to `Cannot exceed quota for InstanceSessionsPerInstanceProfile: 1`, then you must contact AWS support and ask for a limit increase. `WARNING: This will be deprecated in a future version of Terraform`. +* `role` - (Optional) The role name to include in the profile. This. ## Attribute Reference @@ -57,6 +60,7 @@ The following arguments are supported: * `name` - The instance profile's name. * `path` - The path of the instance profile in IAM. * `roles` - The list of roles assigned to the instance profile. +* `role` - The role assigned to the instance profile * `unique_id` - The [unique ID][1] assigned by AWS. [1]: https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html#GUIDs diff --git a/website/source/docs/providers/aws/r/iam_policy_attachment.html.markdown b/website/source/docs/providers/aws/r/iam_policy_attachment.html.markdown index 8de02b51c6..f4034dd6ca 100644 --- a/website/source/docs/providers/aws/r/iam_policy_attachment.html.markdown +++ b/website/source/docs/providers/aws/r/iam_policy_attachment.html.markdown @@ -33,9 +33,9 @@ resource "aws_iam_policy" "policy" { resource "aws_iam_policy_attachment" "test-attach" { name = "test-attachment" - users = ["{aws_iam_user.user.name}"] - roles = ["{aws_iam_role.role.name}"] - groups = ["{aws_iam_group.group.name}"] + users = ["${aws_iam_user.user.name}"] + roles = ["${aws_iam_role.role.name}"] + groups = ["${aws_iam_group.group.name}"] policy_arn = "${aws_iam_policy.policy.arn}" } ``` diff --git a/website/source/docs/providers/azurerm/r/loadbalancer_nat_pool.html.markdown b/website/source/docs/providers/azurerm/r/loadbalancer_nat_pool.html.markdown index be68096f3a..e4740694bc 100644 --- a/website/source/docs/providers/azurerm/r/loadbalancer_nat_pool.html.markdown +++ b/website/source/docs/providers/azurerm/r/loadbalancer_nat_pool.html.markdown @@ -39,7 +39,6 @@ resource "azurerm_lb" "test" { } resource "azurerm_lb_nat_pool" "test" { - location = "West US" resource_group_name = "${azurerm_resource_group.test.name}" loadbalancer_id = "${azurerm_lb.test.id}" name = "SampleApplication Pool" @@ -57,7 +56,6 @@ The following arguments are supported: * `name` - (Required) Specifies the name of the NAT pool. * `resource_group_name` - (Required) The name of the resource group in which to create the resource. -* `location` - (Required) Specifies the supported Azure location where the resource exists. * `loadbalancer_id` - (Required) The ID of the LoadBalancer in which to create the NAT pool. * `frontend_ip_configuration_name` - (Required) The name of the frontend IP configuration exposing this rule. * `protocol` - (Required) The transport protocol for the external endpoint. Possible values are `Udp` or `Tcp`. diff --git a/website/source/docs/providers/circonus/r/check.html.markdown b/website/source/docs/providers/circonus/r/check.html.markdown index 9c1787cc91..e83d6245f4 100644 --- a/website/source/docs/providers/circonus/r/check.html.markdown +++ b/website/source/docs/providers/circonus/r/check.html.markdown @@ -88,6 +88,9 @@ resource "circonus_metric" "used" { enterprise collector running in your datacenter. One collection of metrics will be automatically created for each `collector` specified. +* `consul` - (Optional) A native Consul check. See below for details on how to + configure a `consul` check. + * `http` - (Optional) A poll-based HTTP check. See below for details on how to configure the `http` check. @@ -249,6 +252,140 @@ resource "circonus_check" "rds_metrics" { } ``` +### `consul` Check Type Attributes + +* `acl_token` - (Optional) An ACL Token authenticate the API request. When an + ACL Token is set, this value is transmitted as an HTTP Header in order to not + show up in any logs. The default value is an empty string. + +* `allow_stale` - (Optional) A boolean value that indicates whether or not this + check should require the health information come from the Consul leader node. + For scalability reasons, this value defaults to `false`. See below for + details on detecting the staleness of health information. + +* `ca_chain` - (Optional) A path to a file containing all the certificate + authorities that should be loaded to validate the remote certificate (required + when `http_addr` is a TLS-enabled endpoint). + +* `certificate_file` - (Optional) A path to a file containing the client + certificate that will be presented to the remote server (required when + `http_addr` is a TLS-enabled endpoint). + +* `check_blacklist` - (Optional) A list of check names to exclude from the + result of checks (i.e. no metrics will be generated by whose check name is in + the `check_blacklist`). This blacklist is applied to the `node`, + `service`, and `state` check modes. + +* `ciphers` - (Optional) A list of ciphers to be used in the TLS protocol + (only used when `http_addr` is a TLS-enabled endpoint). + +* `dc` - (Optional) Explicitly name the Consul datacenter to use. The default + value is an empty string. When an empty value is specified, the Consul + datacenter of the agent at the `http_addr` is implicitly used. + +* `headers` - (Optional) A map of the HTTP headers to be sent when executing the + check. NOTE: the `headers` attribute is processed last and will takes + precidence over any other derived value that is transmitted as an HTTP header + to Consul (i.e. it is possible to override the `acl_token` by setting a + headers value). + +* `http_addr` - (Optional) The Consul HTTP endpoint to to query for health + information. The default value is `http://consul.service.consul:8500`. The + scheme must change from `http` to `https` when the endpoint has been + TLS-enabled. + +* `key_file` - (Optional) A path to a file containing key to be used in + conjunction with the cilent certificate (required when `http_addr` is a + TLS-enabled endpoint). + +* `node` - (Optional) Check the health of this node. The value can be either a + Consul Node ID (Consul Version >= 0.7.4) or Node Name. See also the + `service_blacklist`, `node_blacklist`, and `check_blacklist` attributes. This + attribute conflicts with the `service` and `state` attributes. + +* `node_blacklist` - (Optional) A list of node IDs or node names to exclude from + the results of checks (i.e. no metrics will be generated from nodes in the + `node_blacklist`). This blacklist is applied to the `node`, `service`, and + `state` check modes. + +* `service` - (Optional) Check the cluster-wide health of this named service. + See also the `service_blacklist`, `node_blacklist`, and `check_blacklist` + attributes. This attribute conflicts with the `node` and `state` attributes. + +* `service_blacklist` - (Optional) A list of service names to exclude from the + result of checks (i.e. no metrics will be generated by services whose service + name is in the `service_blacklist`). This blacklist is applied to the `node`, + `service`, and `state` check modes. + +* `state` - (Optional) A Circonus check to monitor Consul checks across the + entire Consul cluster. This value may be either `passing`, `warning`, or + `critical`. This `consul` check mode is intended to act as the cluster check + of last resort. This check type is useful when first starting and is intended + to act as a check of last resort before transitioning to explicitly defined + checks for individual services or nodes. The metrics returned from check will + be sorted based on the `CreateIndex` of the entry in order to have a stable + set of metrics in the array of returned values. See also the + `service_blacklist`, `node_blacklist`, and `check_blacklist` attributes. This + attribute conflicts with the `node` and `state` attributes. + +Available metrics depend on the consul check being performed (`node`, `service`, +or `state`). In addition to the data avilable from the endpoints, the `consul` +check also returns a set of metrics that are a variant of: +`{Num,Pct}{,Passing,Warning,Critical}{Checks,Nodes,Services}` (see the +`GLOB_BRACE` section of your local `glob(3)` documentation). + +Example Consul check (partial metrics collection): + +``` +resource "circonus_check" "consul_server" { + active = true + name = "%s" + period = "60s" + + collector { + # Collector ID must be an Enterprise broker able to reach the Consul agent + # listed in `http_addr`. + id = "/broker/2110" + } + + consul { + service = "consul" + + # Other consul check modes: + # node = "consul1" + # state = "critical" + } + + metric { + name = "NumNodes" + tags = [ "source:consul", "lifecycle:unittest" ] + type = "numeric" + } + + metric { + name = "LastContact" + tags = [ "source:consul", "lifecycle:unittest" ] + type = "numeric" + unit = "seconds" + } + + metric { + name = "Index" + tags = [ "source:consul", "lifecycle:unittest" ] + type = "numeric" + unit = "transactions" + } + + metric { + name = "KnownLeader" + tags = [ "source:consul", "lifecycle:unittest" ] + type = "text" + } + + tags = [ "source:consul", "lifecycle:unittest" ] +} +``` + ### `http` Check Type Attributes * `auth_method` - (Optional) HTTP Authentication method to use. When set must diff --git a/website/source/docs/providers/google/r/compute_network.html.markdown b/website/source/docs/providers/google/r/compute_network.html.markdown index 7cea11ad2b..cd03ad2bb4 100644 --- a/website/source/docs/providers/google/r/compute_network.html.markdown +++ b/website/source/docs/providers/google/r/compute_network.html.markdown @@ -53,4 +53,6 @@ exported: * `gateway_ipv4` - The IPv4 address of the gateway. +* `name` - The unique name of the network. + * `self_link` - The URI of the created resource. diff --git a/website/source/docs/providers/google/r/google_project.html.markdown b/website/source/docs/providers/google/r/google_project.html.markdown index ad668b6725..b90af0aa2f 100755 --- a/website/source/docs/providers/google/r/google_project.html.markdown +++ b/website/source/docs/providers/google/r/google_project.html.markdown @@ -12,7 +12,7 @@ Allows creation and management of a Google Cloud Platform project and its associated enabled services/APIs. Projects created with this resource must be associated with an Organization. -See the [Organization documentation](https://cloud.google.com/resource-manager/docs/quickstart) for more details. +See the [Organization documentation](https://cloud.google.com/resource-manager/docs/quickstarts) for more details. The service account used to run Terraform when creating a `google_project` resource must have `roles/resourcemanager.projectCreator`. See the diff --git a/website/source/docs/providers/ns1/r/record.html.markdown b/website/source/docs/providers/ns1/r/record.html.markdown index 88f84cf40c..05b2bde50c 100644 --- a/website/source/docs/providers/ns1/r/record.html.markdown +++ b/website/source/docs/providers/ns1/r/record.html.markdown @@ -18,7 +18,7 @@ resource "ns1_zone" "tld" { } resource "ns1_record" "www" { - zone = "${ns1_zone.tld.id}" + zone = "${ns1_zone.tld.zone}" domain = "www.${ns1_zone.tld.zone}" type = "CNAME" ttl = 60 diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 1337902377..7466b4301f 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -35,13 +35,13 @@ resource "openstack_compute_instance_v2" "basic" { ### Instance With Attached Volume ``` -resource "openstack_blockstorage_volume_v1" "myvol" { +resource "openstack_blockstorage_volume_v2" "myvol" { name = "myvol" size = 1 } -resource "openstack_compute_instance_v2" "volume-attached" { - name = "volume-attached" +resource "openstack_compute_instance_v2" "myinstance" { + name = "myinstance" image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743" flavor_id = "3" key_pair = "my_key_pair_name" @@ -50,10 +50,11 @@ resource "openstack_compute_instance_v2" "volume-attached" { network { name = "my_network" } +} - volume { - volume_id = "${openstack_blockstorage_volume_v1.myvol.id}" - } +resource "openstack_compute_volume_attach_v2" "attached" { + compute_id = "${openstack_compute_instance_v2.myinstance.id}" + volume_id = "${openstack_blockstorage_volume_v2.myvol.id}" } ``` @@ -174,7 +175,7 @@ resource "openstack_compute_instance_v2" "instance_1" { ### Instance With Multiple Networks ``` -resource "openstack_compute_floatingip_v2" "myip" { +resource "openstack_networking_floatingip_v2" "myip" { pool = "my_pool" } @@ -190,13 +191,15 @@ resource "openstack_compute_instance_v2" "multi-net" { } network { - name = "my_second_network" - floating_ip = "${openstack_compute_floatingip_v2.myip.address}" - - # Terraform will use this network for provisioning - access_network = true + name = "my_second_network" } } + +resource "openstack_compute_floatingip_associate_v2" "myip" { + floating_ip = "${openstack_networking_floatingip_v2.myip.address}" + instance_id = "${openstack_compute_instance_v2.multi-net.id}" + fixed_ip = "${openstack_compute_instance_v2.multi-net.network.1.fixed_ip_v4}" +} ``` ### Instance With Personality @@ -280,7 +283,7 @@ The following arguments are supported: * `flavor_name` - (Optional; Required if `flavor_id` is empty) The name of the desired flavor for the server. Changing this resizes the existing server. -* `floating_ip` - (Optional) A *Compute* Floating IP that will be associated +* `floating_ip` - (Deprecated) A *Compute* Floating IP that will be associated with the Instance. The Floating IP must be provisioned already. See *Notes* for more information about Floating IPs. @@ -320,7 +323,7 @@ The following arguments are supported: following [reference](http://docs.openstack.org/developer/nova/block_device_mapping.html) for more information. -* `volume` - (Optional) Attach an existing volume to the instance. The volume +* `volume` - (Deprecated) Attach an existing volume to the instance. The volume structure is described below. *Note*: This is no longer the recommended method of attaching a volume to an instance. Please see `block_device` (above) or the `openstack_compute_volume_attach_v2` and @@ -359,7 +362,7 @@ The `network` block supports: * `fixed_ip_v6` - (Optional) Specifies a fixed IPv6 address to be used on this network. Changing this creates a new server. -* `floating_ip` - (Optional) Specifies a floating IP address to be associated +* `floating_ip` - (Deprecated) Specifies a floating IP address to be associated with this network. Cannot be combined with a top-level floating IP. See *Notes* for more information about Floating IPs. @@ -446,11 +449,17 @@ The following attributes are exported: * `network/floating_ip` - The Floating IP address of the Instance on that network. * `network/mac` - The MAC address of the NIC on that network. +* `all_metadata` - Contains all instance metadata, even metadata not set + by Terraform. ## Notes ### Floating IPs +Specifying Floating IPs within the instance is now deprecated. Please use +either the `openstack_compute_floatingip_associate_v2` resource or attach +the floating IP to an `openstack_networking_port_v2` resource. + Floating IPs can be associated in one of two ways: * You can specify a Floating IP address by using the top-level `floating_ip` diff --git a/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown index 54a92fb78d..9cb62ac40f 100644 --- a/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_port_v2.html.markdown @@ -95,7 +95,9 @@ The following attributes are exported: * `device_owner` - See Argument Reference above. * `security_group_ids` - See Argument Reference above. * `device_id` - See Argument Reference above. -* `fixed_ip/ip_address` - See Argument Reference above. +* `fixed_ip` - See Argument Reference above. +* `all fixed_ips` - The collection of Fixed IP addresses on the port in the + order returned by the Network v2 API. ## Import diff --git a/website/source/docs/providers/rancher/r/environment.html.md b/website/source/docs/providers/rancher/r/environment.html.md index 498ebda087..cb4c1b8f68 100644 --- a/website/source/docs/providers/rancher/r/environment.html.md +++ b/website/source/docs/providers/rancher/r/environment.html.md @@ -31,7 +31,7 @@ The following arguments are supported: ## Attributes Reference -No further attributes are exported. +* `id` - The ID of the environment (ie `1a11`) that can be used in other Terraform resources such as Rancher Stack definitions. ## Import diff --git a/website/source/docs/providers/random/r/pet.html.md b/website/source/docs/providers/random/r/pet.html.md new file mode 100644 index 0000000000..86b457a7ab --- /dev/null +++ b/website/source/docs/providers/random/r/pet.html.md @@ -0,0 +1,60 @@ +--- +layout: "random" +page_title: "Random: random_pet" +sidebar_current: "docs-random-resource-pet" +description: |- + Generates a random pet. +--- + +# random\_pet + +The resource `random_pet` generates random pet names that are intended to be +used as unique identifiers for other resources. + +This resource can be used in conjunction with resources that have +the `create_before_destroy` lifecycle flag set, to avoid conflicts with +unique names during the brief period where both the old and new resources +exist concurrently. + +## Example Usage + +The following example shows how to generate a unique pet name for an AWS EC2 +instance that changes each time a new AMI id is selected. + +``` +resource "random_pet" "server" { + keepers = { + # Generate a new pet name each time we switch to a new AMI id + ami_id = "${var.ami_id}" + } +} + +resource "aws_instance" "server" { + tags = { + Name = "web-server-${random_pet.server.id}" + } + + # Read the AMI id "through" the random_pet resource to ensure that + # both will change together. + ami = "${random_pet.server.keepers.ami_id}" + + # ... (other aws_instance arguments) ... +} +``` + +The result of the above will set the Name of the AWS Instance to +`web-server-simple-snake`. + +## Argument Reference + +The following arguments are supported: + +* `keepers` - (Optional) Arbitrary map of values that, when changed, will + trigger a new id to be generated. See + [the main provider documentation](../index.html) for more information. + +* `length` - (Optional) The length (in words) of the pet name. + +* `prefix` - (Optional) A string to prefix the name with. + +* `separator` - (Optional) The character to separate words in the pet name. diff --git a/website/source/docs/state/environments.html.md b/website/source/docs/state/environments.html.md index 205a9ac08a..e4a5026095 100644 --- a/website/source/docs/state/environments.html.md +++ b/website/source/docs/state/environments.html.md @@ -20,6 +20,11 @@ Environments are a way to create multiple states that contain their own data so a single set of Terraform configurations can manage multiple distinct sets of resources. +Environments are currently supported by the following backends: + + * [Consul](/docs/backends/types/consul.html) + * [S3](/docs/backends/types/s3.html) + ## Using Environments Terraform starts with a single environment named "default". This @@ -120,7 +125,9 @@ For local state, Terraform stores the state environments in a folder For [remote state](/docs/state/remote.html), the environments are stored directly in the configured [backend](/docs/backends). For example, if you use [Consul](/docs/backends/types/consul.html), the environments are stored -by suffixing the state path with the environment name. +by suffixing the state path with the environment name. To ensure that +environment names are stored correctly and safely in all backends, the name +must be valid to use in a URL path segment without escaping. The important thing about environment internals is that environments are meant to be a shared resource. They aren't a private, local-only notion diff --git a/website/source/docs/state/sensitive-data.html.md b/website/source/docs/state/sensitive-data.html.md new file mode 100644 index 0000000000..acd26f8ad7 --- /dev/null +++ b/website/source/docs/state/sensitive-data.html.md @@ -0,0 +1,52 @@ +--- +layout: "docs" +page_title: "State: Sensitive Data" +sidebar_current: "docs-state-sensitive-data" +description: |- + Sensitive data in Terraform state. +--- + +# Sensitive Data in State + +Terraform state can contain sensitive data depending on the resources in-use +and your definition of "sensitive." The state contains resource IDs and all +resource attributes. For resources such as databases, this may contain initial +passwords. + +Some resources (such as RDS databases) have options for PGP encrypting the +values within the state. This is implemented on a per-resource basis and +you should assume the value is plaintext unless otherwise documented. + +When using local state, state is stored in plain-text JSON files. When +using [remote state](/docs/state/remote.html), state is only ever held in memory when used by Terraform. +It may be encrypted at rest but this depends on the specific remote state +backend. + +It is important to keep this in mind if you do (or plan to) store sensitive +data (e.g. database passwords, user passwords, private keys) as it may affect +the risk of exposure of such sensitive data. + +## Recommendations + +Storing state remotely may provide you encryption at rest depending on the +backend you choose. As of Terraform 0.9, Terraform will only hold the state +value in memory when remote state is in use. It is never explicitly persisted +to disk. + +For example, encryption at rest can be enabled with the S3 backend and IAM +policies and logging can be used to identify any invalid access. Requests for +the state go over a TLS connection. + +[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) is +a commercial product from HashiCorp that also acts as a [backend](/docs/backends) +and provides encryption at rest for state. Terraform Enterprise also knows +the identity of the user requesting state and maintains a history of state +changes. This can be used to provide access control and detect any breaches. + +## Future Work + +Long term, the Terraform project wants to further improve the ability to +secure sensitive data. There are plans to provide a +generic mechanism for specific state attributes to be encrypted or even +completely omitted from the state. These do not exist yet except on a +resource-by-resource basis if documented. diff --git a/website/source/index.html.erb b/website/source/index.html.erb index 34f99a2f40..1e911d17b9 100644 --- a/website/source/index.html.erb +++ b/website/source/index.html.erb @@ -169,7 +169,16 @@

Latest

-
+
+
+ +

Join us for a live webinar with Mitchell Hashimoto to learn how Terraform provisions infrastructure across different clouds using a consistent workflow.

+

+ Register Now +

+
+
+

Terraform 0.9 Released

diff --git a/website/source/intro/getting-started/build.html.md b/website/source/intro/getting-started/build.html.md index ac278cb71a..67099c7c0d 100644 --- a/website/source/intro/getting-started/build.html.md +++ b/website/source/intro/getting-started/build.html.md @@ -59,7 +59,7 @@ provider "aws" { } resource "aws_instance" "example" { - ami = "ami-0d729a60" + ami = "ami-2757f631" instance_type = "t2.micro" } ``` @@ -125,7 +125,7 @@ $ terraform plan ... + aws_instance.example - ami: "ami-0d729a60" + ami: "ami-2757f631" availability_zone: "" ebs_block_device.#: "" ephemeral_block_device.#: "" @@ -170,7 +170,7 @@ since Terraform waits for the EC2 instance to become available. ``` $ terraform apply aws_instance.example: Creating... - ami: "" => "ami-0d729a60" + ami: "" => "ami-2757f631" instance_type: "" => "t2.micro" [...] @@ -201,7 +201,7 @@ You can inspect the state using `terraform show`: $ terraform show aws_instance.example: id = i-32cf65a8 - ami = ami-0d729a60 + ami = ami-2757f631 availability_zone = us-east-1a instance_state = running instance_type = t2.micro diff --git a/website/source/intro/getting-started/change.html.md b/website/source/intro/getting-started/change.html.md index 20fb9ce84e..6f9ea6f505 100644 --- a/website/source/intro/getting-started/change.html.md +++ b/website/source/intro/getting-started/change.html.md @@ -23,20 +23,20 @@ can see how the infrastructure evolved over time. ## Configuration -Let's modify the `ami` of our instance. Edit the "aws\_instance.example" +Let's modify the `ami` of our instance. Edit the `aws_instance.example` resource in your configuration and change it to the following: ``` resource "aws_instance" "example" { - ami = "ami-13be557e" + ami = "ami-b374d5a5" instance_type = "t2.micro" } ``` ~> **Note:** EC2 Classic users please use AMI `ami-656be372` and type `t1.micro` -We've changed the AMI from being an Ubuntu 14.04 LTS AMI to being -an Ubuntu 16.04 LTS AMI. Terraform configurations are meant to be +We've changed the AMI from being an Ubuntu 16.04 LTS AMI to being +an Ubuntu 16.10 AMI. Terraform configurations are meant to be changed like this. You can also completely remove resources and Terraform will know to destroy the old one. @@ -49,7 +49,7 @@ $ terraform plan ... -/+ aws_instance.example - ami: "ami-0d729a60" => "ami-13be557e" (forces new resource) + ami: "ami-2757f631" => "ami-b374d5a5" (forces new resource) availability_zone: "us-east-1a" => "" ebs_block_device.#: "0" => "" ephemeral_block_device.#: "0" => "" @@ -86,7 +86,7 @@ aws_instance.example: Refreshing state... (ID: i-64c268fe) aws_instance.example: Destroying... aws_instance.example: Destruction complete aws_instance.example: Creating... - ami: "" => "ami-13be557e" + ami: "" => "ami-b374d5a5" availability_zone: "" => "" ebs_block_device.#: "" => "" ephemeral_block_device.#: "" => "" diff --git a/website/source/intro/getting-started/dependencies.html.md b/website/source/intro/getting-started/dependencies.html.md index 7254e9f23b..2e0e4b47f5 100644 --- a/website/source/intro/getting-started/dependencies.html.md +++ b/website/source/intro/getting-started/dependencies.html.md @@ -70,7 +70,7 @@ $ terraform plan public_ip: "" + aws_instance.example - ami: "ami-13be557e" + ami: "ami-b374d5a5" availability_zone: "" ebs_block_device.#: "" ephemeral_block_device.#: "" @@ -102,7 +102,7 @@ following: ``` $ terraform apply aws_instance.example: Creating... - ami: "" => "ami-13be557e" + ami: "" => "ami-b374d5a5" instance_type: "" => "t2.micro" [..] aws_instance.example: Still creating... (10s elapsed) @@ -166,7 +166,7 @@ created in parallel to everything else. ``` resource "aws_instance" "another" { - ami = "ami-13be557e" + ami = "ami-b374d5a5" instance_type = "t2.micro" } ``` diff --git a/website/source/intro/getting-started/provision.html.md b/website/source/intro/getting-started/provision.html.md index f78c3a4123..d6d65033cf 100644 --- a/website/source/intro/getting-started/provision.html.md +++ b/website/source/intro/getting-started/provision.html.md @@ -25,7 +25,7 @@ To define a provisioner, modify the resource block defining the ``` resource "aws_instance" "example" { - ami = "ami-13be557e" + ami = "ami-b374d5a5" instance_type = "t2.micro" provisioner "local-exec" { @@ -61,7 +61,7 @@ then run `apply`: ``` $ terraform apply aws_instance.example: Creating... - ami: "" => "ami-13be557e" + ami: "" => "ami-b374d5a5" instance_type: "" => "t2.micro" aws_eip.ip: Creating... instance: "" => "i-213f350a" diff --git a/website/source/intro/getting-started/variables.html.md b/website/source/intro/getting-started/variables.html.md index e77c83d762..032616d14d 100644 --- a/website/source/intro/getting-started/variables.html.md +++ b/website/source/intro/getting-started/variables.html.md @@ -167,8 +167,8 @@ support for the `us-west-2` region as well: variable "amis" { type = "map" default = { - us-east-1 = "ami-13be557e" - us-west-2 = "ami-06b94666" + us-east-1 = "ami-b374d5a5" + us-west-2 = "ami-4b32be2b" } } ``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 1b19920465..1f42c1e328 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -180,6 +180,10 @@ > Remote State + + > + Sensitive Data + diff --git a/website/source/layouts/google.erb b/website/source/layouts/google.erb index a9429acd72..ddb3a7a80e 100644 --- a/website/source/layouts/google.erb +++ b/website/source/layouts/google.erb @@ -22,7 +22,7 @@ - > + > Google Cloud Platform Resources