Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Stephen Weatherford 2017-04-12 00:58:33 +00:00
commit 7fbc7d005a
1415 changed files with 90657 additions and 31148 deletions

4
.gitattributes vendored
View File

@ -1,4 +0,0 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
*.go eol=lf

View File

@ -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:

View File

@ -1,24 +1,179 @@
## 0.9.2 (unreleased)
## 0.9.3 (unreleased)
FEATURES:
* **New Resource:** `aws_api_gateway_usage_plan` [GH-12542]
* **New Resource:** `aws_iam_openid_connect_provider` [GH-13456]
* **New Resource:** `aws_lightsail_static_ip` [GH-13175]
* **New Resource:** `aws_lightsail_static_ip_attachment` [GH-13207]
* **New Resource:** `aws_ses_domain_identity` [GH-13098]
* **New Resource:** `azurerm_managed_disk` [GH-12455]
* **New Resource:** `kubernetes_persistent_volume` [GH-13277]
* **New Resource:** `kubernetes_secret` [GH-12960]
* **New Data Source:** `aws_iam_role` [GH-13213]
IMPROVEMENTS:
* provider/aws: Added support for EMR AutoScalingRole [GH-12823]
* provider/dnsimple: Allow dnsimple_record.priority attribute to be set [GH-12843]
* 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: add `-lock-timeout` option, which will block and retry locks for the given duration [GH-13262]
* core: new `chomp` interpolation function which returns the given string with any trailing newline characters removed [GH-13419]
* backend/remote-state: Add support for assume role extensions to s3 backend [GH-13236]
* config: New interpolation functions `basename` and `dirname`, for file path manipulation [GH-13080]
* helper/resource: Allow unknown "pending" states [GH-13099]
* command/hook_ui: Increase max length of state IDs from 20 to 80 [GH-13317]
* 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: Deprecate roles in favour of role in iam_instance_profile [GH-13130]
* provider/aws: Make alb_target_group_attachment port optional [GH-13139]
* provider/aws: `aws_api_gateway_domain_name` `certificate_private_key` field marked as sensitive [GH-13147]
* provider/aws: `aws_directory_service_directory` `password` field marked as sensitive [GH-13147]
* provider/aws: `aws_kinesis_firehose_delivery_stream` `password` field marked as sensitive [GH-13147]
* provider/aws: `aws_opsworks_application` `app_source.0.password` & `ssl_configuration.0.private_key` fields marked as sensitive [GH-13147]
* provider/aws: `aws_opsworks_stack` `custom_cookbooks_source.0.password` field marked as sensitive [GH-13147]
* provider/aws: Support the ability to enable / disable ipv6 support in VPC [GH-12527]
* provider/aws: Added API Gateway integration update [GH-13249]
* provider/aws: Add `identifier` | `name_prefix` to RDS resources [GH-13232]
* provider/aws: Validate `aws_ecs_task_definition.container_definitions` [GH-12161]
* provider/aws: Update caller_identity data source [GH-13092]
* provider/aws: `aws_subnet_ids` data source for getting a list of subnet ids matching certain criteria [GH-13188]
* provider/aws: Support ip_address_type for aws_alb [GH-13227]
* provider/aws: Migrate `aws_dms_*` resources away from AWS waiters [GH-13291]
* provider/aws: Add support for treat_missing_data to cloudwatch_metric_alarm [GH-13358]
* provider/aws: Add support for evaluate_low_sample_count_percentiles to cloudwatch_metric_alarm [GH-13371]
* provider/aws: Add `name_prefix` to `aws_alb_target_group` [GH-13442]
* provider/aws: Add support for EMR clusters to aws_appautoscaling_target [GH-13368]
* provider/bitbucket: Improved error handling [GH-13390]
* provider/cloudstack: Do not force a new resource when updating `cloudstack_loadbalancer_rule` members [GH-11786]
* provider/fastly: Add support for Sumologic logging [GH-12541]
* provider/github: Handle the case when issue labels already exist [GH-13182]
* provider/google: Mark `google_container_cluster`'s `client_key` & `password` inside `master_auth` as sensitive [GH-13148]
* provider/kubernetes: Allow defining custom config context [GH-12958]
* provider/openstack: Add support for 'value_specs' options to `openstack_compute_servergroup_v2` [GH-13380]
* provider/statuscake: Add support for StatusCake TriggerRate field [GH-13340]
* provider/triton: Move to joyent/triton-go [GH-13225]
* provisioner/chef: Make sure we add new Chef-Vault clients as clients [GH-13525]
BUG FIXES:
* provider/arukas: Default timeout for launching container increased to 15mins (was 10mins) [GH-12849]
* provider/mysql: recreate user/grant if user/grant got deleted manually [GH-12791]
* core: Escaped interpolation-like sequences (like `$${foo}`) now permitted in variable defaults [GH-13137]
* core: Fix strange issues with computed values in provider configuration that were worked around with `-input=false` [GH-11264], [GH-13264]
* core: Fix crash when providing nested maps as variable values in a `module` block [GH-13343]
* core: `connection` block attributes are now subject to basic validation of attribute names during validate walk [GH-13400]
* provider/aws: Add Support for maintenance_window and back_window to rds_cluster_instance [GH-13134]
* provider/aws: Increase timeout for AMI registration [GH-13159]
* provider/aws: Increase timeouts for ELB [GH-13161]
* provider/aws: `volume_type` of `aws_elasticsearch_domain.0.ebs_options` marked as `Computed` which prevents spurious diffs [GH-13160]
* provider/aws: Don't set DBName on `aws_db_instance` from snapshot [GH-13140]
* provider/aws: Add DiffSuppression to aws_ecs_service placement_strategies [GH-13220]
* provider/aws: Refresh aws_alb_target_group stickiness on manual updates [GH-13199]
* provider/aws: Preserve default retain_on_delete in cloudfront import [GH-13209]
* provider/aws: Refresh aws_alb_target_group tags [GH-13200]
* provider/aws: Set aws_vpn_connection to recreate when in deleted state [GH-13204]
* provider/aws: Wait for aws_opsworks_instance to be running when it's specified [GH-13218]
* provider/aws: Handle `aws_lambda_function` missing s3 key error [GH-10960]
* provider/aws: Set stickiness to computed in alb_target_group [GH-13278]
* provider/aws: Increase timeout for deploying `cloudfront_distribution` from 40 to 70 mins [GH-13319]
* provider/aws: Increase AMI retry timeouts [GH-13324]
* provider/aws: Increase subnet deletion timeout [GH-13356]
* provider/aws: Increase launch_configuration creation timeout [GH-13357]
* provider/aws: Increase Beanstalk env 'ready' timeout [GH-13359]
* provider/aws: Raise timeout for deleting APIG REST API [GH-13414]
* provider/aws: Raise timeout for attaching/detaching VPN Gateway [GH-13457]
* provider/aws: Recreate opsworks_stack on change of service_role_arn [GH-13325]
* provider/aws: Fix KMS Key reading with Exists method [GH-13348]
* provider/aws: Fix DynamoDB issues about GSIs indexes [GH-13256]
* provider/aws: Fix `aws_s3_bucket` drift detection of logging options [GH-13281]
* provider/aws: Update ElasticTranscoderPreset to have default for MaxFrameRate [GH-13422]
* provider/aws: Fix aws_ami_launch_permission refresh when AMI disappears [GH-13469]
* provider/aws: Add support for updating SSM documents [GH-13491]
* provider/azurerm: Network Security Group - ignoring protocol casing at Import time [GH-13153]
* provider/azurerm: Fix crash when importing Local Network Gateways [GH-13261]
* provider/azurerm: Defaulting the value of `duplicate_detection_history_time_window` for `azurerm_servicebus_topic` [GH-13223]
* provider/bitbucket: Fixed issue where provider would fail with an "EOF" error on some operations [GH-13390]
* provider/kubernetes: Use PATCH to update namespace [GH-13114]
* provider/ns1: No splitting answer on SPF records. [GH-13260]
* provider/openstack: Refresh volume_attachment from state if NotFound [GH-13342]
* provider/openstack: Add SOFT_DELETED to delete status [GH-13444]
* provider/profitbricks: Changed output type of ips variable of ip_block ProfitBricks resource [GH-13290]
## 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:** `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 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. ([#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)

View File

@ -41,7 +41,7 @@ plugin-dev: generate
test: fmtcheck errcheck generate
go test -i $(TEST) || exit 1
echo $(TEST) | \
xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4
xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4
# testacc runs acceptance tests
testacc: fmtcheck generate

View File

@ -5,7 +5,7 @@ Terraform
- [![Gitter chat](https://badges.gitter.im/hashicorp-terraform/Lobby.png)](https://gitter.im/hashicorp-terraform/Lobby)
- Mailing list: [Google Groups](http://groups.google.com/group/terraform-tool)
![Terraform](https://raw.githubusercontent.com/hashicorp/terraform/master/website/source/assets/images/readme.png)
![Terraform](https://rawgithub.com/hashicorp/terraform/master/website/source/assets/images/logo-hashicorp.svg)
Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.
@ -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"
```

View File

@ -7,6 +7,7 @@ package backend
import (
"context"
"errors"
"time"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state"
@ -132,6 +133,9 @@ type Operation struct {
// state.Lockers for its duration, and Unlock when complete.
LockState bool
// The duration to retry obtaining a State lock.
StateLockTimeout time.Duration
// Environment is the named state that should be loaded from the Backend.
Environment string
}

View File

@ -12,6 +12,7 @@ import (
backendlocal "github.com/hashicorp/terraform/backend/local"
backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul"
backendinmem "github.com/hashicorp/terraform/backend/remote-state/inmem"
backendS3 "github.com/hashicorp/terraform/backend/remote-state/s3"
)
// backends is the list of available backends. This is a global variable
@ -36,6 +37,7 @@ func init() {
"local": func() backend.Backend { return &backendlocal.Local{} },
"consul": func() backend.Backend { return backendconsul.New() },
"inmem": func() backend.Backend { return backendinmem.New() },
"s3": func() backend.Backend { return backendS3.New() },
}
// Add the legacy remote backends that haven't yet been convertd to

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
@ -52,9 +52,12 @@ func (b *Local) opApply(
}
if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return

View File

@ -10,8 +10,8 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/format"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
@ -61,9 +61,12 @@ func (b *Local) opPlan(
}
if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return
@ -110,6 +113,12 @@ func (b *Local) opPlan(
// Write the backend if we have one
plan.Backend = op.PlanOutBackend
// This works around a bug (#12871) which is no longer possible to
// trigger but will exist for already corrupted upgrades.
if plan.Backend != nil && plan.State != nil {
plan.State.Remote = nil
}
log.Printf("[INFO] backend/local: writing plan output to: %s", path)
f, err := os.Create(path)
if err == nil {

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state"
)
@ -51,9 +51,12 @@ func (b *Local) opRefresh(
}
if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return

View File

@ -21,7 +21,8 @@ func TestLocal_impl(t *testing.T) {
}
func TestLocal_backend(t *testing.T) {
b := TestLocal(t)
defer testTmpDir(t)()
b := &Local{}
backend.TestBackend(t, b, b)
}

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -type=countHookAction hook_count_action.go"; DO NOT EDIT
// Code generated by "stringer -type=countHookAction hook_count_action.go"; DO NOT EDIT.
package local

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -type=OperationType operation_type.go"; DO NOT EDIT
// Code generated by "stringer -type=OperationType operation_type.go"; DO NOT EDIT.
package backend

View File

@ -56,7 +56,7 @@ func (b *Backend) States() ([]string, error) {
}
func (b *Backend) DeleteState(name string) error {
if name == backend.DefaultStateName {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
@ -102,22 +102,19 @@ func (b *Backend) State(name string) (state.State, error) {
stateMgr = &state.LockDisabled{Inner: stateMgr}
}
// Get the locker, which we know always exists
stateMgrLocker := stateMgr.(state.Locker)
// Grab a lock, we use this to write an empty state if one doesn't
// exist already. We have to write an empty state as a sentinel value
// so States() knows it exists.
lockInfo := state.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := stateMgrLocker.Lock(lockInfo)
lockId, err := stateMgr.Lock(lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock state in Consul: %s", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgrLocker.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}

View File

@ -121,16 +121,15 @@ func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
default:
if c.lockCh != nil {
// we have an active lock already
return "", nil
return "", fmt.Errorf("state %q already locked", c.Path)
}
}
if c.consulLock == nil {
opts := &consulapi.LockOptions{
Key: c.Path + lockSuffix,
// We currently don't procide any options to block terraform and
// retry lock acquisition, but we can wait briefly in case the
// lock is about to be freed.
// only wait briefly, so terraform has the choice to fail fast or
// retry as needed.
LockWaitTime: time.Second,
LockTryOnce: true,
}
@ -191,6 +190,10 @@ func (c *RemoteClient) Unlock(id string) error {
err := c.consulLock.Unlock()
c.lockCh = nil
// This is only cleanup, and will fail if the lock was immediately taken by
// another client, so we don't report an error to the user here.
c.consulLock.Destroy()
kv := c.Client.KV()
_, delErr := kv.Delete(c.Path+lockInfoSuffix, nil)
if delErr != nil {

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
)
@ -98,3 +99,43 @@ func TestConsul_stateLock(t *testing.T) {
remote.TestRemoteLocks(t, sA.(*remote.State).Client, sB.(*remote.State).Client)
}
func TestConsul_destroyLock(t *testing.T) {
srv := newConsulTestServer(t)
defer srv.Stop()
// Get the backend
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
"address": srv.HTTPAddr,
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
})
// Grab the client
s, err := b.State(backend.DefaultStateName)
if err != nil {
t.Fatalf("err: %s", err)
}
c := s.(*remote.State).Client.(*RemoteClient)
info := state.NewLockInfo()
id, err := c.Lock(info)
if err != nil {
t.Fatal(err)
}
lockPath := c.Path + lockSuffix
if err := c.Unlock(id); err != nil {
t.Fatal(err)
}
// get the lock val
pair, _, err := c.Client.KV().Get(lockPath, nil)
if err != nil {
t.Fatal(err)
}
if pair != nil {
t.Fatalf("lock key not cleaned up at: %s", pair.Key)
}
}

View File

@ -0,0 +1,195 @@
package s3
import (
"context"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema"
terraformAWS "github.com/hashicorp/terraform/builtin/providers/aws"
)
// New creates a new backend for S3 remote state.
func New() backend.Backend {
s := &schema.Backend{
Schema: map[string]*schema.Schema{
"bucket": {
Type: schema.TypeString,
Required: true,
Description: "The name of the S3 bucket",
},
"key": {
Type: schema.TypeString,
Required: true,
Description: "The path to the state file inside the bucket",
},
"region": {
Type: schema.TypeString,
Required: true,
Description: "The region of the S3 bucket.",
DefaultFunc: schema.EnvDefaultFunc("AWS_DEFAULT_REGION", nil),
},
"endpoint": {
Type: schema.TypeString,
Optional: true,
Description: "A custom endpoint for the S3 API",
DefaultFunc: schema.EnvDefaultFunc("AWS_S3_ENDPOINT", ""),
},
"encrypt": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether to enable server side encryption of the state file",
Default: false,
},
"acl": {
Type: schema.TypeString,
Optional: true,
Description: "Canned ACL to be applied to the state file",
Default: "",
},
"access_key": {
Type: schema.TypeString,
Optional: true,
Description: "AWS access key",
Default: "",
},
"secret_key": {
Type: schema.TypeString,
Optional: true,
Description: "AWS secret key",
Default: "",
},
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
Description: "The ARN of a KMS Key to use for encrypting the state",
Default: "",
},
"lock_table": {
Type: schema.TypeString,
Optional: true,
Description: "DynamoDB table for state locking",
Default: "",
},
"profile": {
Type: schema.TypeString,
Optional: true,
Description: "AWS profile name",
Default: "",
},
"shared_credentials_file": {
Type: schema.TypeString,
Optional: true,
Description: "Path to a shared credentials file",
Default: "",
},
"token": {
Type: schema.TypeString,
Optional: true,
Description: "MFA token",
Default: "",
},
"role_arn": {
Type: schema.TypeString,
Optional: true,
Description: "The role to be assumed",
Default: "",
},
"session_name": {
Type: schema.TypeString,
Optional: true,
Description: "The session name to use when assuming the role.",
Default: "",
},
"external_id": {
Type: schema.TypeString,
Optional: true,
Description: "The external ID to use when assuming the role",
Default: "",
},
"assume_role_policy": {
Type: schema.TypeString,
Optional: true,
Description: "The permissions applied when assuming a role.",
Default: "",
},
},
}
result := &Backend{Backend: s}
result.Backend.ConfigureFunc = result.configure
return result
}
type Backend struct {
*schema.Backend
// The fields below are set from configure
s3Client *s3.S3
dynClient *dynamodb.DynamoDB
bucketName string
keyName string
serverSideEncryption bool
acl string
kmsKeyID string
lockTable string
}
func (b *Backend) configure(ctx context.Context) error {
if b.s3Client != nil {
return nil
}
// Grab the resource data
data := schema.FromContextBackendConfig(ctx)
b.bucketName = data.Get("bucket").(string)
b.keyName = data.Get("key").(string)
b.serverSideEncryption = data.Get("encrypt").(bool)
b.acl = data.Get("acl").(string)
b.kmsKeyID = data.Get("kms_key_id").(string)
b.lockTable = data.Get("lock_table").(string)
cfg := &terraformAWS.Config{
AccessKey: data.Get("access_key").(string),
AssumeRoleARN: data.Get("role_arn").(string),
AssumeRoleExternalID: data.Get("external_id").(string),
AssumeRolePolicy: data.Get("assume_role_policy").(string),
AssumeRoleSessionName: data.Get("session_name").(string),
CredsFilename: data.Get("shared_credentials_file").(string),
Profile: data.Get("profile").(string),
Region: data.Get("region").(string),
S3Endpoint: data.Get("endpoint").(string),
SecretKey: data.Get("secret_key").(string),
Token: data.Get("token").(string),
}
client, err := cfg.Client()
if err != nil {
return err
}
b.s3Client = client.(*terraformAWS.AWSClient).S3()
b.dynClient = client.(*terraformAWS.AWSClient).DynamoDB()
return nil
}

View File

@ -0,0 +1,159 @@
package s3
import (
"fmt"
"sort"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
)
const (
// This will be used as directory name, the odd looking colon is simply to
// reduce the chance of name conflicts with existing objects.
keyEnvPrefix = "env:"
)
func (b *Backend) States() ([]string, error) {
params := &s3.ListObjectsInput{
Bucket: &b.bucketName,
Prefix: aws.String(keyEnvPrefix + "/"),
}
resp, err := b.s3Client.ListObjects(params)
if err != nil {
return nil, err
}
var envs []string
for _, obj := range resp.Contents {
env := keyEnv(*obj.Key)
if env != "" {
envs = append(envs, env)
}
}
sort.Strings(envs)
envs = append([]string{backend.DefaultStateName}, envs...)
return envs, nil
}
// extract the env name from the S3 key
func keyEnv(key string) string {
parts := strings.Split(key, "/")
if len(parts) < 3 {
// no env here
return ""
}
if parts[0] != keyEnvPrefix {
// not our key, so ignore
return ""
}
return parts[1]
}
func (b *Backend) DeleteState(name string) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
params := &s3.DeleteObjectInput{
Bucket: &b.bucketName,
Key: aws.String(b.path(name)),
}
_, err := b.s3Client.DeleteObject(params)
if err != nil {
return err
}
return nil
}
func (b *Backend) State(name string) (state.State, error) {
client := &RemoteClient{
s3Client: b.s3Client,
dynClient: b.dynClient,
bucketName: b.bucketName,
path: b.path(name),
serverSideEncryption: b.serverSideEncryption,
acl: b.acl,
kmsKeyID: b.kmsKeyID,
lockTable: b.lockTable,
}
stateMgr := &remote.State{Client: client}
//if this isn't the default state name, we need to create the object so
//it's listed by States.
if name != backend.DefaultStateName {
// take a lock on this state while we write it
lockInfo := state.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock s3 state: %s", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent
}
// Grab the value
if err := stateMgr.RefreshState(); err != nil {
err = lockUnlock(err)
return nil, err
}
// If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
err = lockUnlock(err)
return nil, err
}
if err := stateMgr.PersistState(); err != nil {
err = lockUnlock(err)
return nil, err
}
}
// Unlock, the state should now be initialized
if err := lockUnlock(nil); err != nil {
return nil, err
}
}
return stateMgr, nil
}
func (b *Backend) client() *RemoteClient {
return &RemoteClient{}
}
func (b *Backend) path(name string) string {
if name == backend.DefaultStateName {
return b.keyName
}
return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/")
}
const errStateUnlock = `
Error unlocking S3 state. Lock ID: %s
Error: %s
You may have to force-unlock this state in order to use it again.
`

View File

@ -0,0 +1,209 @@
package s3
import (
"fmt"
"os"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform/backend"
)
// verify that we are doing ACC tests or the S3 tests specifically
func testACC(t *testing.T) {
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_S3_TEST") == ""
if skip {
t.Log("s3 backend tests require setting TF_ACC or TF_S3_TEST")
t.Skip()
}
if os.Getenv("AWS_DEFAULT_REGION") == "" {
os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
}
}
func TestBackend_impl(t *testing.T) {
var _ backend.Backend = new(Backend)
}
func TestBackendConfig(t *testing.T) {
testACC(t)
config := map[string]interface{}{
"region": "us-west-1",
"bucket": "tf-test",
"key": "state",
"encrypt": true,
"lock_table": "dynamoTable",
}
b := backend.TestBackendConfig(t, New(), config).(*Backend)
if *b.s3Client.Config.Region != "us-west-1" {
t.Fatalf("Incorrect region was populated")
}
if b.bucketName != "tf-test" {
t.Fatalf("Incorrect bucketName was populated")
}
if b.keyName != "state" {
t.Fatalf("Incorrect keyName was populated")
}
credentials, err := b.s3Client.Config.Credentials.Get()
if err != nil {
t.Fatalf("Error when requesting credentials")
}
if credentials.AccessKeyID == "" {
t.Fatalf("No Access Key Id was populated")
}
if credentials.SecretAccessKey == "" {
t.Fatalf("No Secret Access Key was populated")
}
}
func TestBackend(t *testing.T) {
testACC(t)
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
}).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
backend.TestBackend(t, b, nil)
}
func TestBackendLocked(t *testing.T) {
testACC(t)
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
"lock_table": bucketName,
}).(*Backend)
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
"lock_table": bucketName,
}).(*Backend)
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
backend.TestBackend(t, b1, b2)
}
func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
createBucketReq := &s3.CreateBucketInput{
Bucket: &bucketName,
}
// Be clear about what we're doing in case the user needs to clean
// this up later.
t.Logf("creating S3 bucket %s in %s", bucketName, *s3Client.Config.Region)
_, err := s3Client.CreateBucket(createBucketReq)
if err != nil {
t.Fatal("failed to create test S3 bucket:", err)
}
}
func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
warning := "WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)"
// first we have to get rid of the env objects, or we can't delete the bucket
resp, err := s3Client.ListObjects(&s3.ListObjectsInput{Bucket: &bucketName})
if err != nil {
t.Logf(warning, err)
return
}
for _, obj := range resp.Contents {
if _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil {
// this will need cleanup no matter what, so just warn and exit
t.Logf(warning, err)
return
}
}
if _, err := s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucketName}); err != nil {
t.Logf(warning, err)
}
}
// create the dynamoDB table, and wait until we can query it.
func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
createInput := &dynamodb.CreateTableInput{
AttributeDefinitions: []*dynamodb.AttributeDefinition{
{
AttributeName: aws.String("LockID"),
AttributeType: aws.String("S"),
},
},
KeySchema: []*dynamodb.KeySchemaElement{
{
AttributeName: aws.String("LockID"),
KeyType: aws.String("HASH"),
},
},
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(5),
WriteCapacityUnits: aws.Int64(5),
},
TableName: aws.String(tableName),
}
_, err := dynClient.CreateTable(createInput)
if err != nil {
t.Fatal(err)
}
// now wait until it's ACTIVE
start := time.Now()
time.Sleep(time.Second)
describeInput := &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
}
for {
resp, err := dynClient.DescribeTable(describeInput)
if err != nil {
t.Fatal(err)
}
if *resp.Table.TableStatus == "ACTIVE" {
return
}
if time.Since(start) > time.Minute {
t.Fatalf("timed out creating DynamoDB table %s", tableName)
}
time.Sleep(3 * time.Second)
}
}
func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
params := &dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
}
_, err := dynClient.DeleteTable(params)
if err != nil {
t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err)
}
}

View File

@ -1,4 +1,4 @@
package remote
package s3
import (
"bytes"
@ -6,127 +6,32 @@ import (
"fmt"
"io"
"log"
"os"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-multierror"
multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
terraformAws "github.com/hashicorp/terraform/builtin/providers/aws"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
)
func s3Factory(conf map[string]string) (Client, error) {
bucketName, ok := conf["bucket"]
if !ok {
return nil, fmt.Errorf("missing 'bucket' configuration")
}
keyName, ok := conf["key"]
if !ok {
return nil, fmt.Errorf("missing 'key' configuration")
}
endpoint, ok := conf["endpoint"]
if !ok {
endpoint = os.Getenv("AWS_S3_ENDPOINT")
}
regionName, ok := conf["region"]
if !ok {
regionName = os.Getenv("AWS_DEFAULT_REGION")
if regionName == "" {
return nil, fmt.Errorf(
"missing 'region' configuration or AWS_DEFAULT_REGION environment variable")
}
}
serverSideEncryption := false
if raw, ok := conf["encrypt"]; ok {
v, err := strconv.ParseBool(raw)
if err != nil {
return nil, fmt.Errorf(
"'encrypt' field couldn't be parsed as bool: %s", err)
}
serverSideEncryption = v
}
acl := ""
if raw, ok := conf["acl"]; ok {
acl = raw
}
kmsKeyID := conf["kms_key_id"]
var errs []error
creds, err := terraformAws.GetCredentials(&terraformAws.Config{
AccessKey: conf["access_key"],
SecretKey: conf["secret_key"],
Token: conf["token"],
Profile: conf["profile"],
CredsFilename: conf["shared_credentials_file"],
AssumeRoleARN: conf["role_arn"],
})
if err != nil {
return nil, err
}
// Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user
_, err = creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote.
Please see https://www.terraform.io/docs/state/remote/s3.html for more information on
providing credentials for the AWS S3 remote`))
} else {
errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err))
}
return nil, &multierror.Error{Errors: errs}
}
awsConfig := &aws.Config{
Credentials: creds,
Endpoint: aws.String(endpoint),
Region: aws.String(regionName),
HTTPClient: cleanhttp.DefaultClient(),
}
sess := session.New(awsConfig)
nativeClient := s3.New(sess)
dynClient := dynamodb.New(sess)
return &S3Client{
nativeClient: nativeClient,
bucketName: bucketName,
keyName: keyName,
serverSideEncryption: serverSideEncryption,
acl: acl,
kmsKeyID: kmsKeyID,
dynClient: dynClient,
lockTable: conf["lock_table"],
}, nil
}
type S3Client struct {
nativeClient *s3.S3
type RemoteClient struct {
s3Client *s3.S3
dynClient *dynamodb.DynamoDB
bucketName string
keyName string
path string
serverSideEncryption bool
acl string
kmsKeyID string
dynClient *dynamodb.DynamoDB
lockTable string
}
func (c *S3Client) Get() (*Payload, error) {
output, err := c.nativeClient.GetObject(&s3.GetObjectInput{
func (c *RemoteClient) Get() (*remote.Payload, error) {
output, err := c.s3Client.GetObject(&s3.GetObjectInput{
Bucket: &c.bucketName,
Key: &c.keyName,
Key: &c.path,
})
if err != nil {
@ -148,7 +53,7 @@ func (c *S3Client) Get() (*Payload, error) {
return nil, fmt.Errorf("Failed to read remote state: %s", err)
}
payload := &Payload{
payload := &remote.Payload{
Data: buf.Bytes(),
}
@ -160,7 +65,7 @@ func (c *S3Client) Get() (*Payload, error) {
return payload, nil
}
func (c *S3Client) Put(data []byte) error {
func (c *RemoteClient) Put(data []byte) error {
contentType := "application/json"
contentLength := int64(len(data))
@ -169,7 +74,7 @@ func (c *S3Client) Put(data []byte) error {
ContentLength: &contentLength,
Body: bytes.NewReader(data),
Bucket: &c.bucketName,
Key: &c.keyName,
Key: &c.path,
}
if c.serverSideEncryption {
@ -187,29 +92,28 @@ func (c *S3Client) Put(data []byte) error {
log.Printf("[DEBUG] Uploading remote state to S3: %#v", i)
if _, err := c.nativeClient.PutObject(i); err == nil {
if _, err := c.s3Client.PutObject(i); err == nil {
return nil
} else {
return fmt.Errorf("Failed to upload state: %v", err)
}
}
func (c *S3Client) Delete() error {
_, err := c.nativeClient.DeleteObject(&s3.DeleteObjectInput{
func (c *RemoteClient) Delete() error {
_, err := c.s3Client.DeleteObject(&s3.DeleteObjectInput{
Bucket: &c.bucketName,
Key: &c.keyName,
Key: &c.path,
})
return err
}
func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
if c.lockTable == "" {
return "", nil
}
stateName := fmt.Sprintf("%s/%s", c.bucketName, c.keyName)
info.Path = stateName
info.Path = c.lockPath()
if info.ID == "" {
lockID, err := uuid.GenerateUUID()
@ -222,7 +126,7 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
putParams := &dynamodb.PutItemInput{
Item: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(stateName)},
"LockID": {S: aws.String(c.lockPath())},
"Info": {S: aws.String(string(info.Marshal()))},
},
TableName: aws.String(c.lockTable),
@ -245,10 +149,10 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
return info.ID, nil
}
func (c *S3Client) getLockInfo() (*state.LockInfo, error) {
func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
getParams := &dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))},
"LockID": {S: aws.String(c.lockPath())},
},
ProjectionExpression: aws.String("LockID, Info"),
TableName: aws.String(c.lockTable),
@ -273,7 +177,7 @@ func (c *S3Client) getLockInfo() (*state.LockInfo, error) {
return lockInfo, nil
}
func (c *S3Client) Unlock(id string) error {
func (c *RemoteClient) Unlock(id string) error {
if c.lockTable == "" {
return nil
}
@ -297,7 +201,7 @@ func (c *S3Client) Unlock(id string) error {
params := &dynamodb.DeleteItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))},
"LockID": {S: aws.String(c.lockPath())},
},
TableName: aws.String(c.lockTable),
}
@ -309,3 +213,7 @@ func (c *S3Client) Unlock(id string) error {
}
return nil
}
func (c *RemoteClient) lockPath() string {
return fmt.Sprintf("%s/%s", c.bucketName, c.path)
}

View File

@ -0,0 +1,76 @@
package s3
import (
"fmt"
"testing"
"time"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state/remote"
)
func TestRemoteClient_impl(t *testing.T) {
var _ remote.Client = new(RemoteClient)
var _ remote.ClientLocker = new(RemoteClient)
}
func TestRemoteClient(t *testing.T) {
testACC(t)
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
}).(*Backend)
state, err := b.State(backend.DefaultStateName)
if err != nil {
t.Fatal(err)
}
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
remote.TestClient(t, state.(*remote.State).Client)
}
func TestRemoteClientLocks(t *testing.T) {
testACC(t)
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
"lock_table": bucketName,
}).(*Backend)
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
"bucket": bucketName,
"key": keyName,
"encrypt": true,
"lock_table": bucketName,
}).(*Backend)
s1, err := b1.State(backend.DefaultStateName)
if err != nil {
t.Fatal(err)
}
s2, err := b2.State(backend.DefaultStateName)
if err != nil {
t.Fatal(err)
}
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
}

View File

@ -65,57 +65,109 @@ func testBackendStates(t *testing.T, b Backend) {
}
// Create a couple states
fooState, err := b.State("foo")
foo, err := b.State("foo")
if err != nil {
t.Fatalf("error: %s", err)
}
if err := fooState.RefreshState(); err != nil {
if err := foo.RefreshState(); err != nil {
t.Fatalf("bad: %s", err)
}
if v := fooState.State(); v.HasResources() {
if v := foo.State(); v.HasResources() {
t.Fatalf("should be empty: %s", v)
}
barState, err := b.State("bar")
bar, err := b.State("bar")
if err != nil {
t.Fatalf("error: %s", err)
}
if err := barState.RefreshState(); err != nil {
if err := bar.RefreshState(); err != nil {
t.Fatalf("bad: %s", err)
}
if v := barState.State(); v.HasResources() {
if v := bar.State(); v.HasResources() {
t.Fatalf("should be empty: %s", v)
}
// Verify they are distinct states
// Verify they are distinct states that can be read back from storage
{
s := barState.State()
if s == nil {
s = terraform.NewState()
// start with a fresh state, and record the lineage being
// written to "bar"
barState := terraform.NewState()
barLineage := barState.Lineage
// the foo lineage should be distinct from bar, and unchanged after
// modifying bar
fooState := terraform.NewState()
fooLineage := fooState.Lineage
// write a known state to foo
if err := foo.WriteState(fooState); err != nil {
t.Fatal("error writing foo state:", err)
}
if err := foo.PersistState(); err != nil {
t.Fatal("error persisting foo state:", err)
}
s.Lineage = "bar"
if err := barState.WriteState(s); err != nil {
// write a distinct known state to bar
if err := bar.WriteState(barState); err != nil {
t.Fatalf("bad: %s", err)
}
if err := barState.PersistState(); err != nil {
if err := bar.PersistState(); err != nil {
t.Fatalf("bad: %s", err)
}
if err := fooState.RefreshState(); err != nil {
t.Fatalf("bad: %s", err)
// verify that foo is unchanged with the existing state manager
if err := foo.RefreshState(); err != nil {
t.Fatal("error refreshing foo:", err)
}
if v := fooState.State(); v != nil && v.Lineage == "bar" {
t.Fatalf("bad: %#v", v)
fooState = foo.State()
switch {
case fooState == nil:
t.Fatal("nil state read from foo")
case fooState.Lineage == barLineage:
t.Fatalf("bar lineage read from foo: %#v", fooState)
case fooState.Lineage != fooLineage:
t.Fatal("foo lineage alterred")
}
// fetch foo again from the backend
foo, err = b.State("foo")
if err != nil {
t.Fatal("error re-fetching state:", err)
}
if err := foo.RefreshState(); err != nil {
t.Fatal("error refreshing foo:", err)
}
fooState = foo.State()
switch {
case fooState == nil:
t.Fatal("nil state read from foo")
case fooState.Lineage != fooLineage:
t.Fatal("incorrect state returned from backend")
}
// fetch the bar again from the backend
bar, err = b.State("bar")
if err != nil {
t.Fatal("error re-fetching state:", err)
}
if err := bar.RefreshState(); err != nil {
t.Fatal("error refreshing bar:", err)
}
barState = bar.State()
switch {
case barState == nil:
t.Fatal("nil state read from bar")
case barState.Lineage != barLineage:
t.Fatal("incorrect state returned from backend")
}
}
// Verify we can now list them
{
// we determined that named stated are supported earlier
states, err := b.States()
if err == ErrNamedStatesNotSupported {
t.Logf("TestBackend: named states not supported in %T, skipping", b)
return
if err != nil {
t.Fatal(err)
}
sort.Strings(states)

View File

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

View File

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

View File

@ -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

View File

@ -97,6 +97,22 @@ func TestAccAlicloudImagesDataSource_nameRegexFilter(t *testing.T) {
})
}
func TestAccAlicloudImagesDataSource_imageNotInFirstPage(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAlicloudImagesDataSourceImageNotInFirstPageConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAlicloudDataSourceID("data.alicloud_images.name_regex_filtered_image"),
resource.TestMatchResourceAttr("data.alicloud_images.name_regex_filtered_image", "images.0.image_id", regexp.MustCompile("^ubuntu_14")),
),
},
},
})
}
// Instance store test - using centos images
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"
}
`

View File

@ -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"),

View File

@ -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"),

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

View File

@ -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"]

View File

@ -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) {

View File

@ -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 != "" {

View File

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

View File

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

View File

@ -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

View File

@ -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" {

View File

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

View File

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

View File

@ -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)
}

View File

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

View File

@ -24,14 +24,14 @@ func (client *AliyunClient) DescribeEipAddress(allocationId string) (*ecs.EipAdd
return &eips[0], nil
}
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}}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/denverdino/aliyungo/slb"
"github.com/hashicorp/terraform/helper/schema"
"regexp"
)
@ -355,48 +356,29 @@ func validateSlbListenerScheduler(v interface{}, k string) (ws []string, errors
return
}
func validateSlbListenerStickySession(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
flag := slb.FlagType(value)
if flag != "on" && flag != "off" {
errors = append(errors, fmt.Errorf(
"%q must contain a valid StickySession, expected %s or %s, got %q",
k, "on", "off", value))
}
}
return
}
func validateSlbListenerStickySessionType(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
flag := slb.StickySessionType(value)
if flag != "insert" && flag != "server" {
errors = append(errors, fmt.Errorf(
"%q must contain a valid StickySessionType, expected %s or %s, got %q",
k, "insert", "server", value))
}
}
return
}
func validateSlbListenerCookie(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
flag := slb.StickySessionType(value)
if flag != "insert" && flag != "server" {
errors = append(errors, fmt.Errorf(
"%q must contain a valid StickySessionType, expected %s or %s, got %q",
k, "insert", "server", 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) {
value := v.(int)
if value < 0 || value > 86400 {
if value < 0 || value > 3600 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer persistence timeout between 0 and 86400",
k))
@ -405,6 +387,138 @@ func validateSlbListenerPersistenceTimeout(v interface{}, k string) (ws []string
return
}
func validateSlbListenerHealthCheckDomain(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
//the len add "$_ip",so to max is 84
if len(value) < 1 || len(value) > 84 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 84 characters", k))
}
}
return
}
func validateSlbListenerHealthCheckUri(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
if len(value) < 1 || len(value) > 80 {
errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k))
}
}
return
}
func validateSlbListenerHealthCheckConnectPort(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < 1 || value > 65535 {
if value != -520 {
errors = append(errors, fmt.Errorf(
"%q must be a valid load balancer health check connect port between 1 and 65535 or -520",
k))
return
}
}
return
}
func validateDBBackupPeriod(v interface{}, k string) (ws []string, errors []error) {
days := []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
value := v.(string)
exist := false
for _, d := range days {
if value == d {
exist = true
break
}
}
if !exist {
errors = append(errors, fmt.Errorf(
"%q must contain a valid backup period value should in array %#v, got %q",
k, days, value))
}
return
}
func validateAllowedStringValue(ss []string) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
existed := false
for _, s := range ss {
if s == value {
existed = true
break
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid string value should in array %#v, got %q",
k, ss, value))
}
return
}
}
func validateAllowedSplitStringValue(ss []string, splitStr string) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
existed := false
tsList := strings.Split(value, splitStr)
for _, ts := range tsList {
existed = false
for _, s := range ss {
if ts == s {
existed = true
break
}
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid string value should in %#v, got %q",
k, ss, value))
}
return
}
}
func validateAllowedIntValue(is []int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
existed := false
for _, i := range is {
if i == value {
existed = true
break
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid int value should in array %#v, got %q",
k, is, value))
}
return
}
}
func validateIntegerInRange(min, max int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < min {
errors = append(errors, fmt.Errorf(
"%q cannot be lower than %d: %d", k, min, value))
}
if value > max {
errors = append(errors, fmt.Errorf(
"%q cannot be higher than %d: %d", k, max, value))
}
return
}
}
//data source validate func
//data_source_alicloud_image
func validateNameRegex(v interface{}, k string) (ws []string, errors []error) {

View File

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

View File

@ -134,7 +134,7 @@ func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
if usedEndpoint == "" {
usedEndpoint = "default location"
}
log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+
log.Printf("[INFO] Ignoring AWS metadata API endpoint at %s "+
"as it doesn't return any instance-id", usedEndpoint)
}
}

View File

@ -443,10 +443,10 @@ func expandLambdaFunctionAssociation(lf map[string]interface{}) *cloudfront.Lamb
return &lfa
}
func flattenLambdaFunctionAssociations(lfa *cloudfront.LambdaFunctionAssociations) []interface{} {
s := make([]interface{}, len(lfa.Items))
for i, v := range lfa.Items {
s[i] = flattenLambdaFunctionAssociation(v)
func flattenLambdaFunctionAssociations(lfa *cloudfront.LambdaFunctionAssociations) *schema.Set {
s := schema.NewSet(lambdaFunctionAssociationHash, []interface{}{})
for _, v := range lfa.Items {
s.Add(flattenLambdaFunctionAssociation(v))
}
return s
}

View File

@ -364,14 +364,8 @@ func TestCloudFrontStructure_flattenCacheBehavior(t *testing.T) {
t.Fatalf("Expected out[target_origin_id] to be myS3Origin, got %v", out["target_origin_id"])
}
// the flattened lambda function associations are a slice of maps,
// where as the default cache behavior LFAs are a set. Here we double check
// that and conver the slice to a set, and use Set's Equal() method to check
// equality
var outSet *schema.Set
if outSlice, ok := out["lambda_function_association"].([]interface{}); ok {
outSet = schema.NewSet(lambdaFunctionAssociationHash, outSlice)
} else {
var outSet, ok = out["lambda_function_association"].(*schema.Set)
if !ok {
t.Fatalf("out['lambda_function_association'] is not a slice as expected: %#v", out["lambda_function_association"])
}
@ -496,7 +490,7 @@ func TestCloudFrontStructure_flattenlambdaFunctionAssociations(t *testing.T) {
lfa := expandLambdaFunctionAssociations(in.List())
out := flattenLambdaFunctionAssociations(lfa)
if reflect.DeepEqual(in.List(), out) != true {
if reflect.DeepEqual(in.List(), out.List()) != true {
t.Fatalf("Expected out to be %v, got %v", in, out)
}
}

View File

@ -160,6 +160,14 @@ type AWSClient struct {
wafconn *waf.WAF
}
func (c *AWSClient) S3() *s3.S3 {
return c.s3conn
}
func (c *AWSClient) DynamoDB() *dynamodb.DynamoDB {
return c.dynamodbconn
}
// Client configures and returns a fully initialized AWSClient
func (c *Config) Client() (interface{}, error) {
// Get the auth and region. This can fail if keys/regions were not

View File

@ -5,6 +5,7 @@ import (
"log"
"time"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/terraform/helper/schema"
)
@ -17,24 +18,33 @@ func dataSourceAwsCallerIdentity() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"user_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceAwsCallerIdentityRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient)
client := meta.(*AWSClient).stsconn
log.Printf("[DEBUG] Reading Caller Identity.")
d.SetId(time.Now().UTC().String())
if client.accountid == "" {
log.Println("[DEBUG] No Account ID available, failing")
return fmt.Errorf("No AWS Account ID is available to the provider. Please ensure that\n" +
"skip_requesting_account_id is not set on the AWS provider.")
res, err := client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
if err != nil {
return fmt.Errorf("Error getting Caller Identity: %v", err)
}
log.Printf("[DEBUG] Setting AWS Account ID to %s.", client.accountid)
d.Set("account_id", meta.(*AWSClient).accountid)
log.Printf("[DEBUG] Received Caller Identity: %s", res)
d.SetId(time.Now().UTC().String())
d.Set("account_id", res.Account)
d.Set("arn", res.Arn)
d.Set("user_id", res.UserId)
return nil
}

View File

@ -39,6 +39,14 @@ func testAccCheckAwsCallerIdentityAccountId(n string) resource.TestCheckFunc {
return fmt.Errorf("Incorrect Account ID: expected %q, got %q", expected, rs.Primary.Attributes["account_id"])
}
if rs.Primary.Attributes["user_id"] == "" {
return fmt.Errorf("UserID expected to not be nil")
}
if rs.Primary.Attributes["arn"] == "" {
return fmt.Errorf("ARN expected to not be nil")
}
return nil
}
}

View File

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

View File

@ -15,10 +15,10 @@ func TestAccAWSEcsDataSource_ecsTaskDefinition(t *testing.T) {
resource.TestStep{
Config: testAccCheckAwsEcsTaskDefinitionDataSourceConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("data.aws_ecs_task_definition.mongo", "id", regexp.MustCompile("^arn:aws:ecs:us-west-2:[0-9]{12}:task-definition/mongodb:[1-9]*[0-9]$")),
resource.TestMatchResourceAttr("data.aws_ecs_task_definition.mongo", "id", regexp.MustCompile("^arn:aws:ecs:us-west-2:[0-9]{12}:task-definition/mongodb:[1-9][0-9]*$")),
resource.TestCheckResourceAttr("data.aws_ecs_task_definition.mongo", "family", "mongodb"),
resource.TestCheckResourceAttr("data.aws_ecs_task_definition.mongo", "network_mode", "bridge"),
resource.TestMatchResourceAttr("data.aws_ecs_task_definition.mongo", "revision", regexp.MustCompile("^[1-9]*[0-9]$")),
resource.TestMatchResourceAttr("data.aws_ecs_task_definition.mongo", "revision", regexp.MustCompile("^[1-9][0-9]*$")),
resource.TestCheckResourceAttr("data.aws_ecs_task_definition.mongo", "status", "ACTIVE"),
resource.TestMatchResourceAttr("data.aws_ecs_task_definition.mongo", "task_role_arn", regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/mongo_role$")),
),

View File

@ -0,0 +1,67 @@
package aws
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsIAMRole() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsIAMRoleRead,
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"assume_role_policy_document": {
Type: schema.TypeString,
Computed: true,
},
"path": {
Type: schema.TypeString,
Computed: true,
},
"role_id": {
Type: schema.TypeString,
Computed: true,
},
"role_name": {
Type: schema.TypeString,
Required: true,
},
},
}
}
func dataSourceAwsIAMRoleRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
roleName := d.Get("role_name").(string)
req := &iam.GetRoleInput{
RoleName: aws.String(roleName),
}
resp, err := iamconn.GetRole(req)
if err != nil {
return errwrap.Wrapf("Error getting roles: {{err}}", err)
}
if resp == nil {
return fmt.Errorf("no IAM role found")
}
role := resp.Role
d.SetId(*role.RoleId)
d.Set("arn", role.Arn)
d.Set("assume_role_policy_document", role.AssumeRolePolicyDocument)
d.Set("path", role.Path)
d.Set("role_id", role.RoleId)
return nil
}

View File

@ -0,0 +1,59 @@
package aws
import (
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDataSourceIAMRole_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAwsIAMRoleConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.aws_iam_role.test", "role_id"),
resource.TestCheckResourceAttr("data.aws_iam_role.test", "assume_role_policy_document", "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D"),
resource.TestCheckResourceAttr("data.aws_iam_role.test", "path", "/testpath/"),
resource.TestCheckResourceAttr("data.aws_iam_role.test", "role_name", "TestRole"),
resource.TestMatchResourceAttr("data.aws_iam_role.test", "arn", regexp.MustCompile("^arn:aws:iam::[0-9]{12}:role/testpath/TestRole$")),
),
},
},
})
}
const testAccAwsIAMRoleConfig = `
provider "aws" {
region = "us-east-1"
}
resource "aws_iam_role" "test_role" {
name = "TestRole"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
path = "/testpath/"
}
data "aws_iam_role" "test" {
role_name = "${aws_iam_role.test_role.name}"
}
`

View File

@ -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" {

View File

@ -136,7 +136,7 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro
if matchingTags && matchingVPC {
if hostedZoneFound != nil {
return fmt.Errorf("multplie Route53Zone found please use vpc_id option to filter")
return fmt.Errorf("multiple Route53Zone found please use vpc_id option to filter")
} else {
hostedZoneFound = hostedZone
}

View File

@ -4,69 +4,52 @@ import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceAwsRoute53Zone(t *testing.T) {
rInt := acctest.RandInt()
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)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataSourceAwsRoute53ZoneConfig,
{
Config: testAccDataSourceAwsRoute53ZoneConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_zone_id"),
testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_name"),
testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_vpc"),
testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_tag"),
testAccDataSourceAwsRoute53ZoneCheck(
publicResourceName, "data.aws_route53_zone.by_zone_id", publicDomain),
testAccDataSourceAwsRoute53ZoneCheck(
publicResourceName, "data.aws_route53_zone.by_name", publicDomain),
testAccDataSourceAwsRoute53ZoneCheck(
privateResourceName, "data.aws_route53_zone.by_vpc", privateDomain),
testAccDataSourceAwsRoute53ZoneCheck(
privateResourceName, "data.aws_route53_zone.by_tag", privateDomain),
),
},
},
})
}
func testAccDataSourceAwsRoute53ZoneCheck(name string) resource.TestCheckFunc {
// rsName for the name of the created resource
// dsName for the name of the created data source
// zName for the name of the domain
func testAccDataSourceAwsRoute53ZoneCheck(rsName, dsName, zName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
rs, ok := s.RootModule().Resources[rsName]
if !ok {
return fmt.Errorf("root module has no resource called %s", name)
return fmt.Errorf("root module has no resource called %s", rsName)
}
hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test"]
hostedZone, ok := s.RootModule().Resources[dsName]
if !ok {
return fmt.Errorf("can't find aws_hosted_zone.test in state")
}
attr := rs.Primary.Attributes
if attr["id"] != hostedZone.Primary.Attributes["id"] {
return fmt.Errorf(
"id is %s; want %s",
attr["id"],
hostedZone.Primary.Attributes["id"],
)
}
if attr["name"] != "terraformtestacchz.com." {
return fmt.Errorf(
"Route53 Zone name is %s; want terraformtestacchz.com.",
attr["name"],
)
}
return nil
}
}
func testAccDataSourceAwsRoute53ZoneCheckPrivate(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("root module has no resource called %s", name)
}
hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test_private"]
if !ok {
return fmt.Errorf("can't find aws_hosted_zone.test in state")
return fmt.Errorf("can't find zone %q in state", dsName)
}
attr := rs.Primary.Attributes
@ -78,56 +61,54 @@ func testAccDataSourceAwsRoute53ZoneCheckPrivate(name string) resource.TestCheck
)
}
if attr["name"] != "test.acc." {
return fmt.Errorf(
"Route53 Zone name is %s; want test.acc.",
attr["name"],
)
if attr["name"] != zName {
return fmt.Errorf("Route53 Zone name is %q; want %q", attr["name"], zName)
}
return nil
}
}
const testAccDataSourceAwsRoute53ZoneConfig = `
func testAccDataSourceAwsRoute53ZoneConfig(rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-east-2"
}
provider "aws" {
region = "us-east-2"
}
resource "aws_vpc" "test" {
cidr_block = "172.16.0.0/16"
}
resource "aws_vpc" "test" {
cidr_block = "172.16.0.0/16"
}
resource "aws_route53_zone" "test_private" {
name = "test.acc-%d."
vpc_id = "${aws_vpc.test.id}"
tags {
Environment = "dev-%d"
}
}
resource "aws_route53_zone" "test_private" {
name = "test.acc."
vpc_id = "${aws_vpc.test.id}"
tags {
Environment = "dev"
}
}
data "aws_route53_zone" "by_vpc" {
name = "${aws_route53_zone.test_private.name}"
vpc_id = "${aws_vpc.test.id}"
}
data "aws_route53_zone" "by_vpc" {
name = "${aws_route53_zone.test_private.name}"
vpc_id = "${aws_vpc.test.id}"
}
data "aws_route53_zone" "by_tag" {
name = "${aws_route53_zone.test_private.name}"
private_zone = true
tags {
Environment = "dev"
}
}
data "aws_route53_zone" "by_tag" {
name = "${aws_route53_zone.test_private.name}"
private_zone = true
tags {
Environment = "dev-%d"
}
}
resource "aws_route53_zone" "test" {
name = "terraformtestacchz.com."
}
data "aws_route53_zone" "by_zone_id" {
zone_id = "${aws_route53_zone.test.zone_id}"
}
resource "aws_route53_zone" "test" {
name = "terraformtestacchz-%d.com."
}
data "aws_route53_zone" "by_name" {
name = "${data.aws_route53_zone.by_zone_id.name}"
}
data "aws_route53_zone" "by_zone_id" {
zone_id = "${aws_route53_zone.test.zone_id}"
}
`
data "aws_route53_zone" "by_name" {
name = "${data.aws_route53_zone.by_zone_id.name}"
}`, rInt, rInt, rInt, rInt)
}

View File

@ -0,0 +1,60 @@
package aws
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsSubnetIDs() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsSubnetIDsRead,
Schema: map[string]*schema.Schema{
"vpc_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"ids": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func dataSourceAwsSubnetIDsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
req := &ec2.DescribeSubnetsInput{}
req.Filters = buildEC2AttributeFilterList(
map[string]string{
"vpc-id": d.Get("vpc_id").(string),
},
)
log.Printf("[DEBUG] DescribeSubnets %s\n", req)
resp, err := conn.DescribeSubnets(req)
if err != nil {
return err
}
if resp == nil || len(resp.Subnets) == 0 {
return fmt.Errorf("no matching subnet found for vpc with id %s", d.Get("vpc_id").(string))
}
subnets := make([]string, 0)
for _, subnet := range resp.Subnets {
subnets = append(subnets, *subnet.SubnetId)
}
d.SetId(d.Get("vpc_id").(string))
d.Set("ids", subnets)
return nil
}

View File

@ -0,0 +1,77 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDataSourceAwsSubnetIDs(t *testing.T) {
rInt := acctest.RandIntRange(0, 256)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsSubnetIDsConfig(rInt),
},
{
Config: testAccDataSourceAwsSubnetIDsConfigWithDataSource(rInt),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_subnet_ids.selected", "ids.#", "1"),
),
},
},
})
}
func testAccDataSourceAwsSubnetIDsConfigWithDataSource(rInt int) string {
return fmt.Sprintf(
`
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
tags {
Name = "terraform-testacc-subnet-ids-data-source"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
tags {
Name = "terraform-testacc-subnet-ids-data-source"
}
}
data "aws_subnet_ids" "selected" {
vpc_id = "${aws_vpc.test.id}"
}
`, rInt, rInt)
}
func testAccDataSourceAwsSubnetIDsConfig(rInt int) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
tags {
Name = "terraform-testacc-subnet-ids-data-source"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
tags {
Name = "terraform-testacc-subnet-ids-data-source"
}
}
`, rInt, rInt)
}

View File

@ -4,30 +4,33 @@ import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceAwsSubnet(t *testing.T) {
rInt := acctest.RandIntRange(0, 256)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataSourceAwsSubnetConfig,
Config: testAccDataSourceAwsSubnetConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_id"),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_cidr"),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_tag"),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_vpc"),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_filter"),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_id", rInt),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_cidr", rInt),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_tag", rInt),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_vpc", rInt),
testAccDataSourceAwsSubnetCheck("data.aws_subnet.by_filter", rInt),
),
},
},
})
}
func testAccDataSourceAwsSubnetCheck(name string) resource.TestCheckFunc {
func testAccDataSourceAwsSubnetCheck(name string, rInt int) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
@ -61,13 +64,13 @@ func testAccDataSourceAwsSubnetCheck(name string) resource.TestCheckFunc {
)
}
if attr["cidr_block"] != "172.16.123.0/24" {
if attr["cidr_block"] != fmt.Sprintf("172.%d.123.0/24", rInt) {
return fmt.Errorf("bad cidr_block %s", attr["cidr_block"])
}
if attr["availability_zone"] != "us-west-2a" {
return fmt.Errorf("bad availability_zone %s", attr["availability_zone"])
}
if attr["tags.Name"] != "terraform-testacc-subnet-data-source" {
if attr["tags.Name"] != fmt.Sprintf("terraform-testacc-subnet-data-source-%d", rInt) {
return fmt.Errorf("bad Name tag %s", attr["tags.Name"])
}
@ -75,51 +78,53 @@ func testAccDataSourceAwsSubnetCheck(name string) resource.TestCheckFunc {
}
}
const testAccDataSourceAwsSubnetConfig = `
provider "aws" {
region = "us-west-2"
func testAccDataSourceAwsSubnetConfig(rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "test" {
cidr_block = "172.%d.0.0/16"
tags {
Name = "terraform-testacc-subnet-data-source"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.%d.123.0/24"
availability_zone = "us-west-2a"
tags {
Name = "terraform-testacc-subnet-data-source-%d"
}
}
data "aws_subnet" "by_id" {
id = "${aws_subnet.test.id}"
}
data "aws_subnet" "by_cidr" {
cidr_block = "${aws_subnet.test.cidr_block}"
}
data "aws_subnet" "by_tag" {
tags {
Name = "${aws_subnet.test.tags["Name"]}"
}
}
data "aws_subnet" "by_vpc" {
vpc_id = "${aws_subnet.test.vpc_id}"
}
data "aws_subnet" "by_filter" {
filter {
name = "vpc-id"
values = ["${aws_subnet.test.vpc_id}"]
}
}
`, rInt, rInt, rInt)
}
resource "aws_vpc" "test" {
cidr_block = "172.16.0.0/16"
tags {
Name = "terraform-testacc-subnet-data-source"
}
}
resource "aws_subnet" "test" {
vpc_id = "${aws_vpc.test.id}"
cidr_block = "172.16.123.0/24"
availability_zone = "us-west-2a"
tags {
Name = "terraform-testacc-subnet-data-source"
}
}
data "aws_subnet" "by_id" {
id = "${aws_subnet.test.id}"
}
data "aws_subnet" "by_cidr" {
cidr_block = "${aws_subnet.test.cidr_block}"
}
data "aws_subnet" "by_tag" {
tags {
Name = "${aws_subnet.test.tags["Name"]}"
}
}
data "aws_subnet" "by_vpc" {
vpc_id = "${aws_subnet.test.vpc_id}"
}
data "aws_subnet" "by_filter" {
filter {
name = "vpc-id"
values = ["${aws_subnet.test.vpc_id}"]
}
}
`

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"log"
"net/url"
"strings"
"github.com/hashicorp/terraform/helper/schema"
@ -58,3 +59,19 @@ func suppressEquivalentJsonDiffs(k, old, new string, d *schema.ResourceData) boo
return jsonBytesEqual(ob.Bytes(), nb.Bytes())
}
func suppressOpenIdURL(k, old, new string, d *schema.ResourceData) bool {
oldUrl, err := url.Parse(old)
if err != nil {
return false
}
newUrl, err := url.Parse(new)
if err != nil {
return false
}
oldUrl.Scheme = "https"
return oldUrl.String() == newUrl.String()
}

View File

@ -7,6 +7,10 @@ import (
)
func resourceAwsCloudFrontDistributionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
// This is a non API attribute
// We are merely setting this to the same value as the Default setting in the schema
d.Set("retain_on_delete", false)
conn := meta.(*AWSClient).cloudfrontconn
id := d.Id()
resp, err := conn.GetDistributionConfig(&cloudfront.GetDistributionConfigInput{

View File

@ -19,16 +19,13 @@ func TestAccAWSCloudFrontDistribution_importBasic(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontDistributionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testConfig,
},
resource.TestStep{
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
// Ignore retain_on_delete since it doesn't come from the AWS
// API.
ImportStateVerifyIgnore: []string{"retain_on_delete"},
},
},
})

View File

@ -3,10 +3,12 @@ package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudWatchMetricAlarm_importBasic(t *testing.T) {
rInt := acctest.RandInt()
resourceName := "aws_cloudwatch_metric_alarm.foobar"
resource.Test(t, resource.TestCase{
@ -14,11 +16,11 @@ func TestAccAWSCloudWatchMetricAlarm_importBasic(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchMetricAlarmDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchMetricAlarmConfig,
{
Config: testAccAWSCloudWatchMetricAlarmConfig(rInt),
},
resource.TestStep{
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,

View File

@ -3,11 +3,14 @@ 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"
rInt := acctest.RandInt()
rBgpAsn := acctest.RandIntRange(64512, 65534)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -15,7 +18,7 @@ func TestAccAWSCustomerGateway_importBasic(t *testing.T) {
CheckDestroy: testAccCheckCustomerGatewayDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCustomerGatewayConfig,
Config: testAccCustomerGatewayConfig(rInt, rBgpAsn),
},
resource.TestStep{

View File

@ -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{

View File

@ -1,13 +1,16 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSElasticacheParameterGroup_importBasic(t *testing.T) {
resourceName := "aws_elasticache_parameter_group.bar"
rName := fmt.Sprintf("parameter-group-test-terraform-%d", acctest.RandInt())
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -15,7 +18,7 @@ func TestAccAWSElasticacheParameterGroup_importBasic(t *testing.T) {
CheckDestroy: testAccCheckAWSElasticacheParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSElasticacheParameterGroupConfig,
Config: testAccAWSElasticacheParameterGroupConfig(rName),
},
resource.TestStep{

View File

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

View File

@ -3,11 +3,13 @@ package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSVpnConnection_importBasic(t *testing.T) {
resourceName := "aws_vpn_connection.foo"
rBgpAsn := acctest.RandIntRange(64512, 65534)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -15,7 +17,7 @@ func TestAccAWSVpnConnection_importBasic(t *testing.T) {
CheckDestroy: testAccAwsVpnConnectionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsVpnConnectionConfig,
Config: testAccAwsVpnConnectionConfig(rBgpAsn),
},
{
ResourceName: resourceName,

View File

@ -174,6 +174,7 @@ func Provider() terraform.ResourceProvider {
"aws_elb_service_account": dataSourceAwsElbServiceAccount(),
"aws_iam_account_alias": dataSourceAwsIamAccountAlias(),
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
"aws_iam_role": dataSourceAwsIAMRole(),
"aws_iam_server_certificate": dataSourceAwsIAMServerCertificate(),
"aws_instance": dataSourceAwsInstance(),
"aws_ip_ranges": dataSourceAwsIPRanges(),
@ -187,6 +188,7 @@ func Provider() terraform.ResourceProvider {
"aws_s3_bucket_object": dataSourceAwsS3BucketObject(),
"aws_sns_topic": dataSourceAwsSnsTopic(),
"aws_subnet": dataSourceAwsSubnet(),
"aws_subnet_ids": dataSourceAwsSubnetIDs(),
"aws_security_group": dataSourceAwsSecurityGroup(),
"aws_vpc": dataSourceAwsVpc(),
"aws_vpc_endpoint": dataSourceAwsVpcEndpoint(),
@ -220,6 +222,7 @@ func Provider() terraform.ResourceProvider {
"aws_api_gateway_resource": resourceAwsApiGatewayResource(),
"aws_api_gateway_rest_api": resourceAwsApiGatewayRestApi(),
"aws_api_gateway_usage_plan": resourceAwsApiGatewayUsagePlan(),
"aws_api_gateway_usage_plan_key": resourceAwsApiGatewayUsagePlanKey(),
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
"aws_appautoscaling_target": resourceAwsAppautoscalingTarget(),
"aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(),
@ -306,6 +309,7 @@ func Provider() terraform.ResourceProvider {
"aws_iam_group_membership": resourceAwsIamGroupMembership(),
"aws_iam_group_policy_attachment": resourceAwsIamGroupPolicyAttachment(),
"aws_iam_instance_profile": resourceAwsIamInstanceProfile(),
"aws_iam_openid_connect_provider": resourceAwsIamOpenIDConnectProvider(),
"aws_iam_policy": resourceAwsIamPolicy(),
"aws_iam_policy_attachment": resourceAwsIamPolicyAttachment(),
"aws_iam_role_policy_attachment": resourceAwsIamRolePolicyAttachment(),
@ -336,6 +340,8 @@ func Provider() terraform.ResourceProvider {
"aws_lightsail_domain": resourceAwsLightsailDomain(),
"aws_lightsail_instance": resourceAwsLightsailInstance(),
"aws_lightsail_key_pair": resourceAwsLightsailKeyPair(),
"aws_lightsail_static_ip": resourceAwsLightsailStaticIp(),
"aws_lightsail_static_ip_attachment": resourceAwsLightsailStaticIpAttachment(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_load_balancer_policy": resourceAwsLoadBalancerPolicy(),
"aws_load_balancer_backend_server_policy": resourceAwsLoadBalancerBackendServerPolicies(),
@ -382,6 +388,7 @@ func Provider() terraform.ResourceProvider {
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_ses_active_receipt_rule_set": resourceAwsSesActiveReceiptRuleSet(),
"aws_ses_domain_identity": resourceAwsSesDomainIdentity(),
"aws_ses_receipt_filter": resourceAwsSesReceiptFilter(),
"aws_ses_receipt_rule": resourceAwsSesReceiptRule(),
"aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(),

View File

@ -69,7 +69,6 @@ func resourceAwsAlb() *schema.Resource {
"subnets": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
ForceNew: true,
Required: true,
Set: schema.HashString,
},
@ -109,6 +108,12 @@ func resourceAwsAlb() *schema.Resource {
Default: 60,
},
"ip_address_type": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
@ -159,6 +164,10 @@ func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
}
if v, ok := d.GetOk("ip_address_type"); ok {
elbOpts.IpAddressType = aws.String(v.(string))
}
log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts)
resp, err := elbconn.CreateLoadBalancer(elbOpts)
@ -175,7 +184,7 @@ func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[INFO] ALB ID: %s", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"active", "provisioning", "failed"},
Pending: []string{"provisioning", "failed"},
Target: []string{"active"},
Refresh: func() (interface{}, string, error) {
describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
@ -194,7 +203,7 @@ func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
return describeResp, *dLb.State.Code, nil
},
Timeout: 5 * time.Minute,
Timeout: 10 * time.Minute,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
@ -312,6 +321,62 @@ func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error {
}
if d.HasChange("subnets") {
subnets := expandStringList(d.Get("subnets").(*schema.Set).List())
params := &elbv2.SetSubnetsInput{
LoadBalancerArn: aws.String(d.Id()),
Subnets: subnets,
}
_, err := elbconn.SetSubnets(params)
if err != nil {
return fmt.Errorf("Failure Setting ALB Subnets: %s", err)
}
}
if d.HasChange("ip_address_type") {
params := &elbv2.SetIpAddressTypeInput{
LoadBalancerArn: aws.String(d.Id()),
IpAddressType: aws.String(d.Get("ip_address_type").(string)),
}
_, err := elbconn.SetIpAddressType(params)
if err != nil {
return fmt.Errorf("Failure Setting ALB IP Address Type: %s", err)
}
}
stateConf := &resource.StateChangeConf{
Pending: []string{"active", "provisioning", "failed"},
Target: []string{"active"},
Refresh: func() (interface{}, string, error) {
describeResp, err := elbconn.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{aws.String(d.Id())},
})
if err != nil {
return nil, "", err
}
if len(describeResp.LoadBalancers) != 1 {
return nil, "", fmt.Errorf("No load balancers returned for %s", d.Id())
}
dLb := describeResp.LoadBalancers[0]
log.Printf("[INFO] ALB state: %s", *dLb.State.Code)
return describeResp, *dLb.State.Code, nil
},
Timeout: 10 * time.Minute,
MinTimeout: 3 * time.Second,
}
_, err := stateConf.WaitForState()
if err != nil {
return err
}
return resourceAwsAlbRead(d, meta)
}
@ -368,6 +433,7 @@ func flattenAwsAlbResource(d *schema.ResourceData, meta interface{}, alb *elbv2.
d.Set("vpc_id", alb.VpcId)
d.Set("zone_id", alb.CanonicalHostedZoneId)
d.Set("dns_name", alb.DNSName)
d.Set("ip_address_type", alb.IpAddressType)
respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{
ResourceArns: []*string{alb.LoadBalancerArn},

View File

@ -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)
}

View File

@ -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)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -37,10 +38,18 @@ func resourceAwsAlbTargetGroup() *schema.Resource {
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateAwsAlbTargetGroupName,
},
"name_prefix": {
Type: schema.TypeString,
Required: true,
Optional: true,
ForceNew: true,
ValidateFunc: validateAwsAlbTargetGroupName,
ValidateFunc: validateAwsAlbTargetGroupNamePrefix,
},
"port": {
@ -73,6 +82,7 @@ func resourceAwsAlbTargetGroup() *schema.Resource {
"stickiness": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@ -171,8 +181,17 @@ func resourceAwsAlbTargetGroup() *schema.Resource {
func resourceAwsAlbTargetGroupCreate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
var groupName string
if v, ok := d.GetOk("name"); ok {
groupName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
groupName = resource.PrefixedUniqueId(v.(string))
} else {
groupName = resource.PrefixedUniqueId("tf-")
}
params := &elbv2.CreateTargetGroupInput{
Name: aws.String(d.Get("name").(string)),
Name: aws.String(groupName),
Port: aws.Int64(int64(d.Get("port").(int))),
Protocol: aws.String(d.Get("protocol").(string)),
VpcId: aws.String(d.Get("vpc_id").(string)),
@ -258,11 +277,19 @@ func resourceAwsAlbTargetGroupRead(d *schema.ResourceData, meta interface{}) err
for _, attr := range attrResp.Attributes {
switch *attr.Key {
case "stickiness.enabled":
stickinessMap["enabled"] = *attr.Value
enabled, err := strconv.ParseBool(*attr.Value)
if err != nil {
return fmt.Errorf("Error converting stickiness.enabled to bool: %s", *attr.Value)
}
stickinessMap["enabled"] = enabled
case "stickiness.type":
stickinessMap["type"] = *attr.Value
case "stickiness.lb_cookie.duration_seconds":
stickinessMap["cookie_duration"] = *attr.Value
duration, err := strconv.Atoi(*attr.Value)
if err != nil {
return fmt.Errorf("Error converting stickiness.lb_cookie.duration_seconds to int: %s", *attr.Value)
}
stickinessMap["cookie_duration"] = duration
case "deregistration_delay.timeout_seconds":
timeout, err := strconv.Atoi(*attr.Value)
if err != nil {
@ -271,7 +298,24 @@ func resourceAwsAlbTargetGroupRead(d *schema.ResourceData, meta interface{}) err
d.Set("deregistration_delay", timeout)
}
}
d.Set("stickiness", []interface{}{stickinessMap})
if err := d.Set("stickiness", []interface{}{stickinessMap}); err != nil {
return err
}
tagsResp, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{
ResourceArns: []*string{aws.String(d.Id())},
})
if err != nil {
return errwrap.Wrapf("Error retrieving Target Group Tags: {{err}}", err)
}
for _, t := range tagsResp.TagDescriptions {
if *t.ResourceArn == d.Id() {
if err := d.Set("tags", tagsToMapELBv2(t.Tags)); err != nil {
return err
}
}
}
return nil
}
@ -437,14 +481,6 @@ func validateAwsAlbTargetGroupHealthCheckProtocol(v interface{}, k string) (ws [
return
}
func validateAwsAlbTargetGroupName(v interface{}, k string) (ws []string, errors []error) {
name := v.(string)
if len(name) > 32 {
errors = append(errors, fmt.Errorf("%q (%q) cannot be longer than '32' characters", k, name))
}
return
}
func validateAwsAlbTargetGroupPort(v interface{}, k string) (ws []string, errors []error) {
port := v.(int)
if port < 1 || port > 65536 {

View File

@ -34,7 +34,7 @@ func resourceAwsAlbTargetGroupAttachment() *schema.Resource {
"port": {
Type: schema.TypeInt,
ForceNew: true,
Required: true,
Optional: true,
},
},
}
@ -43,18 +43,21 @@ func resourceAwsAlbTargetGroupAttachment() *schema.Resource {
func resourceAwsAlbAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
params := &elbv2.RegisterTargetsInput{
TargetGroupArn: aws.String(d.Get("target_group_arn").(string)),
Targets: []*elbv2.TargetDescription{
{
Id: aws.String(d.Get("target_id").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
},
},
target := &elbv2.TargetDescription{
Id: aws.String(d.Get("target_id").(string)),
}
log.Printf("[INFO] Registering Target %s (%d) with Target Group %s", d.Get("target_id").(string),
d.Get("port").(int), d.Get("target_group_arn").(string))
if v, ok := d.GetOk("port"); ok {
target.Port = aws.Int64(int64(v.(int)))
}
params := &elbv2.RegisterTargetsInput{
TargetGroupArn: aws.String(d.Get("target_group_arn").(string)),
Targets: []*elbv2.TargetDescription{target},
}
log.Printf("[INFO] Registering Target %s with Target Group %s", d.Get("target_id").(string),
d.Get("target_group_arn").(string))
_, err := elbconn.RegisterTargets(params)
if err != nil {
@ -69,14 +72,17 @@ func resourceAwsAlbAttachmentCreate(d *schema.ResourceData, meta interface{}) er
func resourceAwsAlbAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
target := &elbv2.TargetDescription{
Id: aws.String(d.Get("target_id").(string)),
}
if v, ok := d.GetOk("port"); ok {
target.Port = aws.Int64(int64(v.(int)))
}
params := &elbv2.DeregisterTargetsInput{
TargetGroupArn: aws.String(d.Get("target_group_arn").(string)),
Targets: []*elbv2.TargetDescription{
{
Id: aws.String(d.Get("target_id").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
},
},
Targets: []*elbv2.TargetDescription{target},
}
_, err := elbconn.DeregisterTargets(params)
@ -93,14 +99,18 @@ func resourceAwsAlbAttachmentDelete(d *schema.ResourceData, meta interface{}) er
// target, so there is no work to do beyond ensuring that the target and group still exist.
func resourceAwsAlbAttachmentRead(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
target := &elbv2.TargetDescription{
Id: aws.String(d.Get("target_id").(string)),
}
if v, ok := d.GetOk("port"); ok {
target.Port = aws.Int64(int64(v.(int)))
}
resp, err := elbconn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{
TargetGroupArn: aws.String(d.Get("target_group_arn").(string)),
Targets: []*elbv2.TargetDescription{
{
Id: aws.String(d.Get("target_id").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
},
},
Targets: []*elbv2.TargetDescription{target},
})
if err != nil {
if isTargetGroupNotFound(err) {

View File

@ -3,14 +3,15 @@ package aws
import (
"errors"
"fmt"
"strconv"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"strconv"
"testing"
)
func TestAccAWSALBTargetGroupAttachment_basic(t *testing.T) {
@ -32,6 +33,25 @@ func TestAccAWSALBTargetGroupAttachment_basic(t *testing.T) {
})
}
func TestAccAWSALBTargetGroupAttachment_withoutPort(t *testing.T) {
targetGroupName := fmt.Sprintf("test-target-group-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb_target_group.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBTargetGroupAttachmentDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBTargetGroupAttachmentConfigWithoutPort(targetGroupName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSALBTargetGroupAttachmentExists("aws_alb_target_group_attachment.test"),
),
},
},
})
}
func testAccCheckAWSALBTargetGroupAttachmentExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -45,15 +65,20 @@ func testAccCheckAWSALBTargetGroupAttachmentExists(n string) resource.TestCheckF
conn := testAccProvider.Meta().(*AWSClient).elbv2conn
port, _ := strconv.Atoi(rs.Primary.Attributes["port"])
_, hasPort := rs.Primary.Attributes["port"]
targetGroupArn, _ := rs.Primary.Attributes["target_group_arn"]
target := &elbv2.TargetDescription{
Id: aws.String(rs.Primary.Attributes["target_id"]),
}
if hasPort == true {
port, _ := strconv.Atoi(rs.Primary.Attributes["port"])
target.Port = aws.Int64(int64(port))
}
describe, err := conn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{
TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]),
Targets: []*elbv2.TargetDescription{
{
Id: aws.String(rs.Primary.Attributes["target_id"]),
Port: aws.Int64(int64(port)),
},
},
TargetGroupArn: aws.String(targetGroupArn),
Targets: []*elbv2.TargetDescription{target},
})
if err != nil {
@ -76,15 +101,20 @@ func testAccCheckAWSALBTargetGroupAttachmentDestroy(s *terraform.State) error {
continue
}
port, _ := strconv.Atoi(rs.Primary.Attributes["port"])
_, hasPort := rs.Primary.Attributes["port"]
targetGroupArn, _ := rs.Primary.Attributes["target_group_arn"]
target := &elbv2.TargetDescription{
Id: aws.String(rs.Primary.Attributes["target_id"]),
}
if hasPort == true {
port, _ := strconv.Atoi(rs.Primary.Attributes["port"])
target.Port = aws.Int64(int64(port))
}
describe, err := conn.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{
TargetGroupArn: aws.String(rs.Primary.Attributes["target_group_arn"]),
Targets: []*elbv2.TargetDescription{
{
Id: aws.String(rs.Primary.Attributes["target_id"]),
Port: aws.Int64(int64(port)),
},
},
TargetGroupArn: aws.String(targetGroupArn),
Targets: []*elbv2.TargetDescription{target},
})
if err == nil {
if len(describe.TargetHealthDescriptions) != 0 {
@ -103,6 +133,55 @@ func testAccCheckAWSALBTargetGroupAttachmentDestroy(s *terraform.State) error {
return nil
}
func testAccAWSALBTargetGroupAttachmentConfigWithoutPort(targetGroupName string) string {
return fmt.Sprintf(`
resource "aws_alb_target_group_attachment" "test" {
target_group_arn = "${aws_alb_target_group.test.arn}"
target_id = "${aws_instance.test.id}"
}
resource "aws_instance" "test" {
ami = "ami-f701cb97"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.subnet.id}"
}
resource "aws_alb_target_group" "test" {
name = "%s"
port = 443
protocol = "HTTPS"
vpc_id = "${aws_vpc.test.id}"
deregistration_delay = 200
stickiness {
type = "lb_cookie"
cookie_duration = 10000
}
health_check {
path = "/health"
interval = 60
port = 8081
protocol = "HTTP"
timeout = 3
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-299"
}
}
resource "aws_subnet" "subnet" {
cidr_block = "10.0.1.0/24"
vpc_id = "${aws_vpc.test.id}"
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}`, targetGroupName)
}
func testAccAWSALBTargetGroupAttachmentConfig_basic(targetGroupName string) string {
return fmt.Sprintf(`
resource "aws_alb_target_group_attachment" "test" {

View File

@ -3,6 +3,7 @@ package aws
import (
"errors"
"fmt"
"regexp"
"testing"
"github.com/aws/aws-sdk-go/aws"
@ -77,6 +78,47 @@ func TestAccAWSALBTargetGroup_basic(t *testing.T) {
resource.TestCheckResourceAttr("aws_alb_target_group.test", "health_check.0.healthy_threshold", "3"),
resource.TestCheckResourceAttr("aws_alb_target_group.test", "health_check.0.unhealthy_threshold", "3"),
resource.TestCheckResourceAttr("aws_alb_target_group.test", "health_check.0.matcher", "200-299"),
resource.TestCheckResourceAttr("aws_alb_target_group.test", "tags.%", "1"),
resource.TestCheckResourceAttr("aws_alb_target_group.test", "tags.TestName", "TestAccAWSALBTargetGroup_basic"),
),
},
},
})
}
func TestAccAWSALBTargetGroup_namePrefix(t *testing.T) {
var conf elbv2.TargetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb_target_group.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBTargetGroupConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBTargetGroupExists("aws_alb_target_group.test", &conf),
resource.TestMatchResourceAttr("aws_alb_target_group.test", "name", regexp.MustCompile("^tf-")),
),
},
},
})
}
func TestAccAWSALBTargetGroup_generatedName(t *testing.T) {
var conf elbv2.TargetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb_target_group.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBTargetGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBTargetGroupConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSALBTargetGroupExists("aws_alb_target_group.test", &conf),
),
},
},
@ -713,3 +755,28 @@ resource "aws_vpc" "test" {
}
}`, targetGroupName, stickinessBlock)
}
const testAccAWSALBTargetGroupConfig_namePrefix = `
resource "aws_alb_target_group" "test" {
name_prefix = "tf-"
port = 80
protocol = "HTTP"
vpc_id = "${aws_vpc.test.id}"
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
`
const testAccAWSALBTargetGroupConfig_generatedName = `
resource "aws_alb_target_group" "test" {
port = 80
protocol = "HTTP"
vpc_id = "${aws_vpc.test.id}"
}
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
}
`

View File

@ -67,6 +67,7 @@ func TestAccAWSALB_basic(t *testing.T) {
resource.TestCheckResourceAttr("aws_alb.alb_test", "tags.TestName", "TestAccAWSALB_basic"),
resource.TestCheckResourceAttr("aws_alb.alb_test", "enable_deletion_protection", "false"),
resource.TestCheckResourceAttr("aws_alb.alb_test", "idle_timeout", "30"),
resource.TestCheckResourceAttr("aws_alb.alb_test", "ip_address_type", "ipv4"),
resource.TestCheckResourceAttrSet("aws_alb.alb_test", "vpc_id"),
resource.TestCheckResourceAttrSet("aws_alb.alb_test", "zone_id"),
resource.TestCheckResourceAttrSet("aws_alb.alb_test", "dns_name"),
@ -179,6 +180,63 @@ func TestAccAWSALB_updatedSecurityGroups(t *testing.T) {
})
}
func TestAccAWSALB_updatedSubnets(t *testing.T) {
var pre, post elbv2.LoadBalancer
albName := fmt.Sprintf("testaccawsalb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb.alb_test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBConfig_basic(albName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSALBExists("aws_alb.alb_test", &pre),
resource.TestCheckResourceAttr("aws_alb.alb_test", "subnets.#", "2"),
),
},
{
Config: testAccAWSALBConfig_updateSubnets(albName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSALBExists("aws_alb.alb_test", &post),
resource.TestCheckResourceAttr("aws_alb.alb_test", "subnets.#", "3"),
testAccCheckAWSAlbARNs(&pre, &post),
),
},
},
})
}
func TestAccAWSALB_updatedIpAddressType(t *testing.T) {
var pre, post elbv2.LoadBalancer
albName := fmt.Sprintf("testaccawsalb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_alb.alb_test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSALBDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSALBConfigWithIpAddressType(albName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSALBExists("aws_alb.alb_test", &pre),
resource.TestCheckResourceAttr("aws_alb.alb_test", "ip_address_type", "ipv4"),
),
},
{
Config: testAccAWSALBConfigWithIpAddressTypeUpdated(albName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckAWSALBExists("aws_alb.alb_test", &post),
resource.TestCheckResourceAttr("aws_alb.alb_test", "ip_address_type", "dualstack"),
),
},
},
})
}
// TestAccAWSALB_noSecurityGroup regression tests the issue in #8264,
// where if an ALB is created without a security group, a default one
// is assigned.
@ -359,6 +417,228 @@ func testAccCheckAWSALBDestroy(s *terraform.State) error {
return nil
}
func testAccAWSALBConfigWithIpAddressTypeUpdated(albName string) string {
return fmt.Sprintf(`resource "aws_alb" "alb_test" {
name = "%s"
security_groups = ["${aws_security_group.alb_test.id}"]
subnets = ["${aws_subnet.alb_test_1.id}", "${aws_subnet.alb_test_2.id}"]
ip_address_type = "dualstack"
idle_timeout = 30
enable_deletion_protection = false
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_alb_listener" "test" {
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_target_group" "test" {
name = "%s"
port = 80
protocol = "HTTP"
vpc_id = "${aws_vpc.alb_test.id}"
deregistration_delay = 200
stickiness {
type = "lb_cookie"
cookie_duration = 10000
}
health_check {
path = "/health2"
interval = 30
port = 8082
protocol = "HTTPS"
timeout = 4
healthy_threshold = 4
unhealthy_threshold = 4
matcher = "200"
}
}
resource "aws_egress_only_internet_gateway" "igw" {
vpc_id = "${aws_vpc.alb_test.id}"
}
resource "aws_vpc" "alb_test" {
cidr_block = "10.0.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.alb_test.id}"
}
resource "aws_subnet" "alb_test_1" {
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2a"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.alb_test.ipv6_cidr_block, 8, 1)}"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_subnet" "alb_test_2" {
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "10.0.2.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2b"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.alb_test.ipv6_cidr_block, 8, 2)}"
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"]
}
tags {
TestName = "TestAccAWSALB_basic"
}
}`, albName, albName)
}
func testAccAWSALBConfigWithIpAddressType(albName string) string {
return fmt.Sprintf(`resource "aws_alb" "alb_test" {
name = "%s"
security_groups = ["${aws_security_group.alb_test.id}"]
subnets = ["${aws_subnet.alb_test_1.id}", "${aws_subnet.alb_test_2.id}"]
ip_address_type = "ipv4"
idle_timeout = 30
enable_deletion_protection = false
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_alb_listener" "test" {
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_target_group" "test" {
name = "%s"
port = 80
protocol = "HTTP"
vpc_id = "${aws_vpc.alb_test.id}"
deregistration_delay = 200
stickiness {
type = "lb_cookie"
cookie_duration = 10000
}
health_check {
path = "/health2"
interval = 30
port = 8082
protocol = "HTTPS"
timeout = 4
healthy_threshold = 4
unhealthy_threshold = 4
matcher = "200"
}
}
resource "aws_egress_only_internet_gateway" "igw" {
vpc_id = "${aws_vpc.alb_test.id}"
}
resource "aws_vpc" "alb_test" {
cidr_block = "10.0.0.0/16"
assign_generated_ipv6_cidr_block = true
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.alb_test.id}"
}
resource "aws_subnet" "alb_test_1" {
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2a"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.alb_test.ipv6_cidr_block, 8, 1)}"
tags {
TestName = "TestAccAWSALB_basic"
}
}
resource "aws_subnet" "alb_test_2" {
vpc_id = "${aws_vpc.alb_test.id}"
cidr_block = "10.0.2.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2b"
ipv6_cidr_block = "${cidrsubnet(aws_vpc.alb_test.ipv6_cidr_block, 8, 2)}"
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"]
}
tags {
TestName = "TestAccAWSALB_basic"
}
}`, albName, albName)
}
func testAccAWSALBConfig_basic(albName string) string {
return fmt.Sprintf(`resource "aws_alb" "alb_test" {
name = "%s"
@ -426,6 +706,73 @@ resource "aws_security_group" "alb_test" {
}`, albName)
}
func testAccAWSALBConfig_updateSubnets(albName string) string {
return fmt.Sprintf(`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"
}
}
variable "subnets" {
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.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 = 3
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)
}
func testAccAWSALBConfig_generatedName() string {
return fmt.Sprintf(`
resource "aws_alb" "alb_test" {

View File

@ -18,9 +18,10 @@ import (
)
const (
AWSAMIRetryTimeout = 10 * time.Minute
AWSAMIRetryDelay = 5 * time.Second
AWSAMIRetryMinTimeout = 3 * time.Second
AWSAMIRetryTimeout = 40 * time.Minute
AWSAMIDeleteRetryTimeout = 90 * time.Minute
AWSAMIRetryDelay = 5 * time.Second
AWSAMIRetryMinTimeout = 3 * time.Second
)
func resourceAwsAmi() *schema.Resource {
@ -329,7 +330,7 @@ func resourceAwsAmiWaitForDestroy(id string, client *ec2.EC2) error {
Pending: []string{"available", "pending", "failed"},
Target: []string{"destroyed"},
Refresh: AMIStateRefreshFunc(client, id),
Timeout: AWSAMIRetryTimeout,
Timeout: AWSAMIDeleteRetryTimeout,
Delay: AWSAMIRetryDelay,
MinTimeout: AWSAMIRetryTimeout,
}

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"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"
)
@ -16,13 +17,14 @@ import (
func TestAccAWSAMIFromInstance(t *testing.T) {
var amiId string
snapshots := []string{}
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAMIFromInstanceConfig,
{
Config: testAccAWSAMIFromInstanceConfig(rInt),
Check: func(state *terraform.State) error {
rs, ok := state.RootModule().Resources["aws_ami_from_instance.test"]
if !ok {
@ -51,13 +53,13 @@ func TestAccAWSAMIFromInstance(t *testing.T) {
image := describe.Images[0]
if expected := "available"; *image.State != expected {
return fmt.Errorf("invalid image state; expected %v, got %v", expected, image.State)
return fmt.Errorf("invalid image state; expected %v, got %v", expected, *image.State)
}
if expected := "machine"; *image.ImageType != expected {
return fmt.Errorf("wrong image type; expected %v, got %v", expected, image.ImageType)
return fmt.Errorf("wrong image type; expected %v, got %v", expected, *image.ImageType)
}
if expected := "terraform-acc-ami-from-instance"; *image.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, image.Name)
if expected := fmt.Sprintf("terraform-acc-ami-from-instance-%d", rInt); *image.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, *image.Name)
}
for _, bdm := range image.BlockDeviceMappings {
@ -137,24 +139,25 @@ func TestAccAWSAMIFromInstance(t *testing.T) {
})
}
var testAccAWSAMIFromInstanceConfig = `
provider "aws" {
region = "us-east-1"
}
func testAccAWSAMIFromInstanceConfig(rInt int) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "test" {
// This AMI has one block device mapping, so we expect to have
// one snapshot in our created AMI.
ami = "ami-408c7f28"
instance_type = "t1.micro"
tags {
Name = "testAccAWSAMIFromInstanceConfig_TestAMI"
}
}
resource "aws_instance" "test" {
// This AMI has one block device mapping, so we expect to have
// one snapshot in our created AMI.
ami = "ami-408c7f28"
instance_type = "t1.micro"
tags {
Name = "testAccAWSAMIFromInstanceConfig_TestAMI"
}
}
resource "aws_ami_from_instance" "test" {
name = "terraform-acc-ami-from-instance"
description = "Testing Terraform aws_ami_from_instance resource"
source_instance_id = "${aws_instance.test.id}"
resource "aws_ami_from_instance" "test" {
name = "terraform-acc-ami-from-instance-%d"
description = "Testing Terraform aws_ami_from_instance resource"
source_instance_id = "${aws_instance.test.id}"
}`, rInt)
}
`

View File

@ -2,7 +2,11 @@ package aws
import (
"fmt"
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/schema"
)
@ -92,6 +96,12 @@ func hasLaunchPermission(conn *ec2.EC2, image_id string, account_id string) (boo
Attribute: aws.String("launchPermission"),
})
if err != nil {
// When an AMI disappears out from under a launch permission resource, we will
// see either InvalidAMIID.NotFound or InvalidAMIID.Unavailable.
if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidAMIID") {
log.Printf("[DEBUG] %s no longer exists, so we'll drop launch permission for %s from the state", image_id, account_id)
return false, nil
}
return false, err
}

View File

@ -2,15 +2,18 @@ package aws
import (
"fmt"
r "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
r "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSAMILaunchPermission_Basic(t *testing.T) {
image_id := ""
account_id := os.Getenv("AWS_ACCOUNT_ID")
imageID := ""
accountID := os.Getenv("AWS_ACCOUNT_ID")
r.Test(t, r.TestCase{
PreCheck: func() {
@ -23,19 +26,36 @@ func TestAccAWSAMILaunchPermission_Basic(t *testing.T) {
Steps: []r.TestStep{
// Scaffold everything
r.TestStep{
Config: testAccAWSAMILaunchPermissionConfig(account_id, true),
Config: testAccAWSAMILaunchPermissionConfig(accountID, true),
Check: r.ComposeTestCheckFunc(
testCheckResourceGetAttr("aws_ami_copy.test", "id", &image_id),
testAccAWSAMILaunchPermissionExists(account_id, &image_id),
testCheckResourceGetAttr("aws_ami_copy.test", "id", &imageID),
testAccAWSAMILaunchPermissionExists(accountID, &imageID),
),
},
// Drop just launch permission to test destruction
r.TestStep{
Config: testAccAWSAMILaunchPermissionConfig(account_id, false),
Config: testAccAWSAMILaunchPermissionConfig(accountID, false),
Check: r.ComposeTestCheckFunc(
testAccAWSAMILaunchPermissionDestroyed(account_id, &image_id),
testAccAWSAMILaunchPermissionDestroyed(accountID, &imageID),
),
},
// Re-add everything so we can test when AMI disappears
r.TestStep{
Config: testAccAWSAMILaunchPermissionConfig(accountID, true),
Check: r.ComposeTestCheckFunc(
testCheckResourceGetAttr("aws_ami_copy.test", "id", &imageID),
testAccAWSAMILaunchPermissionExists(accountID, &imageID),
),
},
// Here we delete the AMI to verify the follow-on refresh after this step
// should not error.
r.TestStep{
Config: testAccAWSAMILaunchPermissionConfig(accountID, true),
Check: r.ComposeTestCheckFunc(
testAccAWSAMIDisappears(&imageID),
),
ExpectNonEmptyPlan: true,
},
},
})
}
@ -58,31 +78,53 @@ func testCheckResourceGetAttr(name, key string, value *string) r.TestCheckFunc {
}
}
func testAccAWSAMILaunchPermissionExists(account_id string, image_id *string) r.TestCheckFunc {
func testAccAWSAMILaunchPermissionExists(accountID string, imageID *string) r.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil {
if has, err := hasLaunchPermission(conn, *imageID, accountID); err != nil {
return err
} else if !has {
return fmt.Errorf("launch permission does not exist for '%s' on '%s'", account_id, *image_id)
return fmt.Errorf("launch permission does not exist for '%s' on '%s'", accountID, *imageID)
}
return nil
}
}
func testAccAWSAMILaunchPermissionDestroyed(account_id string, image_id *string) r.TestCheckFunc {
func testAccAWSAMILaunchPermissionDestroyed(accountID string, imageID *string) r.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil {
if has, err := hasLaunchPermission(conn, *imageID, accountID); err != nil {
return err
} else if has {
return fmt.Errorf("launch permission still exists for '%s' on '%s'", account_id, *image_id)
return fmt.Errorf("launch permission still exists for '%s' on '%s'", accountID, *imageID)
}
return nil
}
}
func testAccAWSAMILaunchPermissionConfig(account_id string, includeLaunchPermission bool) string {
// testAccAWSAMIDisappears is technically a "test check function" but really it
// exists to perform a side effect of deleting an AMI out from under a resource
// so we can test that Terraform will react properly
func testAccAWSAMIDisappears(imageID *string) r.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
req := &ec2.DeregisterImageInput{
ImageId: aws.String(*imageID),
}
_, err := conn.DeregisterImage(req)
if err != nil {
return err
}
if err := resourceAwsAmiWaitForDestroy(*imageID, conn); err != nil {
return err
}
return nil
}
}
func testAccAWSAMILaunchPermissionConfig(accountID string, includeLaunchPermission bool) string {
base := `
resource "aws_ami_copy" "test" {
name = "launch-permission-test"
@ -101,5 +143,5 @@ resource "aws_ami_launch_permission" "self-test" {
image_id = "${aws_ami_copy.test.id}"
account_id = "%s"
}
`, account_id)
`, accountID)
}

View File

@ -42,8 +42,9 @@ func resourceAwsApiGatewayApiKey() *schema.Resource {
},
"stage_key": {
Type: schema.TypeSet,
Optional: true,
Type: schema.TypeSet,
Optional: true,
Deprecated: "Since the API Gateway usage plans feature was launched on August 11, 2016, usage plans are now required to associate an API key with an API stage",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"rest_api_id": {
@ -68,6 +69,15 @@ func resourceAwsApiGatewayApiKey() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"value": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Sensitive: true,
ValidateFunc: validateApiGatewayApiKeyValue,
},
},
}
}
@ -80,6 +90,7 @@ func resourceAwsApiGatewayApiKeyCreate(d *schema.ResourceData, meta interface{})
Name: aws.String(d.Get("name").(string)),
Description: aws.String(d.Get("description").(string)),
Enabled: aws.Bool(d.Get("enabled").(bool)),
Value: aws.String(d.Get("value").(string)),
StageKeys: expandApiGatewayStageKeys(d),
})
if err != nil {
@ -96,7 +107,8 @@ func resourceAwsApiGatewayApiKeyRead(d *schema.ResourceData, meta interface{}) e
log.Printf("[DEBUG] Reading API Gateway API Key: %s", d.Id())
apiKey, err := conn.GetApiKey(&apigateway.GetApiKeyInput{
ApiKey: aws.String(d.Id()),
ApiKey: aws.String(d.Id()),
IncludeValue: aws.Bool(true),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFoundException" {
@ -111,6 +123,7 @@ func resourceAwsApiGatewayApiKeyRead(d *schema.ResourceData, meta interface{}) e
d.Set("description", apiKey.Description)
d.Set("enabled", apiKey.Enabled)
d.Set("stage_key", flattenApiGatewayStageKeys(apiKey.StageKeys))
d.Set("value", apiKey.Value)
if err := d.Set("created_date", apiKey.CreatedDate.Format(time.RFC3339)); err != nil {
log.Printf("[DEBUG] Error setting created_date: %s", err)

View File

@ -33,6 +33,8 @@ func TestAccAWSAPIGatewayApiKey_basic(t *testing.T) {
"aws_api_gateway_api_key.test", "created_date"),
resource.TestCheckResourceAttrSet(
"aws_api_gateway_api_key.test", "last_updated_date"),
resource.TestCheckResourceAttr(
"aws_api_gateway_api_key.custom", "value", "MyCustomToken#@&\"'(§!ç)-_*$€¨^£%ù+=/:.;?,|"),
),
},
},
@ -176,4 +178,15 @@ resource "aws_api_gateway_api_key" "test" {
stage_name = "${aws_api_gateway_deployment.test.stage_name}"
}
}
resource "aws_api_gateway_api_key" "custom" {
name = "bar"
enabled = true
value = "MyCustomToken#@&\"'(§!ç)-_*$€¨^£%ù+=/:.;?,|"
stage_key {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
stage_name = "${aws_api_gateway_deployment.test.stage_name}"
}
}
`

View File

@ -48,6 +48,7 @@ func resourceAwsApiGatewayDomainName() *schema.Resource {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Sensitive: true,
ConflictsWith: []string{"certificate_arn"},
},

View File

@ -11,87 +11,94 @@ import (
"github.com/aws/aws-sdk-go/service/apigateway"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"strings"
)
func resourceAwsApiGatewayIntegration() *schema.Resource {
return &schema.Resource{
Create: resourceAwsApiGatewayIntegrationCreate,
Read: resourceAwsApiGatewayIntegrationRead,
Update: resourceAwsApiGatewayIntegrationCreate,
Update: resourceAwsApiGatewayIntegrationUpdate,
Delete: resourceAwsApiGatewayIntegrationDelete,
Schema: map[string]*schema.Schema{
"rest_api_id": &schema.Schema{
"rest_api_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"resource_id": &schema.Schema{
"resource_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"http_method": &schema.Schema{
"http_method": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateHTTPMethod,
},
"type": &schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateApiGatewayIntegrationType,
},
"uri": &schema.Schema{
"uri": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"credentials": &schema.Schema{
"credentials": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"integration_http_method": &schema.Schema{
"integration_http_method": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateHTTPMethod,
},
"request_templates": &schema.Schema{
"request_templates": {
Type: schema.TypeMap,
Optional: true,
Elem: schema.TypeString,
},
"request_parameters": &schema.Schema{
"request_parameters": {
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ConflictsWith: []string{"request_parameters_in_json"},
},
"request_parameters_in_json": &schema.Schema{
"request_parameters_in_json": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"request_parameters"},
Deprecated: "Use field request_parameters instead",
},
"content_handling": &schema.Schema{
"content_handling": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateApiGatewayIntegrationContentHandling,
},
"passthrough_behavior": &schema.Schema{
"passthrough_behavior": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateApiGatewayIntegrationPassthroughBehavior,
},
},
@ -101,6 +108,7 @@ func resourceAwsApiGatewayIntegration() *schema.Resource {
func resourceAwsApiGatewayIntegrationCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).apigateway
log.Print("[DEBUG] Creating API Gateway Integration")
var integrationHttpMethod *string
if v, ok := d.GetOk("integration_http_method"); ok {
integrationHttpMethod = aws.String(v.(string))
@ -163,13 +171,13 @@ func resourceAwsApiGatewayIntegrationCreate(d *schema.ResourceData, meta interfa
d.SetId(fmt.Sprintf("agi-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string)))
return nil
return resourceAwsApiGatewayIntegrationRead(d, meta)
}
func resourceAwsApiGatewayIntegrationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).apigateway
log.Printf("[DEBUG] Reading API Gateway Integration %s", d.Id())
log.Printf("[DEBUG] Reading API Gateway Integration: %s", d.Id())
integration, err := conn.GetIntegration(&apigateway.GetIntegrationInput{
HttpMethod: aws.String(d.Get("http_method").(string)),
ResourceId: aws.String(d.Get("resource_id").(string)),
@ -191,17 +199,127 @@ func resourceAwsApiGatewayIntegrationRead(d *schema.ResourceData, meta interface
}
d.Set("request_templates", aws.StringValueMap(integration.RequestTemplates))
d.Set("credentials", integration.Credentials)
d.Set("type", integration.Type)
d.Set("uri", integration.Uri)
d.Set("request_parameters", aws.StringValueMap(integration.RequestParameters))
d.Set("request_parameters_in_json", aws.StringValueMap(integration.RequestParameters))
d.Set("passthrough_behavior", integration.PassthroughBehavior)
d.Set("content_handling", integration.ContentHandling)
if integration.Uri != nil {
d.Set("uri", integration.Uri)
}
if integration.Credentials != nil {
d.Set("credentials", integration.Credentials)
}
if integration.ContentHandling != nil {
d.Set("content_handling", integration.ContentHandling)
}
return nil
}
func resourceAwsApiGatewayIntegrationUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).apigateway
log.Printf("[DEBUG] Updating API Gateway Integration: %s", d.Id())
operations := make([]*apigateway.PatchOperation, 0)
// https://docs.aws.amazon.com/apigateway/api-reference/link-relation/integration-update/#remarks
// According to the above documentation, only a few parts are addable / removable.
if d.HasChange("request_templates") {
o, n := d.GetChange("request_templates")
prefix := "requestTemplates"
os := o.(map[string]interface{})
ns := n.(map[string]interface{})
// Handle Removal
for k := range os {
if _, ok := ns[k]; !ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("remove"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
})
}
}
for k, v := range ns {
// Handle replaces
if _, ok := os[k]; ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("replace"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
Value: aws.String(v.(string)),
})
}
// Handle additions
if _, ok := os[k]; !ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("add"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
Value: aws.String(v.(string)),
})
}
}
}
if d.HasChange("request_parameters") {
o, n := d.GetChange("request_parameters")
prefix := "requestParameters"
os := o.(map[string]interface{})
ns := n.(map[string]interface{})
// Handle Removal
for k := range os {
if _, ok := ns[k]; !ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("remove"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
})
}
}
for k, v := range ns {
// Handle replaces
if _, ok := os[k]; ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("replace"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
Value: aws.String(v.(string)),
})
}
// Handle additions
if _, ok := os[k]; !ok {
operations = append(operations, &apigateway.PatchOperation{
Op: aws.String("add"),
Path: aws.String(fmt.Sprintf("/%s/%s", prefix, strings.Replace(k, "/", "~1", -1))),
Value: aws.String(v.(string)),
})
}
}
}
params := &apigateway.UpdateIntegrationInput{
HttpMethod: aws.String(d.Get("http_method").(string)),
ResourceId: aws.String(d.Get("resource_id").(string)),
RestApiId: aws.String(d.Get("rest_api_id").(string)),
PatchOperations: operations,
}
_, err := conn.UpdateIntegration(params)
if err != nil {
return fmt.Errorf("Error updating API Gateway Integration: %s", err)
}
d.SetId(fmt.Sprintf("agi-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string)))
return resourceAwsApiGatewayIntegrationRead(d, meta)
}
func resourceAwsApiGatewayIntegrationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).apigateway
log.Printf("[DEBUG] Deleting API Gateway Integration: %s", d.Id())

View File

@ -19,88 +19,80 @@ func TestAccAWSAPIGatewayIntegration_basic(t *testing.T) {
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAPIGatewayIntegrationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccAWSAPIGatewayIntegrationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayIntegrationExists("aws_api_gateway_integration.test", &conf),
testAccCheckAWSAPIGatewayIntegrationAttributes(&conf),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "type", "HTTP"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "integration_http_method", "GET"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "uri", "https://www.google.de"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "request_templates.application/json", ""),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "request_templates.application/xml", "#set($inputRoot = $input.path('$'))\n{ }"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "passthrough_behavior", "WHEN_NO_MATCH"),
resource.TestCheckNoResourceAttr(
"aws_api_gateway_integration.test", "content_handling"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "type", "HTTP"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "integration_http_method", "GET"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "uri", "https://www.google.de"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "passthrough_behavior", "WHEN_NO_MATCH"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "content_handling", "CONVERT_TO_TEXT"),
resource.TestCheckNoResourceAttr("aws_api_gateway_integration.test", "credentials"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.integration.request.header.X-Authorization", "'static'"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.integration.request.header.X-Foo", "'Bar'"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.application/json", ""),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.application/xml", "#set($inputRoot = $input.path('$'))\n{ }"),
),
},
resource.TestStep{
{
Config: testAccAWSAPIGatewayIntegrationConfigUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayIntegrationExists("aws_api_gateway_integration.test", &conf),
testAccCheckAWSAPIGatewayMockIntegrationAttributes(&conf),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "type", "MOCK"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "integration_http_method", ""),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "uri", ""),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "passthrough_behavior", "NEVER"),
resource.TestCheckResourceAttr(
"aws_api_gateway_integration.test", "content_handling", "CONVERT_TO_BINARY"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "type", "HTTP"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "integration_http_method", "GET"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "uri", "https://www.google.de"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "passthrough_behavior", "WHEN_NO_MATCH"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "content_handling", "CONVERT_TO_TEXT"),
resource.TestCheckNoResourceAttr("aws_api_gateway_integration.test", "credentials"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.integration.request.header.X-Authorization", "'updated'"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.integration.request.header.X-FooBar", "'Baz'"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.application/json", "{'foobar': 'bar}"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.text/html", "<html>Foo</html>"),
),
},
{
Config: testAccAWSAPIGatewayIntegrationConfigUpdateNoTemplates,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayIntegrationExists("aws_api_gateway_integration.test", &conf),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "type", "HTTP"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "integration_http_method", "GET"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "uri", "https://www.google.de"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "passthrough_behavior", "WHEN_NO_MATCH"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "content_handling", "CONVERT_TO_TEXT"),
resource.TestCheckNoResourceAttr("aws_api_gateway_integration.test", "credentials"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.%", "0"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.%", "0"),
),
},
{
Config: testAccAWSAPIGatewayIntegrationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayIntegrationExists("aws_api_gateway_integration.test", &conf),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "type", "HTTP"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "integration_http_method", "GET"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "uri", "https://www.google.de"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "passthrough_behavior", "WHEN_NO_MATCH"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "content_handling", "CONVERT_TO_TEXT"),
resource.TestCheckNoResourceAttr("aws_api_gateway_integration.test", "credentials"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_parameters.integration.request.header.X-Authorization", "'static'"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.%", "2"),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.application/json", ""),
resource.TestCheckResourceAttr("aws_api_gateway_integration.test", "request_templates.application/xml", "#set($inputRoot = $input.path('$'))\n{ }"),
),
},
},
})
}
func testAccCheckAWSAPIGatewayMockIntegrationAttributes(conf *apigateway.Integration) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *conf.Type != "MOCK" {
return fmt.Errorf("Wrong Type: %q", *conf.Type)
}
if *conf.RequestParameters["integration.request.header.X-Authorization"] != "'updated'" {
return fmt.Errorf("wrong updated RequestParameters for header.X-Authorization")
}
if *conf.ContentHandling != "CONVERT_TO_BINARY" {
return fmt.Errorf("wrong ContentHandling: %q", *conf.ContentHandling)
}
return nil
}
}
func testAccCheckAWSAPIGatewayIntegrationAttributes(conf *apigateway.Integration) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *conf.HttpMethod == "" {
return fmt.Errorf("empty HttpMethod")
}
if *conf.Uri != "https://www.google.de" {
return fmt.Errorf("wrong Uri")
}
if *conf.Type != "HTTP" {
return fmt.Errorf("wrong Type")
}
if conf.RequestTemplates["application/json"] != nil {
return fmt.Errorf("wrong RequestTemplate for application/json")
}
if *conf.RequestTemplates["application/xml"] != "#set($inputRoot = $input.path('$'))\n{ }" {
return fmt.Errorf("wrong RequestTemplate for application/xml")
}
if *conf.RequestParameters["integration.request.header.X-Authorization"] != "'static'" {
return fmt.Errorf("wrong RequestParameters for header.X-Authorization")
}
return nil
}
}
func testAccCheckAWSAPIGatewayIntegrationExists(n string, res *apigateway.Integration) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -196,13 +188,15 @@ resource "aws_api_gateway_integration" "test" {
}
request_parameters = {
"integration.request.header.X-Authorization" = "'static'"
"integration.request.header.X-Authorization" = "'static'"
"integration.request.header.X-Foo" = "'Bar'"
}
type = "HTTP"
uri = "https://www.google.de"
integration_http_method = "GET"
passthrough_behavior = "WHEN_NO_MATCH"
content_handling = "CONVERT_TO_TEXT"
}
`
@ -233,13 +227,55 @@ resource "aws_api_gateway_integration" "test" {
resource_id = "${aws_api_gateway_resource.test.id}"
http_method = "${aws_api_gateway_method.test.http_method}"
request_parameters = {
"integration.request.header.X-Authorization" = "'updated'"
request_templates = {
"application/json" = "{'foobar': 'bar}"
"text/html" = "<html>Foo</html>"
}
type = "MOCK"
passthrough_behavior = "NEVER"
content_handling = "CONVERT_TO_BINARY"
request_parameters = {
"integration.request.header.X-Authorization" = "'updated'"
"integration.request.header.X-FooBar" = "'Baz'"
}
type = "HTTP"
uri = "https://www.google.de"
integration_http_method = "GET"
passthrough_behavior = "WHEN_NO_MATCH"
content_handling = "CONVERT_TO_TEXT"
}
`
const testAccAWSAPIGatewayIntegrationConfigUpdateNoTemplates = `
resource "aws_api_gateway_rest_api" "test" {
name = "test"
}
resource "aws_api_gateway_resource" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
parent_id = "${aws_api_gateway_rest_api.test.root_resource_id}"
path_part = "test"
}
resource "aws_api_gateway_method" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_resource.test.id}"
http_method = "GET"
authorization = "NONE"
request_models = {
"application/json" = "Error"
}
}
resource "aws_api_gateway_integration" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_resource.test.id}"
http_method = "${aws_api_gateway_method.test.http_method}"
type = "HTTP"
uri = "https://www.google.de"
integration_http_method = "GET"
passthrough_behavior = "WHEN_NO_MATCH"
content_handling = "CONVERT_TO_TEXT"
}
`

View File

@ -15,6 +15,7 @@ import (
func TestAccAWSAPIGatewayMethod_basic(t *testing.T) {
var conf apigateway.Method
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -22,7 +23,7 @@ func TestAccAWSAPIGatewayMethod_basic(t *testing.T) {
CheckDestroy: testAccCheckAWSAPIGatewayMethodDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSAPIGatewayMethodConfig,
Config: testAccAWSAPIGatewayMethodConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayMethodExists("aws_api_gateway_method.test", &conf),
testAccCheckAWSAPIGatewayMethodAttributes(&conf),
@ -36,7 +37,7 @@ func TestAccAWSAPIGatewayMethod_basic(t *testing.T) {
},
{
Config: testAccAWSAPIGatewayMethodConfigUpdate,
Config: testAccAWSAPIGatewayMethodConfigUpdate(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayMethodExists("aws_api_gateway_method.test", &conf),
testAccCheckAWSAPIGatewayMethodAttributesUpdate(&conf),
@ -72,7 +73,7 @@ func TestAccAWSAPIGatewayMethod_customauthorizer(t *testing.T) {
},
{
Config: testAccAWSAPIGatewayMethodConfigUpdate,
Config: testAccAWSAPIGatewayMethodConfigUpdate(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAPIGatewayMethodExists("aws_api_gateway_method.test", &conf),
testAccCheckAWSAPIGatewayMethodAttributesUpdate(&conf),
@ -199,7 +200,7 @@ func testAccCheckAWSAPIGatewayMethodDestroy(s *terraform.State) error {
func testAccAWSAPIGatewayMethodConfigWithCustomAuthorizer(rInt int) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = "tf-acc-test-custom-auth"
name = "tf-acc-test-custom-auth-%d"
}
resource "aws_iam_role" "invocation_role" {
@ -261,7 +262,7 @@ EOF
resource "aws_lambda_function" "authorizer" {
filename = "test-fixtures/lambdatest.zip"
source_code_hash = "${base64sha256(file("test-fixtures/lambdatest.zip"))}"
function_name = "tf_acc_api_gateway_authorizer"
function_name = "tf_acc_api_gateway_authorizer_%d"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
runtime = "nodejs4.3"
@ -295,12 +296,13 @@ resource "aws_api_gateway_method" "test" {
"method.request.header.Content-Type" = false
"method.request.querystring.page" = true
}
}`, rInt, rInt, rInt)
}`, rInt, rInt, rInt, rInt, rInt)
}
const testAccAWSAPIGatewayMethodConfig = `
func testAccAWSAPIGatewayMethodConfig(rInt int) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = "test"
name = "tf-acc-test-apig-method-%d"
}
resource "aws_api_gateway_resource" "test" {
@ -324,11 +326,13 @@ resource "aws_api_gateway_method" "test" {
"method.request.querystring.page" = true
}
}
`
`, rInt)
}
const testAccAWSAPIGatewayMethodConfigUpdate = `
func testAccAWSAPIGatewayMethodConfigUpdate(rInt int) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = "test"
name = "tf-acc-test-apig-method-%d"
}
resource "aws_api_gateway_resource" "test" {
@ -351,4 +355,5 @@ resource "aws_api_gateway_method" "test" {
"method.request.querystring.page" = false
}
}
`
`, rInt)
}

View File

@ -172,7 +172,7 @@ func resourceAwsApiGatewayRestApiDelete(d *schema.ResourceData, meta interface{}
conn := meta.(*AWSClient).apigateway
log.Printf("[DEBUG] Deleting API Gateway: %s", d.Id())
return resource.Retry(5*time.Minute, func() *resource.RetryError {
return resource.Retry(10*time.Minute, func() *resource.RetryError {
_, err := conn.DeleteRestApi(&apigateway.DeleteRestApiInput{
RestApiId: aws.String(d.Id()),
})

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