Merge branch hashicorp/master into dtolnay/conflict

This commit is contained in:
David Tolnay 2016-09-07 19:17:22 -07:00
commit 61185f083c
1689 changed files with 176404 additions and 24677 deletions

View File

@ -373,7 +373,7 @@ to a single resource. Most tests follow a similar structure.
1. Pre-flight checks are made to ensure that sufficient provider configuration
is available to be able to proceed - for example in an acceptance test
targetting AWS, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_KEY` must be set prior
targetting AWS, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` must be set prior
to running acceptance tests. This is common to all tests exercising a single
provider.

View File

@ -1,7 +1,7 @@
sudo: false
language: go
go:
- 1.6
- 1.7
install:
# This script is used by the Travis build to install a cookie for
# go.googlesource.com so rate limits are higher when using `go get` to fetch

View File

@ -1,173 +1,545 @@
## 0.7 (Unreleased)
BACKWARDS INCOMPATIBILITIES / NOTES:
* Terraform's built-in plugins are now distributed as part of the main Terraform binary, and use the go-plugin framework. Overrides are still available using separate binaries, but will need recompiling against Terraform 0.7.
* The `terraform plan` command no longer persists state. This makes the command much safer to run, since it is now side-effect free. The `refresh` and `apply` commands still persist state to local and remote storage. Any automation that assumes that `terraform plan` persists state will need to be reworked to explicitly call `terraform refresh` to get the equivalent side-effect. (The `terraform plan` command no longer has the `-state-out` or `-backup` flags due to this change.)
* The `concat()` interpolation function can no longer be used to join strings.
* Quotation marks may no longer be escaped in HIL expressions [GH-7201]
* `openstack_networking_subnet_v2` now defaults to turning DHCP on.
* `aws_elb` now defaults `cross_zone_load_balancing` to `true`
* `resource_aws_instance`: EC2 Classic users may continue to use
`security_groups` to reference Security Groups by their `name`. Users who are
managing Instances inside VPCs will need to use `vpc_security_group_ids` instead,
and reference the security groups by their `id`.
Ref https://github.com/hashicorp/terraform/issues/6416#issuecomment-219145065
* Lists materialized using splat syntax, for example `aws_instance.foo.*.id` are now ordered by the count index rather than lexographically sorted. If this produces a large number of undesirable differences, you can use the new `sort()` interpolation function to produce the previous behaviour.
* `aws_route53_record`: `latency_routing_policy`, `geolocation_routing_policy`, and `failover_routing_policy` block options have been added. With these additions weve renamed the `weight` attribute to `weighted_routing_policy`, and it has changed from a string to a block to match the others. Please see the updated documentation on using `weighted_routing_policy`: https://www.terraform.io/docs/providers/aws/r/route53_record.html . [GH-6954]
* You now access the values of maps using the syntax `var.map["key"]` or the `lookup` function instead of `var.map.key`.
* Outputs on `terraform_remote_state` resources are now top level attributes rather than inside the `output` map. In order to access outputs, use the syntax: `terraform_remote_state.name.outputname`. Currently outputs cannot be named `config` or `backend`.
* `azurerm_dns_cname_record` now accepts a single record rather than a list of records
* `aws_db_instance` now defaults `publicly_accessible` to false
* `openstack_fw_policy_v1` now correctly applies rules in the order they are specified. Upon the next apply, current rules might be re-ordered.
## 0.7.4 (Unreleased)
FEATURES:
* **Data sources** are a new kind of primitive in Terraform. Attributes for data sources are refreshed and available during the planning stage. [GH-6598]
* **Lists and maps** can now be used as first class types for variables and may also be passed between modules. [GH-6322]
* **State management CLI commands** provide a variety of state manipulation functions for advanced use cases. This should be used where possible instead of manually modifying state files. [GH-5811]
* **State Import** allows a way to import existing resources into Terraform state for many types of resource. Initial coverage of AWS is quite high, and it is straightforward to add support for new resources.
IMPROVEMENTS:
* provider/aws: Support 'publish' attribute in lambda_function [GH-8653]
* provider/google: Resources depending on the `network` attribute can now reference the network by `self_link` or `name` [GH-8639]
* provider/postgresql: The standard environment variables PGHOST, PGUSER, PGPASSWORD and PGSSLMODE are now supported for provider configuration [GH-8666]
BUG FIXES:
* provider/aws: Bump `aws_elasticsearch_domain` timeout values [GH-672]
* provider/aws: `aws_nat_gateways` will now recreate on `failed` state [GH-8689]
* provider/scaleway: fix security_group_rule identification [GH-8661]
## 0.7.3 (September 5, 2016)
BACKWARDS INCOMPATIBILITIES / NOTES:
* Terraform now validates the uniqueness of variable and output names in your configurations. In prior versions certain ways of duplicating variable names would work. This is now a configuration error (and should've always been). If you get an error running Terraform you may need to remove the duplicates. Done right, this should not affect the behavior of Terraform.
* The internal structure of `.terraform/modules` changed slightly. For configurations with modules, you'll need to run `terraform get` again.
FEATURES:
* **New Provider:** `rabbitmq` ([#7694](https://github.com/hashicorp/terraform/issues/7694))
* **New Data Source:** `aws_cloudformation_stack` ([#8640](https://github.com/hashicorp/terraform/issues/8640))
* **New Resource:** `aws_cloudwatch_log_stream` ([#8626](https://github.com/hashicorp/terraform/issues/8626))
* **New Resource:** `aws_default_route_table` ([#8323](https://github.com/hashicorp/terraform/issues/8323))
* **New Resource:** `aws_spot_datafeed_subscription` ([#8640](https://github.com/hashicorp/terraform/issues/8640))
* **New Resource:** `aws_s3_bucket_policy` ([#8615](https://github.com/hashicorp/terraform/issues/8615))
* **New Resource:** `aws_sns_topic_policy` ([#8654](https://github.com/hashicorp/terraform/issues/8654))
* **New Resource:** `aws_sqs_queue_policy` ([#8657](https://github.com/hashicorp/terraform/issues/8657))
* **New Resource:** `aws_ssm_association` ([#8376](https://github.com/hashicorp/terraform/issues/8376))
* **New Resource:** `cloudstack_affinity_group` ([#8360](https://github.com/hashicorp/terraform/issues/8360))
* **New Resource:** `librato_alert` ([#8170](https://github.com/hashicorp/terraform/issues/8170))
* **New Resource:** `librato_service` ([#8170](https://github.com/hashicorp/terraform/issues/8170))
* **New Remote State Backend:** `local` ([#8647](https://github.com/hashicorp/terraform/issues/8647))
* Data source blocks can now have a count associated with them ([#8635](https://github.com/hashicorp/terraform/issues/8635))
* The count of a resource can now be referenced for interpolations: `self.count` and `type.name.count` work ([#8581](https://github.com/hashicorp/terraform/issues/8581))
* Provisioners now support connection using IPv6 in addition to IPv4 ([#6616](https://github.com/hashicorp/terraform/issues/6616))
IMPROVEMENTS:
* core: Add wildcard (match all) support to `ignore_changes` ([#8599](https://github.com/hashicorp/terraform/issues/8599))
* core: HTTP module sources can now use netrc files for auth
* core: Show last resource state in a timeout error message ([#8510](https://github.com/hashicorp/terraform/issues/8510))
* helper/schema: Add diff suppression callback ([#8585](https://github.com/hashicorp/terraform/issues/8585))
* provider/aws: API Gateway Custom Authorizer ([#8535](https://github.com/hashicorp/terraform/issues/8535))
* provider/aws: Add MemoryReservation To `aws_ecs_container_definition` data source ([#8437](https://github.com/hashicorp/terraform/issues/8437))
* provider/aws: Add ability Enable/Disable For ELB Access logs ([#8438](https://github.com/hashicorp/terraform/issues/8438))
* provider/aws: Add support for assuming a role prior to performing API operations ([#8638](https://github.com/hashicorp/terraform/issues/8638))
* provider/aws: Export `arn` of `aws_autoscaling_group` ([#8503](https://github.com/hashicorp/terraform/issues/8503))
* provider/aws: More robust handling of Lambda function archives hosted on S3 ([#6860](https://github.com/hashicorp/terraform/issues/6860))
* provider/aws: Spurious diffs of `aws_s3_bucket` policy attributes due to JSON field ordering are reduced ([#8615](https://github.com/hashicorp/terraform/issues/8615))
* provider/aws: `name_regex` attribute for local post-filtering of `aws_ami` data source results ([#8403](https://github.com/hashicorp/terraform/issues/8403))
* provider/aws: Support for lifecycle hooks at ASG creation ([#5620](https://github.com/hashicorp/terraform/issues/5620))
* provider/consul: Make provider settings truly optional ([#8551](https://github.com/hashicorp/terraform/issues/8551))
* provider/statuscake: Add support for contact-group id in statuscake test ([#8417](https://github.com/hashicorp/terraform/issues/8417))
* **New Command:** `terraform state` to provide access to a variety of state manipulation functions [GH-5811]
* **New Data Source:** `aws_ami` [GH-6911]
* **New Data Source:** `aws_availability_zones` [GH-6805]
* **New Data Source:** `aws_iam_policy_document` [GH-6881]
* **New Data Source:** `aws_s3_bucket_object` [GH-6946]
* **New Interpolation Function:** `sort` [GH-7128]
* **New Interpolation Function:** `distinct` [GH-7174]
* **New Provider:** `grafana` [GH-6206]
* **New Provider:** `random` - allows generation of random values without constantly generating diffs [GH-6672]
* **New Remote State Provider:** - `gcs` - Google Cloud Storage [GH-6814]
* **New Remote State Provider:** - `azure` - Microsoft Azure Storage [GH-7064]
* **New Resource:** `aws_elb_attachment` [GH-6879]
* **New Resource:** `aws_elastictranscoder_preset` [GH-6965]
* **New Resource:** `aws_elastictranscoder_pipeline` [GH-6965]
* **New Resource:** `aws_iam_group_policy_attachment` [GH-6858]
* **New Resource:** `aws_iam_role_policy_attachment` [GH-6858]
* **New Resource:** `aws_iam_user_policy_attachment` [GH-6858]
* **New Resource:** `aws_rds_cluster_parameter_group` [GH-5269]
* **New Resource:** `aws_spot_fleet_request` [GH-7243]
* **New Resource:** `openstack_blockstorage_volume_v2` [GH-6693]
* **New Resource:** `openstack_lb_loadbalancer_v2` [GH-7012]
* **New Resource:** `openstack_lb_listener_v2` [GH-7012]
* **New Resource:** `openstack_lb_pool_v2` [GH-7012]
* **New Resource:** `openstack_lb_member_v2` [GH-7012]
* **New Resource:** `openstack_lb_monitor_v2` [GH-7012]
* **New Resource:** `vsphere_virtual_disk` [GH-6273]
* **New Resource:** `github_repository_collaborator` [GH-6861]
* **New Resource:** `azurerm_virtual_machine_scale_set` [GH-6711]
* **New Resource:** `datadog_timeboard` [GH-6900]
* core: Tainted resources now show up in the plan and respect dependency ordering [GH-6600]
* core: The `lookup` interpolation function can now have a default fall-back value specified [GH-6884]
* core: The `terraform plan` command no longer persists state. [GH-6811]
BUG FIXES:
* core: Changing a module source from file to VCS no longer errors ([#8398](https://github.com/hashicorp/terraform/issues/8398))
* core: Configuration is now validated prior to input, fixing an obscure parse error when attempting to interpolate a count ([#8591](https://github.com/hashicorp/terraform/issues/8591))
* core: JSON configuration with resources with a single key parse properly ([#8485](https://github.com/hashicorp/terraform/issues/8485))
* core: States with duplicate modules are detected and an error is shown ([#8463](https://github.com/hashicorp/terraform/issues/8463))
* core: Validate uniqueness of variables/outputs in a module ([#8482](https://github.com/hashicorp/terraform/issues/8482))
* core: `-var` flag inputs starting with `/` work
* core: `-var` flag inputs starting with a number work and was fixed in such a way that this should overall be a lot more resilient to inputs ([#8044](https://github.com/hashicorp/terraform/issues/8044))
* provider/aws: Add AWS error message to retry APIGateway account update ([#8533](https://github.com/hashicorp/terraform/issues/8533))
* provider/aws: Do not set empty string to state for `aws_vpn_gateway` availability zone ([#8645](https://github.com/hashicorp/terraform/issues/8645))
* provider/aws: Fix. Adjust create and destroy timeout in aws_vpn_gateway_attachment. ([#8636](https://github.com/hashicorp/terraform/issues/8636))
* provider/aws: Handle missing EFS mount target in `aws_efs_mount_target` ([#8529](https://github.com/hashicorp/terraform/issues/8529))
* provider/aws: If an `aws_security_group` was used in Lambda function it may have prevented you from destroying such SG due to dangling ENIs created by Lambda service. These ENIs are now automatically cleaned up prior to SG deletion ([#8033](https://github.com/hashicorp/terraform/issues/8033))
* provider/aws: Increase `aws_route_table` timeouts from 1 min to 2 mins ([#8465](https://github.com/hashicorp/terraform/issues/8465))
* provider/aws: Increase aws_rds_cluster timeout to 40 minutes ([#8623](https://github.com/hashicorp/terraform/issues/8623))
* provider/aws: Refresh `aws_route` from state if `aws_route_table` not found ([#8443](https://github.com/hashicorp/terraform/issues/8443))
* provider/aws: Remove `aws_elasticsearch_domain` from state if it doesn't exist ([#8643](https://github.com/hashicorp/terraform/issues/8643))
* provider/aws: Remove unsafe ptr dereferencing from ECS/ECR ([#8514](https://github.com/hashicorp/terraform/issues/8514))
* provider/aws: Set `apply_method` to state in `aws_db_parameter_group` ([#8603](https://github.com/hashicorp/terraform/issues/8603))
* provider/aws: Stop `aws_instance` `source_dest_check` triggering an API call on each terraform run ([#8450](https://github.com/hashicorp/terraform/issues/8450))
* provider/aws: Wait for `aws_route_53_record` to be in-sync after a delete ([#8646](https://github.com/hashicorp/terraform/issues/8646))
* provider/aws: `aws_volume_attachment` detachment errors are caught ([#8479](https://github.com/hashicorp/terraform/issues/8479))
* provider/aws: adds resource retry to `aws_spot_instance_request` ([#8516](https://github.com/hashicorp/terraform/issues/8516))
* provider/aws: Add validation of Health Check target to aws_elb. ([#8578](https://github.com/hashicorp/terraform/issues/8578))
* provider/aws: Skip detaching when aws_internet_gateway not found ([#8454](https://github.com/hashicorp/terraform/issues/8454))
* provider/aws: Handle all kinds of CloudFormation stack failures ([#5606](https://github.com/hashicorp/terraform/issues/5606))
* provider/azurerm: Reordering the checks after an Azure API Get ([#8607](https://github.com/hashicorp/terraform/issues/8607))
* provider/chef: Fix "invalid header" errors that could occur ([#8382](https://github.com/hashicorp/terraform/issues/8382))
* provider/github: Remove unsafe ptr dereferencing ([#8512](https://github.com/hashicorp/terraform/issues/8512))
* provider/librato: Refresh space from state when not found ([#8596](https://github.com/hashicorp/terraform/issues/8596))
* provider/mysql: Fix breakage in parsing MySQL version string ([#8571](https://github.com/hashicorp/terraform/issues/8571))
* provider/template: `template_file` vars can be floating point ([#8590](https://github.com/hashicorp/terraform/issues/8590))
* provider/triton: Fix bug where the ID of a `triton_key` was used prior to being set ([#8563](https://github.com/hashicorp/terraform/issues/8563))
## 0.7.2 (August 25, 2016)
BACKWARDS INCOMPATIBILITIES / NOTES:
* provider/openstack: changes were made to how volumes attached to instances are detected. If you attached a volume to an instance out of band to Terraform, it will be detached upon the next apply. You can resolve this by adding a `volume` entry for the attached volume.
* provider/aws: `aws_spot_fleet_request` has changed the `associate_public_ip_address` default from `true` to `false`
FEATURES:
* **New Resource:** `aws_api_gateway_base_path_mapping` ([#8353](https://github.com/hashicorp/terraform/issues/8353))
* **New Resource:** `aws_api_gateway_domain_name` ([#8353](https://github.com/hashicorp/terraform/issues/8353))
* **New Resource:** `aws_ssm_document` ([#8460](https://github.com/hashicorp/terraform/issues/8460))
IMPROVEMENTS:
* core: Names generated with a unique prefix are now sortable based on age ([#8249](https://github.com/hashicorp/terraform/issues/8249))
* provider/aws: Add Primary Endpoint Address attribute for `aws_elasticache_replication_group` ([#8385](https://github.com/hashicorp/terraform/issues/8385))
* provider/aws: Add support for `network_mode` to `aws_ecs_task_definition` ([#8391](https://github.com/hashicorp/terraform/issues/8391))
* provider/aws: Add support for LB target group to ECS service ([#8190](https://github.com/hashicorp/terraform/issues/8190))
* provider/aws: Support Tags for `aws_alb` and `aws_alb_target_group` resources ([#8422](https://github.com/hashicorp/terraform/issues/8422))
* provider/aws: Support `snapshot_name` for ElastiCache Cluster and Replication Groups ([#8419](https://github.com/hashicorp/terraform/issues/8419))
* provider/aws: Add support to `aws_redshift_cluster` for restoring from snapshot ([#8414](https://github.com/hashicorp/terraform/issues/8414))
* provider/aws: Add validation for master_password in `aws_redshift_cluster` ([#8434](https://github.com/hashicorp/terraform/issues/8434))
* provider/openstack: Add `allowed_address_pairs` to `openstack_networking_port_v2` ([#8257](https://github.com/hashicorp/terraform/issues/8257))
BUG FIXES:
* core: fix crash case when malformed JSON given ([#8295](https://github.com/hashicorp/terraform/issues/8295))
* core: when asking for input, spaces are allowed ([#8394](https://github.com/hashicorp/terraform/issues/8394))
* core: module sources with URL encodings in the local file path won't error ([#8418](https://github.com/hashicorp/terraform/issues/8418))
* command/apply: prefix destroying resources with module path ([#8396](https://github.com/hashicorp/terraform/issues/8396))
* command/import: can import into specific indexes ([#8335](https://github.com/hashicorp/terraform/issues/8335))
* command/push: -upload-modules=false works ([#8456](https://github.com/hashicorp/terraform/issues/8456))
* command/state mv: nested modules can be moved ([#8304](https://github.com/hashicorp/terraform/issues/8304))
* command/state mv: resources with a count > 1 can be moved ([#8304](https://github.com/hashicorp/terraform/issues/8304))
* provider/aws: Refresh `aws_lambda_event_source_mapping` from state when NotFound ([#8378](https://github.com/hashicorp/terraform/issues/8378))
* provider/aws: `aws_elasticache_replication_group_id` validation change ([#8381](https://github.com/hashicorp/terraform/issues/8381))
* provider/aws: Fix possible crash if using duplicate Route53 records ([#8399](https://github.com/hashicorp/terraform/issues/8399))
* provider/aws: Refresh `aws_autoscaling_policy` from state on 404 ([#8430](https://github.com/hashicorp/terraform/issues/8430))
* provider/aws: Fix crash with VPC Peering connection accept/requests ([#8432](https://github.com/hashicorp/terraform/issues/8432))
* provider/aws: AWS SpotFleet Requests now works with Subnets and AZs ([#8320](https://github.com/hashicorp/terraform/issues/8320))
* provider/aws: Refresh `aws_cloudwatch_event_target` from state on `ResourceNotFoundException` ([#8442](https://github.com/hashicorp/terraform/issues/8442))
* provider/aws: Validate `aws_iam_policy_attachment` Name parameter to stop being empty ([#8441](https://github.com/hashicorp/terraform/issues/8441))
* provider/aws: Fix segmentation fault in `aws_api_gateway_base_path_mapping` resource ([#8466](https://github.com/hashicorp/terraform/issues/8466))
* provider/google: fix crash regression from Terraform 0.7.1 on `google_compute_firewall` resource ([#8390](https://github.com/hashicorp/terraform/issues/8390))
* provider/openstack: Volume Attachment and Detachment Fixes ([#8172](https://github.com/hashicorp/terraform/issues/8172))
## 0.7.1 (August 19, 2016)
FEATURES:
* **New Command:** `terraform state rm` ([#8200](https://github.com/hashicorp/terraform/issues/8200))
* **New Provider:** `archive` ([#7322](https://github.com/hashicorp/terraform/issues/7322))
* **New Resource:** `aws_alb` ([#8254](https://github.com/hashicorp/terraform/issues/8254))
* **New Resource:** `aws_alb_listener` ([#8269](https://github.com/hashicorp/terraform/issues/8269))
* **New Resource:** `aws_alb_target_group` ([#8254](https://github.com/hashicorp/terraform/issues/8254))
* **New Resource:** `aws_alb_target_group_attachment` ([#8254](https://github.com/hashicorp/terraform/issues/8254))
* **New Resource:** `aws_alb_target_group_rule` ([#8321](https://github.com/hashicorp/terraform/issues/8321))
* **New Resource:** `aws_vpn_gateway_attachment` ([#7870](https://github.com/hashicorp/terraform/issues/7870))
* **New Resource:** `aws_load_balancer_policy` ([#7458](https://github.com/hashicorp/terraform/issues/7458))
* **New Resource:** `aws_load_balancer_backend_server_policy` ([#7458](https://github.com/hashicorp/terraform/issues/7458))
* **New Resource:** `aws_load_balancer_listener_policy` ([#7458](https://github.com/hashicorp/terraform/issues/7458))
* **New Resource:** `aws_lb_ssl_negotiation_policy` ([#8084](https://github.com/hashicorp/terraform/issues/8084))
* **New Resource:** `aws_elasticache_replication_groups` ([#8275](https://github.com/hashicorp/terraform/issues/8275))
* **New Resource:** `azurerm_virtual_network_peering` ([#8168](https://github.com/hashicorp/terraform/issues/8168))
* **New Resource:** `azurerm_servicebus_namespace` ([#8195](https://github.com/hashicorp/terraform/issues/8195))
* **New Resource:** `google_compute_image` ([#7960](https://github.com/hashicorp/terraform/issues/7960))
* **New Resource:** `packet_volume` ([#8142](https://github.com/hashicorp/terraform/issues/8142))
* **New Resource:** `consul_prepared_query` ([#7474](https://github.com/hashicorp/terraform/issues/7474))
* **New Data Source:** `aws_ip_ranges` ([#7984](https://github.com/hashicorp/terraform/issues/7984))
* **New Data Source:** `fastly_ip_ranges` ([#7984](https://github.com/hashicorp/terraform/issues/7984))
* **New Data Source:** `aws_caller_identity` ([#8206](https://github.com/hashicorp/terraform/issues/8206))
* **New Data Source:** `aws_elb_service_account` ([#8221](https://github.com/hashicorp/terraform/issues/8221))
* **New Data Source:** `aws_redshift_service_account` ([#8224](https://github.com/hashicorp/terraform/issues/8224))
IMPROVEMENTS
* provider/archive support folders in output_path ([#8278](https://github.com/hashicorp/terraform/issues/8278))
* provider/aws: Introduce `aws_elasticsearch_domain` `elasticsearch_version` field (to specify ES version) ([#7860](https://github.com/hashicorp/terraform/issues/7860))
* provider/aws: Add support for TargetGroups (`aws_alb_target_groups`) to `aws_autoscaling_group` [8327]
* provider/aws: CloudWatch Metrics are now supported for `aws_route53_health_check` resources ([#8319](https://github.com/hashicorp/terraform/issues/8319))
* provider/aws: Query all pages of group membership ([#6726](https://github.com/hashicorp/terraform/issues/6726))
* provider/aws: Query all pages of IAM Policy attachments ([#7779](https://github.com/hashicorp/terraform/issues/7779))
* provider/aws: Change the way ARNs are built ([#7151](https://github.com/hashicorp/terraform/issues/7151))
* provider/aws: Add support for Elasticsearch destination to firehose delivery streams ([#7839](https://github.com/hashicorp/terraform/issues/7839))
* provider/aws: Retry AttachInternetGateway and increase timeout on `aws_internet_gateway` ([#7891](https://github.com/hashicorp/terraform/issues/7891))
* provider/aws: Add support for Enhanced monitoring to `aws_rds_cluster_instance` ([#8038](https://github.com/hashicorp/terraform/issues/8038))
* provider/aws: Add ability to set Requests Payer in `aws_s3_bucket` ([#8065](https://github.com/hashicorp/terraform/issues/8065))
* provider/aws: Add ability to set canned ACL in `aws_s3_bucket_object` ([#8091](https://github.com/hashicorp/terraform/issues/8091))
* provider/aws: Allow skipping credentials validation, requesting Account ID and/or metadata API check ([#7874](https://github.com/hashicorp/terraform/issues/7874))
* provider/aws: API gateway request/response parameters can now be specified as map, original `*_in_json` parameters deprecated ([#7794](https://github.com/hashicorp/terraform/issues/7794))
* provider/aws: Add support for `promotion_tier` to `aws_rds_cluster_instance` ([#8087](https://github.com/hashicorp/terraform/issues/8087))
* provider/aws: Allow specifying custom S3 endpoint and enforcing S3 path style URLs via new provider options ([#7871](https://github.com/hashicorp/terraform/issues/7871))
* provider/aws: Add ability to set Storage Class in `aws_s3_bucket_object` ([#8174](https://github.com/hashicorp/terraform/issues/8174))
* provider/aws: Treat `aws_lambda_function` w/ empty `subnet_ids` & `security_groups_ids` in `vpc_config` as VPC-disabled function ([#6191](https://github.com/hashicorp/terraform/issues/6191))
* provider/aws: Allow `source_ids` in `aws_db_event_subscription` to be Updatable ([#7892](https://github.com/hashicorp/terraform/issues/7892))
* provider/aws: Make `aws_efs_mount_target` creation fail for 2+ targets per AZ ([#8205](https://github.com/hashicorp/terraform/issues/8205))
* provider/aws: Add `force_destroy` option to `aws_route53_zone` ([#8239](https://github.com/hashicorp/terraform/issues/8239))
* provider/aws: Support import of `aws_s3_bucket` ([#8262](https://github.com/hashicorp/terraform/issues/8262))
* provider/aws: Increase timeout for retrying creation of IAM role ([#7733](https://github.com/hashicorp/terraform/issues/7733))
* provider/aws: Add ability to set peering options in aws_vpc_peering_connection. ([#8310](https://github.com/hashicorp/terraform/issues/8310))
* provider/azure: add custom_data argument for azure_instance resource ([#8158](https://github.com/hashicorp/terraform/issues/8158))
* provider/azurerm: Adds support for uploading blobs to azure storage from local source ([#7994](https://github.com/hashicorp/terraform/issues/7994))
* provider/azurerm: Storage blob contents can be copied from an existing blob ([#8126](https://github.com/hashicorp/terraform/issues/8126))
* provider/datadog: Allow `tags` to be configured for monitor resources. ([#8284](https://github.com/hashicorp/terraform/issues/8284))
* provider/google: allows atomic Cloud DNS record changes ([#6575](https://github.com/hashicorp/terraform/issues/6575))
* provider/google: Move URLMap hosts to TypeSet from TypeList ([#7472](https://github.com/hashicorp/terraform/issues/7472))
* provider/google: Support static private IP addresses in `resource_compute_instance` ([#6310](https://github.com/hashicorp/terraform/issues/6310))
* provider/google: Add support for using a GCP Image Family ([#8083](https://github.com/hashicorp/terraform/issues/8083))
* provider/openstack: Support updating the External Gateway assigned to a Neutron router ([#8070](https://github.com/hashicorp/terraform/issues/8070))
* provider/openstack: Support for `value_specs` param on `openstack_networking_network_v2` ([#8155](https://github.com/hashicorp/terraform/issues/8155))
* provider/openstack: Add `value_specs` param on `openstack_networking_subnet_v2` ([#8181](https://github.com/hashicorp/terraform/issues/8181))
* provider/vsphere: Improved SCSI controller handling in `vsphere_virtual_machine` ([#7908](https://github.com/hashicorp/terraform/issues/7908))
* provider/vsphere: Adding disk type of `Thick Lazy` to `vsphere_virtual_disk` and `vsphere_virtual_machine` ([#7916](https://github.com/hashicorp/terraform/issues/7916))
* provider/vsphere: Standardizing datastore references to use builtin Path func ([#8075](https://github.com/hashicorp/terraform/issues/8075))
* provider/consul: add tls config support to consul provider ([#7015](https://github.com/hashicorp/terraform/issues/7015))
* remote/consul: Support setting datacenter when using consul remote state ([#8102](https://github.com/hashicorp/terraform/issues/8102))
* provider/google: Support import of `google_compute_instance_template` ([#8147](https://github.com/hashicorp/terraform/issues/8147)), `google_compute_firewall` ([#8236](https://github.com/hashicorp/terraform/issues/8236)), `google_compute_target_pool` ([#8133](https://github.com/hashicorp/terraform/issues/8133)), `google_compute_fowarding_rule` ([#8122](https://github.com/hashicorp/terraform/issues/8122)), `google_compute_http_health_check` ([#8121](https://github.com/hashicorp/terraform/issues/8121)), `google_compute_autoscaler` ([#8115](https://github.com/hashicorp/terraform/issues/8115))
BUG FIXES:
* core: Fix issue preventing `taint` from working with resources that had no other attributes in their diff ([#8167](https://github.com/hashicorp/terraform/issues/8167))
* core: CLI will only run exact match commands ([#7983](https://github.com/hashicorp/terraform/issues/7983))
* core: Fix panic when resources ends up null in state file ([#8120](https://github.com/hashicorp/terraform/issues/8120))
* core: Fix panic when validating a count with a unprefixed variable ([#8243](https://github.com/hashicorp/terraform/issues/8243))
* core: Divide by zero in interpolations no longer panics ([#7701](https://github.com/hashicorp/terraform/issues/7701))
* core: Fix panic on some invalid interpolation syntax ([#5672](https://github.com/hashicorp/terraform/issues/5672))
* provider/aws: guard against missing image_digest in `aws_ecs_task_definition` ([#7966](https://github.com/hashicorp/terraform/issues/7966))
* provider/aws: `aws_cloudformation_stack` now respects `timeout_in_minutes` field when waiting for CF API to finish an update operation ([#7997](https://github.com/hashicorp/terraform/issues/7997))
* provider/aws: Prevent errors when `aws_s3_bucket` `acceleration_status` is not available in a given region ([#7999](https://github.com/hashicorp/terraform/issues/7999))
* provider/aws: Add state filter to `aws_availability_zone`s data source ([#7965](https://github.com/hashicorp/terraform/issues/7965))
* provider/aws: Handle lack of snapshot ID for a volume in `ami_copy` ([#7995](https://github.com/hashicorp/terraform/issues/7995))
* provider/aws: Retry association of IAM Role & instance profile ([#7938](https://github.com/hashicorp/terraform/issues/7938))
* provider/aws: Fix `aws_s3_bucket` resource `redirect_all_requests_to` action ([#7883](https://github.com/hashicorp/terraform/issues/7883))
* provider/aws: Fix issue updating ElasticBeanstalk Environment Settings ([#7777](https://github.com/hashicorp/terraform/issues/7777))
* provider/aws: `aws_rds_cluster` creation timeout bumped to 40 minutes ([#8052](https://github.com/hashicorp/terraform/issues/8052))
* provider/aws: Update ElasticTranscoder to allow empty notifications, removing notifications, etc ([#8207](https://github.com/hashicorp/terraform/issues/8207))
* provider/aws: Fix line ending errors/diffs with IAM Server Certs ([#8074](https://github.com/hashicorp/terraform/issues/8074))
* provider/aws: Fixing IAM data source policy generation to prevent spurious diffs ([#6956](https://github.com/hashicorp/terraform/issues/6956))
* provider/aws: Correct how CORS rules are handled in `aws_s3_bucket` ([#8096](https://github.com/hashicorp/terraform/issues/8096))
* provider/aws: allow numeric characters in RedshiftClusterDbName ([#8178](https://github.com/hashicorp/terraform/issues/8178))
* provider/aws: `aws_security_group` now creates tags as early as possible in the process ([#7849](https://github.com/hashicorp/terraform/issues/7849))
* provider/aws: Defensively code around `db_security_group` ingress rules ([#7893](https://github.com/hashicorp/terraform/issues/7893))
* provider/aws: `aws_spot_fleet_request` throws panic on missing subnet_id or availability_zone ([#8217](https://github.com/hashicorp/terraform/issues/8217))
* provider/aws: Terraform fails during Redshift delete if FinalSnapshot is being taken. ([#8270](https://github.com/hashicorp/terraform/issues/8270))
* provider/azurerm: `azurerm_storage_account` will interrupt for Ctrl-C ([#8215](https://github.com/hashicorp/terraform/issues/8215))
* provider/azurerm: Public IP - Setting idle timeout value caused panic. #8283
* provider/digitalocean: trim whitespace from ssh key ([#8173](https://github.com/hashicorp/terraform/issues/8173))
* provider/digitalocean: Enforce Lowercase on IPV6 Addresses ([#7652](https://github.com/hashicorp/terraform/issues/7652))
* provider/google: Use resource specific project when making queries/changes ([#7029](https://github.com/hashicorp/terraform/issues/7029))
* provider/google: Fix read for the backend service resource ([#7476](https://github.com/hashicorp/terraform/issues/7476))
* provider/mysql: `mysql_user` works with MySQL versions before 5.7.6 ([#8251](https://github.com/hashicorp/terraform/issues/8251))
* provider/openstack: Fix typo in OpenStack LBaaSv2 pool resource ([#8179](https://github.com/hashicorp/terraform/issues/8179))
* provider/vSphere: Fix for IPv6 only environment creation ([#7643](https://github.com/hashicorp/terraform/issues/7643))
* provider/google: Correct update process for authorized networks in `google_sql_database_instance` ([#8290](https://github.com/hashicorp/terraform/issues/8290))
## 0.7.0 (August 2, 2016)
BACKWARDS INCOMPATIBILITIES / NOTES:
* Terraform Core
* Terraform's built-in plugins are now distributed as part of the main Terraform binary, and use the go-plugin framework. Overrides are still available using separate binaries, but will need recompiling against Terraform 0.7.
* The `terraform plan` command no longer persists state. This makes the command much safer to run, since it is now side-effect free. The `refresh` and `apply` commands still persist state to local and remote storage. Any automation that assumes that `terraform plan` persists state will need to be reworked to explicitly call `terraform refresh` to get the equivalent side-effect. (The `terraform plan` command no longer has the `-state-out` or `-backup` flags due to this change.)
* The `concat()` interpolation function can no longer be used to join strings.
* Quotation marks may no longer be escaped in HIL expressions ([#7201](https://github.com/hashicorp/terraform/issues/7201))
* Lists materialized using splat syntax, for example `aws_instance.foo.*.id` are now ordered by the count index rather than lexographically sorted. If this produces a large number of undesirable differences, you can use the new `sort()` interpolation function to produce the previous behaviour.
* You now access the values of maps using the syntax `var.map["key"]` or the `lookup` function instead of `var.map.key`.
* Outputs on `terraform_remote_state` resources are now top level attributes rather than inside the `output` map. In order to access outputs, use the syntax: `terraform_remote_state.name.outputname`. Currently outputs cannot be named `config` or `backend`.
* AWS Provider
* `aws_elb` now defaults `cross_zone_load_balancing` to `true`
* `aws_instance`: EC2 Classic users may continue to use `security_groups` to reference Security Groups by their `name`. Users who are managing Instances inside VPCs will need to use `vpc_security_group_ids` instead, and reference the security groups by their `id`. Ref https://github.com/hashicorp/terraform/issues/6416#issuecomment-219145065
* `aws_kinesis_firehose_delivery_stream`: AWS Kinesis Firehose has been refactored to support Redshift as a destination in addition to S3. As a result, the configuration has changed and users will need to update their configuration to match the new `s3_configuration` block. Checkout the documentaiton on [AWS Kinesis Firehose](http://localhost:4567/docs/providers/aws/r/kinesis_firehose_delivery_stream.html) for more information ([#7375](https://github.com/hashicorp/terraform/issues/7375))
* `aws_route53_record`: `latency_routing_policy`, `geolocation_routing_policy`, and `failover_routing_policy` block options have been added. With these additions weve renamed the `weight` attribute to `weighted_routing_policy`, and it has changed from a string to a block to match the others. Please see the updated documentation on using `weighted_routing_policy`: https://www.terraform.io/docs/providers/aws/r/route53_record.html . ([#6954](https://github.com/hashicorp/terraform/issues/6954))
* `aws_db_instance` now defaults `publicly_accessible` to false
* Microsoft Azure Provider
* In documentation, the "Azure (Resource Manager)" provider has been renamed to the "Microsoft Azure" provider.
* `azurerm_dns_cname_record` now accepts a single record rather than a list of records
* `azurerm_virtual_machine` computer_name now Required
* Openstack Provider
* `openstack_networking_subnet_v2` now defaults to turning DHCP on.
* `openstack_fw_policy_v1` now correctly applies rules in the order they are specified. Upon the next apply, current rules might be re-ordered.
* The `member` attribute of `openstack_lb_pool_v1` has been deprecated. Please ue the new `openstack_lb_member_v1` resource.
* Docker Provider
* `keep_updated` parameter removed from `docker_image` - This parameter never did what it was supposed to do. See relevant docs, specifically `pull_trigger` & new `docker_registry_image` data source to understand how to keep your `docker_image` updated.
* Atlas Provider
* `atlas_artifact` resource has be deprecated. Please use the new `atlas_artifact` Data Source.
* CloudStack Provider
* All deprecated parameters are removed from all `CloudStack` resources
FEATURES:
* **Data sources** are a new kind of primitive in Terraform. Attributes for data sources are refreshed and available during the planning stage. ([#6598](https://github.com/hashicorp/terraform/issues/6598))
* **Lists and maps** can now be used as first class types for variables and may also be passed between modules. ([#6322](https://github.com/hashicorp/terraform/issues/6322))
* **State management CLI commands** provide a variety of state manipulation functions for advanced use cases. This should be used where possible instead of manually modifying state files. ([#5811](https://github.com/hashicorp/terraform/issues/5811))
* **State Import** allows a way to import existing resources into Terraform state for many types of resource. Initial coverage of AWS is quite high, and it is straightforward to add support for new resources.
* **New Command:** `terraform state` to provide access to a variety of state manipulation functions ([#5811](https://github.com/hashicorp/terraform/issues/5811))
* **New Option:** `terraform output` now supports the `-json` flag to print a machine-readable representation of outputs ([#7608](https://github.com/hashicorp/terraform/issues/7608))
* **New Data Source:** `aws_ami` ([#6911](https://github.com/hashicorp/terraform/issues/6911))
* **New Data Source:** `aws_availability_zones` ([#6805](https://github.com/hashicorp/terraform/issues/6805))
* **New Data Source:** `aws_iam_policy_document` ([#6881](https://github.com/hashicorp/terraform/issues/6881))
* **New Data Source:** `aws_s3_bucket_object` ([#6946](https://github.com/hashicorp/terraform/issues/6946))
* **New Data Source:** `aws_ecs_container_definition` ([#7230](https://github.com/hashicorp/terraform/issues/7230))
* **New Data Source:** `atlas_artifact` ([#7419](https://github.com/hashicorp/terraform/issues/7419))
* **New Data Source:** `docker_registry_image` ([#7000](https://github.com/hashicorp/terraform/issues/7000))
* **New Data Source:** `consul_keys` ([#7678](https://github.com/hashicorp/terraform/issues/7678))
* **New Interpolation Function:** `sort` ([#7128](https://github.com/hashicorp/terraform/issues/7128))
* **New Interpolation Function:** `distinct` ([#7174](https://github.com/hashicorp/terraform/issues/7174))
* **New Interpolation Function:** `list` ([#7528](https://github.com/hashicorp/terraform/issues/7528))
* **New Interpolation Function:** `map` ([#7832](https://github.com/hashicorp/terraform/issues/7832))
* **New Provider:** `grafana` ([#6206](https://github.com/hashicorp/terraform/issues/6206))
* **New Provider:** `logentries` ([#7067](https://github.com/hashicorp/terraform/issues/7067))
* **New Provider:** `scaleway` ([#7331](https://github.com/hashicorp/terraform/issues/7331))
* **New Provider:** `random` - allows generation of random values without constantly generating diffs ([#6672](https://github.com/hashicorp/terraform/issues/6672))
* **New Remote State Provider:** - `gcs` - Google Cloud Storage ([#6814](https://github.com/hashicorp/terraform/issues/6814))
* **New Remote State Provider:** - `azure` - Microsoft Azure Storage ([#7064](https://github.com/hashicorp/terraform/issues/7064))
* **New Resource:** `aws_elb_attachment` ([#6879](https://github.com/hashicorp/terraform/issues/6879))
* **New Resource:** `aws_elastictranscoder_preset` ([#6965](https://github.com/hashicorp/terraform/issues/6965))
* **New Resource:** `aws_elastictranscoder_pipeline` ([#6965](https://github.com/hashicorp/terraform/issues/6965))
* **New Resource:** `aws_iam_group_policy_attachment` ([#6858](https://github.com/hashicorp/terraform/issues/6858))
* **New Resource:** `aws_iam_role_policy_attachment` ([#6858](https://github.com/hashicorp/terraform/issues/6858))
* **New Resource:** `aws_iam_user_policy_attachment` ([#6858](https://github.com/hashicorp/terraform/issues/6858))
* **New Resource:** `aws_rds_cluster_parameter_group` ([#5269](https://github.com/hashicorp/terraform/issues/5269))
* **New Resource:** `aws_spot_fleet_request` ([#7243](https://github.com/hashicorp/terraform/issues/7243))
* **New Resource:** `aws_ses_active_receipt_rule_set` ([#5387](https://github.com/hashicorp/terraform/issues/5387))
* **New Resource:** `aws_ses_receipt_filter` ([#5387](https://github.com/hashicorp/terraform/issues/5387))
* **New Resource:** `aws_ses_receipt_rule` ([#5387](https://github.com/hashicorp/terraform/issues/5387))
* **New Resource:** `aws_ses_receipt_rule_set` ([#5387](https://github.com/hashicorp/terraform/issues/5387))
* **New Resource:** `aws_simpledb_domain` ([#7600](https://github.com/hashicorp/terraform/issues/7600))
* **New Resource:** `aws_opsworks_user_profile` ([#6304](https://github.com/hashicorp/terraform/issues/6304))
* **New Resource:** `aws_opsworks_permission` ([#6304](https://github.com/hashicorp/terraform/issues/6304))
* **New Resource:** `aws_ami_launch_permission` ([#7365](https://github.com/hashicorp/terraform/issues/7365))
* **New Resource:** `aws_appautoscaling_policy` ([#7663](https://github.com/hashicorp/terraform/issues/7663))
* **New Resource:** `aws_appautoscaling_target` ([#7663](https://github.com/hashicorp/terraform/issues/7663))
* **New Resource:** `openstack_blockstorage_volume_v2` ([#6693](https://github.com/hashicorp/terraform/issues/6693))
* **New Resource:** `openstack_lb_loadbalancer_v2` ([#7012](https://github.com/hashicorp/terraform/issues/7012))
* **New Resource:** `openstack_lb_listener_v2` ([#7012](https://github.com/hashicorp/terraform/issues/7012))
* **New Resource:** `openstack_lb_pool_v2` ([#7012](https://github.com/hashicorp/terraform/issues/7012))
* **New Resource:** `openstack_lb_member_v2` ([#7012](https://github.com/hashicorp/terraform/issues/7012))
* **New Resource:** `openstack_lb_monitor_v2` ([#7012](https://github.com/hashicorp/terraform/issues/7012))
* **New Resource:** `vsphere_virtual_disk` ([#6273](https://github.com/hashicorp/terraform/issues/6273))
* **New Resource:** `github_repository_collaborator` ([#6861](https://github.com/hashicorp/terraform/issues/6861))
* **New Resource:** `datadog_timeboard` ([#6900](https://github.com/hashicorp/terraform/issues/6900))
* **New Resource:** `digitalocean_tag` ([#7500](https://github.com/hashicorp/terraform/issues/7500))
* **New Resource:** `digitalocean_volume` ([#7560](https://github.com/hashicorp/terraform/issues/7560))
* **New Resource:** `consul_agent_service` ([#7508](https://github.com/hashicorp/terraform/issues/7508))
* **New Resource:** `consul_catalog_entry` ([#7508](https://github.com/hashicorp/terraform/issues/7508))
* **New Resource:** `consul_node` ([#7508](https://github.com/hashicorp/terraform/issues/7508))
* **New Resource:** `consul_service` ([#7508](https://github.com/hashicorp/terraform/issues/7508))
* **New Resource:** `mysql_grant` ([#7656](https://github.com/hashicorp/terraform/issues/7656))
* **New Resource:** `mysql_user` ([#7656](https://github.com/hashicorp/terraform/issues/7656))
* **New Resource:** `azurerm_storage_table` ([#7327](https://github.com/hashicorp/terraform/issues/7327))
* **New Resource:** `azurerm_virtual_machine_scale_set` ([#6711](https://github.com/hashicorp/terraform/issues/6711))
* **New Resource:** `azurerm_traffic_manager_endpoint` ([#7826](https://github.com/hashicorp/terraform/issues/7826))
* **New Resource:** `azurerm_traffic_manager_profile` ([#7826](https://github.com/hashicorp/terraform/issues/7826))
* core: Tainted resources now show up in the plan and respect dependency ordering ([#6600](https://github.com/hashicorp/terraform/issues/6600))
* core: The `lookup` interpolation function can now have a default fall-back value specified ([#6884](https://github.com/hashicorp/terraform/issues/6884))
* core: The `terraform plan` command no longer persists state. ([#6811](https://github.com/hashicorp/terraform/issues/6811))
IMPROVEMENTS:
* core: The `jsonencode` interpolation function now supports encoding lists and maps [GH-6749]
* core: Add the ability for resource definitions to mark attributes as "sensitive" which will omit them from UI output. [GH-6923]
* provider/aws: Add `option_settings` to `aws_db_option_group` [GH-6560]
* provider/aws: Add more explicit support for Skipping Final Snapshot in RDS Cluster [GH-6795]
* provider/aws: Add support for S3 Bucket Acceleration [GH-6628]
* provider/aws: Add support for `kms_key_id` to `aws_db_instance` [GH-6651]
* provider/aws: Add support to `aws_redshift_cluster` for `iam_roles` [GH-6647]
* provider/aws: SQS use raw policy string if compact fails [GH-6724]
* provider/aws: Set default description to "Managed by Terraform" [GH-6104]
* provider/aws: Support for Redshift Cluster encryption using a KMS key [GH-6712]
* provider/aws: Support tags for AWS redshift cluster [GH-5356]
* provider/aws: Add `iam_arn` to aws_cloudfront_origin_access_identity [GH-6955]
* provider/aws: Add `cross_zone_load_balancing` on `aws_elb` default to true [GH-6897]
* provider/aws: Add support for `character_set_name` to `aws_db_instance` [GH-4861]
* provider/aws: Add support for DB parameter group with RDS Cluster Instances (Aurora) [GH-6865]
* provider/aws: Add `name_prefix` to `aws_iam_instance_profile` and `aws_iam_role` [GH-6939]
* provider/aws: Allow authentication & credentials validation for federated IAM Roles and EC2 instance profiles [GH-6536]
* provider/aws: Rename parameter_group_name to db_cluster_parameter_group_name [GH-7083]
* provider/aws: Retry RouteTable Route/Assocation creation [GH-7156]
* provider/aws: `delegation_set_id` conflicts w/ `vpc_id` in `aws_route53_zone` as delegation sets can only be used for public zones [GH-7213]
* provider/azurerm: Add support for EnableIPForwarding to `azurerm_network_interface` [GH-6807]
* provider/azurerm: Add support for exporting the `azurerm_storage_account` access keys [GH-6742]
* provider/azurerm: The Azure SDK now exposes better error messages [GH-6976]
* core: The `jsonencode` interpolation function now supports encoding lists and maps ([#6749](https://github.com/hashicorp/terraform/issues/6749))
* core: Add the ability for resource definitions to mark attributes as "sensitive" which will omit them from UI output. ([#6923](https://github.com/hashicorp/terraform/issues/6923))
* core: Support `.` in map keys ([#7654](https://github.com/hashicorp/terraform/issues/7654))
* core: Enhance interpolation functions to account for first class maps and lists ([#7832](https://github.com/hashicorp/terraform/issues/7832)) ([#7834](https://github.com/hashicorp/terraform/issues/7834))
* command: Remove second DefaultDataDirectory const ([#7666](https://github.com/hashicorp/terraform/issues/7666))
* provider/aws: Add `dns_name` to `aws_efs_mount_target` ([#7428](https://github.com/hashicorp/terraform/issues/7428))
* provider/aws: Add `force_destroy` to `aws_iam_user` for force-deleting access keys assigned to the user ([#7766](https://github.com/hashicorp/terraform/issues/7766))
* provider/aws: Add `option_settings` to `aws_db_option_group` ([#6560](https://github.com/hashicorp/terraform/issues/6560))
* provider/aws: Add more explicit support for Skipping Final Snapshot in RDS Cluster ([#6795](https://github.com/hashicorp/terraform/issues/6795))
* provider/aws: Add support for S3 Bucket Acceleration ([#6628](https://github.com/hashicorp/terraform/issues/6628))
* provider/aws: Add support for `kms_key_id` to `aws_db_instance` ([#6651](https://github.com/hashicorp/terraform/issues/6651))
* provider/aws: Specifying more than one health check on an `aws_elb` fails with an error prior to making an API request ([#7489](https://github.com/hashicorp/terraform/issues/7489))
* provider/aws: Add support to `aws_redshift_cluster` for `iam_roles` ([#6647](https://github.com/hashicorp/terraform/issues/6647))
* provider/aws: SQS use raw policy string if compact fails ([#6724](https://github.com/hashicorp/terraform/issues/6724))
* provider/aws: Set default description to "Managed by Terraform" ([#6104](https://github.com/hashicorp/terraform/issues/6104))
* provider/aws: Support for Redshift Cluster encryption using a KMS key ([#6712](https://github.com/hashicorp/terraform/issues/6712))
* provider/aws: Support tags for AWS redshift cluster ([#5356](https://github.com/hashicorp/terraform/issues/5356))
* provider/aws: Add `iam_arn` to aws_cloudfront_origin_access_identity ([#6955](https://github.com/hashicorp/terraform/issues/6955))
* provider/aws: Add `cross_zone_load_balancing` on `aws_elb` default to true ([#6897](https://github.com/hashicorp/terraform/issues/6897))
* provider/aws: Add support for `character_set_name` to `aws_db_instance` ([#4861](https://github.com/hashicorp/terraform/issues/4861))
* provider/aws: Add support for DB parameter group with RDS Cluster Instances (Aurora) ([#6865](https://github.com/hashicorp/terraform/issues/6865))
* provider/aws: Add `name_prefix` to `aws_iam_instance_profile` and `aws_iam_role` ([#6939](https://github.com/hashicorp/terraform/issues/6939))
* provider/aws: Allow authentication & credentials validation for federated IAM Roles and EC2 instance profiles ([#6536](https://github.com/hashicorp/terraform/issues/6536))
* provider/aws: Rename parameter_group_name to db_cluster_parameter_group_name ([#7083](https://github.com/hashicorp/terraform/issues/7083))
* provider/aws: Retry RouteTable Route/Assocation creation ([#7156](https://github.com/hashicorp/terraform/issues/7156))
* provider/aws: `delegation_set_id` conflicts w/ `vpc_id` in `aws_route53_zone` as delegation sets can only be used for public zones ([#7213](https://github.com/hashicorp/terraform/issues/7213))
* provider/aws: Support Elastic Beanstalk scheduledaction ([#7376](https://github.com/hashicorp/terraform/issues/7376))
* provider/aws: Add support for NewInstancesProtectedFromScaleIn to `aws_autoscaling_group` ([#6490](https://github.com/hashicorp/terraform/issues/6490))
* provider/aws: Added support for `snapshot_identifier` parameter in aws_rds_cluster ([#7158](https://github.com/hashicorp/terraform/issues/7158))
* provider/aws: Add inplace edit/update DB Security Group Rule Ingress ([#7245](https://github.com/hashicorp/terraform/issues/7245))
* provider/aws: Added support for redshift destination to firehose delivery streams ([#7375](https://github.com/hashicorp/terraform/issues/7375))
* provider/aws: Allow `aws_redshift_security_group` ingress rules to change ([#5939](https://github.com/hashicorp/terraform/issues/5939))
* provider/aws: Add support for `encryption` and `kms_key_id` to `aws_ami` ([#7181](https://github.com/hashicorp/terraform/issues/7181))
* provider/aws: AWS prefix lists to enable security group egress to a VPC Endpoint ([#7511](https://github.com/hashicorp/terraform/issues/7511))
* provider/aws: Retry creation of IAM role depending on new IAM user ([#7324](https://github.com/hashicorp/terraform/issues/7324))
* provider/aws: Allow `port` on `aws_db_instance` to be updated ([#7441](https://github.com/hashicorp/terraform/issues/7441))
* provider/aws: Allow VPC Classic Linking in Autoscaling Launch Configs ([#7470](https://github.com/hashicorp/terraform/issues/7470))
* provider/aws: Support `task_role_arn` on `aws_ecs_task_definition ([#7653](https://github.com/hashicorp/terraform/issues/7653))
* provider/aws: Support Tags on `aws_rds_cluster` ([#7695](https://github.com/hashicorp/terraform/issues/7695))
* provider/aws: Support kms_key_id for `aws_rds_cluster` ([#7662](https://github.com/hashicorp/terraform/issues/7662))
* provider/aws: Allow setting a `poll_interval` on `aws_elastic_beanstalk_environment` ([#7523](https://github.com/hashicorp/terraform/issues/7523))
* provider/aws: Add support for Kinesis streams shard-level metrics ([#7684](https://github.com/hashicorp/terraform/issues/7684))
* provider/aws: Support create / update greater than twenty db parameters in `aws_db_parameter_group` ([#7364](https://github.com/hashicorp/terraform/issues/7364))
* provider/aws: expose network interface id in `aws_instance` ([#6751](https://github.com/hashicorp/terraform/issues/6751))
* provider/aws: Adding passthrough behavior for API Gateway integration ([#7801](https://github.com/hashicorp/terraform/issues/7801))
* provider/aws: Enable Redshift Cluster Logging ([#7813](https://github.com/hashicorp/terraform/issues/7813))
* provider/aws: Add ability to set Performance Mode in `aws_efs_file_system` ([#7791](https://github.com/hashicorp/terraform/issues/7791))
* provider/azurerm: Add support for EnableIPForwarding to `azurerm_network_interface` ([#6807](https://github.com/hashicorp/terraform/issues/6807))
* provider/azurerm: Add support for exporting the `azurerm_storage_account` access keys ([#6742](https://github.com/hashicorp/terraform/issues/6742))
* provider/azurerm: The Azure SDK now exposes better error messages ([#6976](https://github.com/hashicorp/terraform/issues/6976))
* provider/azurerm: `azurerm_dns_zone` now returns `name_servers` ([#7434](https://github.com/hashicorp/terraform/issues/7434))
* provider/azurerm: dump entire Request/Response in autorest Decorator ([#7719](https://github.com/hashicorp/terraform/issues/7719))
* provider/azurerm: add option to delete VMs Data disks on termination ([#7793](https://github.com/hashicorp/terraform/issues/7793))
* provider/clc: Add support for hyperscale and bareMetal server types and package installation
* provider/clc: Fix optional server password [GH-6414]
* provider/cloudstack: Add support for affinity groups to `cloudstack_instance` [GH-6898]
* provider/cloudstack: Enable swapping of ACLs without having to rebuild the network tier [GH-6741]
* provider/datadog: Add support for 'require full window' and 'locked' [GH-6738]
* provider/fastly: Add support for Cache Settings [GH-6781]
* provider/fastly: Add support for Service Request Settings on `fastly_service_v1` resources [GH-6622]
* provider/fastly: Add support for custom VCL configuration [GH-6662]
* provider/google: Support optional uuid naming for Instance Template [GH-6604]
* provider/openstack: Add support for client certificate authentication [GH-6279]
* provider/openstack: Allow Neutron-based Floating IP to target a specific tenant [GH-6454]
* provider/openstack: Enable DHCP By Default [GH-6838]
* provider/openstack: Implement fixed_ip on Neutron floating ip allocations [GH-6837]
* provider/openstack: Increase timeouts for image resize, subnets, and routers [GH-6764]
* provider/openstack: Add `lb_provider` argument to `lb_pool_v1` resource [GH-6919]
* provider/openstack: Enforce `ForceNew` on Instance Block Device [GH-6921]
* provider/openstack: Can now stop instances before destroying them [GH-7184]
* provider/vsphere: Add support for `controller_type` to `vsphere_virtual_machine` [GH-6785]
* provider/vsphere: Fix bug with `vsphere_virtual_machine` wait for ip [GH-6377]
* provider/vsphere: Virtual machine update disk [GH-6619]
* provider/vsphere: `vsphere_virtual_machine` adding controller creation logic [GH-6853]
* provider/vsphere: `vsphere_virtual_machine` added support for `mac address` on `network_interface` [GH-6966]
* provider/vsphere: Enhanced `vsphere` logging capabilities [GH-6893]
* provider/vSphere: Add DiskEnableUUID option to `vsphere_virtual_machine` [GH-7088]
* provider/vsphere: Virtual Machine and File resources handle Read errors properley [GH-7220]
* provider/clc: Fix optional server password ([#6414](https://github.com/hashicorp/terraform/issues/6414))
* provider/cloudstack: Add support for affinity groups to `cloudstack_instance` ([#6898](https://github.com/hashicorp/terraform/issues/6898))
* provider/cloudstack: Enable swapping of ACLs without having to rebuild the network tier ([#6741](https://github.com/hashicorp/terraform/issues/6741))
* provider/cloudstack: Improve ACL swapping ([#7315](https://github.com/hashicorp/terraform/issues/7315))
* provider/cloudstack: Add project support to `cloudstack_network_acl` and `cloudstack_network_acl_rule` ([#7612](https://github.com/hashicorp/terraform/issues/7612))
* provider/cloudstack: Add option to set `root_disk_size` to `cloudstack_instance` ([#7070](https://github.com/hashicorp/terraform/issues/7070))
* provider/cloudstack: Do no longer force a new `cloudstack_instance` resource when updating `user_data` ([#7074](https://github.com/hashicorp/terraform/issues/7074))
* provider/cloudstack: Add option to set `security_group_names` to `cloudstack_instance` ([#7240](https://github.com/hashicorp/terraform/issues/7240))
* provider/cloudstack: Add option to set `affinity_group_names` to `cloudstack_instance` ([#7242](https://github.com/hashicorp/terraform/issues/7242))
* provider/datadog: Add support for 'require full window' and 'locked' ([#6738](https://github.com/hashicorp/terraform/issues/6738))
* provider/docker: Docker Container DNS Setting Enhancements ([#7392](https://github.com/hashicorp/terraform/issues/7392))
* provider/docker: Add `destroy_grace_seconds` option to stop container before delete ([#7513](https://github.com/hashicorp/terraform/issues/7513))
* provider/docker: Add `pull_trigger` option to `docker_image` to trigger pulling layers of a given image ([#7000](https://github.com/hashicorp/terraform/issues/7000))
* provider/fastly: Add support for Cache Settings ([#6781](https://github.com/hashicorp/terraform/issues/6781))
* provider/fastly: Add support for Service Request Settings on `fastly_service_v1` resources ([#6622](https://github.com/hashicorp/terraform/issues/6622))
* provider/fastly: Add support for custom VCL configuration ([#6662](https://github.com/hashicorp/terraform/issues/6662))
* provider/google: Support optional uuid naming for Instance Template ([#6604](https://github.com/hashicorp/terraform/issues/6604))
* provider/openstack: Add support for client certificate authentication ([#6279](https://github.com/hashicorp/terraform/issues/6279))
* provider/openstack: Allow Neutron-based Floating IP to target a specific tenant ([#6454](https://github.com/hashicorp/terraform/issues/6454))
* provider/openstack: Enable DHCP By Default ([#6838](https://github.com/hashicorp/terraform/issues/6838))
* provider/openstack: Implement fixed_ip on Neutron floating ip allocations ([#6837](https://github.com/hashicorp/terraform/issues/6837))
* provider/openstack: Increase timeouts for image resize, subnets, and routers ([#6764](https://github.com/hashicorp/terraform/issues/6764))
* provider/openstack: Add `lb_provider` argument to `lb_pool_v1` resource ([#6919](https://github.com/hashicorp/terraform/issues/6919))
* provider/openstack: Enforce `ForceNew` on Instance Block Device ([#6921](https://github.com/hashicorp/terraform/issues/6921))
* provider/openstack: Can now stop instances before destroying them ([#7184](https://github.com/hashicorp/terraform/issues/7184))
* provider/openstack: Disassociate LBaaS v1 Monitors from Pool Before Deletion ([#6997](https://github.com/hashicorp/terraform/issues/6997))
* provider/powerdns: Add support for PowerDNS 4 API ([#7819](https://github.com/hashicorp/terraform/issues/7819))
* provider/triton: add `triton_machine` `domain names` ([#7149](https://github.com/hashicorp/terraform/issues/7149))
* provider/vsphere: Add support for `controller_type` to `vsphere_virtual_machine` ([#6785](https://github.com/hashicorp/terraform/issues/6785))
* provider/vsphere: Fix bug with `vsphere_virtual_machine` wait for ip ([#6377](https://github.com/hashicorp/terraform/issues/6377))
* provider/vsphere: Virtual machine update disk ([#6619](https://github.com/hashicorp/terraform/issues/6619))
* provider/vsphere: `vsphere_virtual_machine` adding controller creation logic ([#6853](https://github.com/hashicorp/terraform/issues/6853))
* provider/vsphere: `vsphere_virtual_machine` added support for `mac address` on `network_interface` ([#6966](https://github.com/hashicorp/terraform/issues/6966))
* provider/vsphere: Enhanced `vsphere` logging capabilities ([#6893](https://github.com/hashicorp/terraform/issues/6893))
* provider/vsphere: Add DiskEnableUUID option to `vsphere_virtual_machine` ([#7088](https://github.com/hashicorp/terraform/issues/7088))
* provider/vsphere: Virtual Machine and File resources handle Read errors properley ([#7220](https://github.com/hashicorp/terraform/issues/7220))
* provider/vsphere: set uuid as `vsphere_virtual_machine` output ([#4382](https://github.com/hashicorp/terraform/issues/4382))
* provider/vsphere: Add support for `keep_on_remove` to `vsphere_virtual_machine` ([#7169](https://github.com/hashicorp/terraform/issues/7169))
* provider/vsphere: Add support for additional `vsphere_virtial_machine` SCSI controller types ([#7525](https://github.com/hashicorp/terraform/issues/7525))
* provisioner/file: File provisioners may now have file content set as an attribute ([#7561](https://github.com/hashicorp/terraform/issues/7561))
BUG FIXES:
* core: Correct the previous fix for a bug causing "attribute not found" messages during destroy, as it was insufficient [GH-6599]
* core: Fix issue causing syntax errors interpolating count attribute when value passed between modules [GH-6833]
* core: Fix "diffs didn't match during apply" error for computed sets [GH-7205]
* core: Fix issue where `terraform init .` would truncate existing files [GH-7273]
* provider/aws: Changing keys in `aws_dynamodb_table` correctly force new resources [GH-6829]
* provider/aws: Fix a bug where CloudWatch alarms are created repeatedly if the user does not have permission to use the the DescribeAlarms operation [GH-7227]
* provider/aws: Fix crash in `aws_elasticache_parameter_group` occuring following edits in the console [GH-6687]
* provider/aws: Fix issue reattaching a VPN gateway to a VPC [GH-6987]
* provider/aws: Fix issue with Root Block Devices and encrypted flag in Launch Configurations [GH-6512]
* provider/aws: If more ENIs are attached to `aws_instance`, the one w/ DeviceIndex `0` is always used in context of `aws_instance` (previously unpredictable) [GH-6761]
* provider/aws: Make 'stage_name' required in api_gateway_deployment [GH-6797]
* provider/aws: Mark Lambda function as gone when it's gone [GH-6924]
* provider/aws: Trim trailing `.` from `name` in `aws_route53_record` resources to prevent spurious diffs [GH-6592]
* provider/aws: Update Lambda functions on name change [GH-7081]
* provider/aws: Updating state when `aws_sns_topic_subscription` is missing [GH-6629]
* provider/aws: `aws_codedeploy_deployment_group` panic when setting `on_premises_instance_tag_filter` [GH-6617]
* provider/aws: `aws_db_instance` now defaults `publicly_accessible` to false [GH-7117]
* provider/aws: `aws_opsworks_application.app_source` SSH key is write-only [GH-6649]
* provider/aws: fix Elastic Beanstalk `cname_prefix` continual plans [GH-6653]
* provider/aws: Bundle IOPs and Allocated Storage update for DB Instances [GH-7203]
* provider/aws: fix aws_security_group_rule refresh [GH-6730]
* provider/aws: Fix issue with Elastic Beanstalk and invalid settings [GH-7222]
* provider/aws: Fix issue where aws_app_cookie_stickiness_policy fails on destroy if LoadBalancer doesn't exist [GH-7166]
* provider/aws: Stickiness Policy exists, but isn't assigned to the ELB [GH-7188]
* provider/azurerm: Fixes terraform crash when using SSH keys with `azurerm_virtual_machine` [GH-6766]
* provider/azurerm: Fix a bug causing 'diffs do not match' on `azurerm_network_interface` resources [GH-6790]
* provider/azurerm: Normalizes `availability_set_id` casing to avoid spurious diffs in `azurerm_virtual_machine` [GH-6768]
* provider/azurerm: Add support for storage container name validation [GH-6852]
* provider/azurerm: Remove storage containers and blobs when storage accounts are not found [GH-6855]
* provider/azurerm: `azurerm_virtual_machine` fix `additional_unattend_rm` Windows config option [GH-7105]
* provider/azurerm: Fix `azurerm_virtual_machine` windows_config [GH-7123]
* provider/azurerm: `azurerm_dns_cname_record` can create CNAME records again [GH-7113]
* provider/azurerm: `azurerm_network_security_group` now waits for the provisioning state of `ready` before proceeding [GH-7307]
* provider/azurerm: `computer_name` is now required for `azurerm_virtual_machine` resources [GH-7308]
* provider/cloudflare: Fix issue upgrading CloudFlare Records created before v0.6.15 [GH-6969]
* provider/cloudstack: Fix using `cloudstack_network_acl` within a project [GH-6743]
* provider/digitalocean: Stop `digitocean_droplet` forcing new resource on uppercase region [GH-7044]
* provider/google: Fix a bug causing an error attempting to delete an already-deleted `google_compute_disk` [GH-6689]
* provider/mysql: Specifying empty provider credentials no longer causes a panic [GH-7211]
* provider/openstack: Reassociate Floating IP on network changes [GH-6579]
* provider/openstack: Ensure CIDRs Are Lower Case [GH-6864]
* provider/openstack: Rebuild Instances On Network Changes [GH-6844]
* provider/openstack: Firewall rules are applied in the correct order [GH-7194]
* provider/vsphere: `gateway` and `ipv6_gateway` are now read from `vsphere_virtual_machine` resources [GH-6522]
* provider/vsphere: `ipv*_gateway` parameters won't force a new `vsphere_virtual_machine` [GH-6635]
* provider/vsphere: adding a `vsphere_virtual_machine` migration [GH-7023]
* provider/vsphere: Don't require vsphere debug paths to be set [GH-7027]
* provider/vsphere: Fix bug where `enable_disk_uuid` was not set on `vsphere_virtual_machine` resources [GH-7275]
* core: Correct the previous fix for a bug causing "attribute not found" messages during destroy, as it was insufficient ([#6599](https://github.com/hashicorp/terraform/issues/6599))
* core: Fix issue causing syntax errors interpolating count attribute when value passed between modules ([#6833](https://github.com/hashicorp/terraform/issues/6833))
* core: Fix "diffs didn't match during apply" error for computed sets ([#7205](https://github.com/hashicorp/terraform/issues/7205))
* core: Fix issue where `terraform init .` would truncate existing files ([#7273](https://github.com/hashicorp/terraform/issues/7273))
* core: Don't compare diffs between maps with computed values ([#7249](https://github.com/hashicorp/terraform/issues/7249))
* core: Don't copy existing files over themselves when fetching modules ([#7273](https://github.com/hashicorp/terraform/issues/7273))
* core: Always increment the state serial number when upgrading the version ([#7402](https://github.com/hashicorp/terraform/issues/7402))
* core: Fix a crash during eval when we're upgrading an empty state ([#7403](https://github.com/hashicorp/terraform/issues/7403))
* core: Honor the `-state-out` flag when applying with a plan file ([#7443](https://github.com/hashicorp/terraform/issues/7443))
* core: Fix a panic when a `terraform_remote_state` data source doesn't exist ([#7464](https://github.com/hashicorp/terraform/issues/7464))
* core: Fix issue where `ignore_changes` caused incorrect diffs on dependent resources ([#7563](https://github.com/hashicorp/terraform/issues/7563))
* provider/aws: Manual changes to `aws_codedeploy_deployment_group` resources are now detected ([#7530](https://github.com/hashicorp/terraform/issues/7530))
* provider/aws: Changing keys in `aws_dynamodb_table` correctly force new resources ([#6829](https://github.com/hashicorp/terraform/issues/6829))
* provider/aws: Fix a bug where CloudWatch alarms are created repeatedly if the user does not have permission to use the the DescribeAlarms operation ([#7227](https://github.com/hashicorp/terraform/issues/7227))
* provider/aws: Fix crash in `aws_elasticache_parameter_group` occuring following edits in the console ([#6687](https://github.com/hashicorp/terraform/issues/6687))
* provider/aws: Fix issue reattaching a VPN gateway to a VPC ([#6987](https://github.com/hashicorp/terraform/issues/6987))
* provider/aws: Fix issue with Root Block Devices and encrypted flag in Launch Configurations ([#6512](https://github.com/hashicorp/terraform/issues/6512))
* provider/aws: If more ENIs are attached to `aws_instance`, the one w/ DeviceIndex `0` is always used in context of `aws_instance` (previously unpredictable) ([#6761](https://github.com/hashicorp/terraform/issues/6761))
* provider/aws: Increased lambda event mapping creation timeout ([#7657](https://github.com/hashicorp/terraform/issues/7657))
* provider/aws: Handle spurious failures in resourceAwsSecurityGroupRuleRead ([#7377](https://github.com/hashicorp/terraform/issues/7377))
* provider/aws: Make 'stage_name' required in api_gateway_deployment ([#6797](https://github.com/hashicorp/terraform/issues/6797))
* provider/aws: Mark Lambda function as gone when it's gone ([#6924](https://github.com/hashicorp/terraform/issues/6924))
* provider/aws: Trim trailing `.` from `name` in `aws_route53_record` resources to prevent spurious diffs ([#6592](https://github.com/hashicorp/terraform/issues/6592))
* provider/aws: Update Lambda functions on name change ([#7081](https://github.com/hashicorp/terraform/issues/7081))
* provider/aws: Updating state when `aws_sns_topic_subscription` is missing ([#6629](https://github.com/hashicorp/terraform/issues/6629))
* provider/aws: `aws_codedeploy_deployment_group` panic when setting `on_premises_instance_tag_filter` ([#6617](https://github.com/hashicorp/terraform/issues/6617))
* provider/aws: `aws_db_instance` now defaults `publicly_accessible` to false ([#7117](https://github.com/hashicorp/terraform/issues/7117))
* provider/aws: `aws_opsworks_application.app_source` SSH key is write-only ([#6649](https://github.com/hashicorp/terraform/issues/6649))
* provider/aws: fix Elastic Beanstalk `cname_prefix` continual plans ([#6653](https://github.com/hashicorp/terraform/issues/6653))
* provider/aws: Bundle IOPs and Allocated Storage update for DB Instances ([#7203](https://github.com/hashicorp/terraform/issues/7203))
* provider/aws: Fix case when instanceId is absent in network interfaces ([#6851](https://github.com/hashicorp/terraform/issues/6851))
* provider/aws: fix aws_security_group_rule refresh ([#6730](https://github.com/hashicorp/terraform/issues/6730))
* provider/aws: Fix issue with Elastic Beanstalk and invalid settings ([#7222](https://github.com/hashicorp/terraform/issues/7222))
* provider/aws: Fix issue where aws_app_cookie_stickiness_policy fails on destroy if LoadBalancer doesn't exist ([#7166](https://github.com/hashicorp/terraform/issues/7166))
* provider/aws: Stickiness Policy exists, but isn't assigned to the ELB ([#7188](https://github.com/hashicorp/terraform/issues/7188))
* provider/aws: Fix issue with `manage_bundler` on `aws_opsworks_layers` ([#7219](https://github.com/hashicorp/terraform/issues/7219))
* provider/aws: Set Elastic Beanstalk stack name back to state ([#7445](https://github.com/hashicorp/terraform/issues/7445))
* provider/aws: Allow recreation of VPC Peering Connection when state is rejected ([#7466](https://github.com/hashicorp/terraform/issues/7466))
* provider/aws: Remove EFS File System from State when NotFound ([#7437](https://github.com/hashicorp/terraform/issues/7437))
* provider/aws: `aws_customer_gateway` refreshing from state on deleted state ([#7482](https://github.com/hashicorp/terraform/issues/7482))
* provider/aws: Retry finding `aws_route` after creating it ([#7463](https://github.com/hashicorp/terraform/issues/7463))
* provider/aws: Refresh CloudWatch Group from state on 404 ([#7576](https://github.com/hashicorp/terraform/issues/7576))
* provider/aws: Adding in additional retry logic due to latency with delete of `db_option_group` ([#7312](https://github.com/hashicorp/terraform/issues/7312))
* provider/aws: Safely get ELB values ([#7585](https://github.com/hashicorp/terraform/issues/7585))
* provider/aws: Fix bug for recurring plans on ec2-classic and vpc in beanstalk ([#6491](https://github.com/hashicorp/terraform/issues/6491))
* provider/aws: Bump rds_cluster timeout to 15 mins ([#7604](https://github.com/hashicorp/terraform/issues/7604))
* provider/aws: Fix ICMP fields in `aws_network_acl_rule` to allow ICMP code 0 (echo reply) to be configured ([#7669](https://github.com/hashicorp/terraform/issues/7669))
* provider/aws: Fix bug with Updating `aws_autoscaling_group` `enabled_metrics` ([#7698](https://github.com/hashicorp/terraform/issues/7698))
* provider/aws: Ignore IOPS on non io1 AWS root_block_device ([#7783](https://github.com/hashicorp/terraform/issues/7783))
* provider/aws: Ignore missing ENI attachment when trying to detach ENI ([#7185](https://github.com/hashicorp/terraform/issues/7185))
* provider/aws: Fix issue updating ElasticBeanstalk Environment templates ([#7811](https://github.com/hashicorp/terraform/issues/7811))
* provider/aws: Restore Defaults to SQS Queues ([#7818](https://github.com/hashicorp/terraform/issues/7818))
* provider/aws: Don't delete Lambda function from state on initial call of the Read func ([#7829](https://github.com/hashicorp/terraform/issues/7829))
* provider/aws: `aws_vpn_gateway` should be removed from state when in deleted state ([#7861](https://github.com/hashicorp/terraform/issues/7861))
* provider/aws: Fix aws_route53_record 0-2 migration ([#7907](https://github.com/hashicorp/terraform/issues/7907))
* provider/azurerm: Fixes terraform crash when using SSH keys with `azurerm_virtual_machine` ([#6766](https://github.com/hashicorp/terraform/issues/6766))
* provider/azurerm: Fix a bug causing 'diffs do not match' on `azurerm_network_interface` resources ([#6790](https://github.com/hashicorp/terraform/issues/6790))
* provider/azurerm: Normalizes `availability_set_id` casing to avoid spurious diffs in `azurerm_virtual_machine` ([#6768](https://github.com/hashicorp/terraform/issues/6768))
* provider/azurerm: Add support for storage container name validation ([#6852](https://github.com/hashicorp/terraform/issues/6852))
* provider/azurerm: Remove storage containers and blobs when storage accounts are not found ([#6855](https://github.com/hashicorp/terraform/issues/6855))
* provider/azurerm: `azurerm_virtual_machine` fix `additional_unattend_rm` Windows config option ([#7105](https://github.com/hashicorp/terraform/issues/7105))
* provider/azurerm: Fix `azurerm_virtual_machine` windows_config ([#7123](https://github.com/hashicorp/terraform/issues/7123))
* provider/azurerm: `azurerm_dns_cname_record` can create CNAME records again ([#7113](https://github.com/hashicorp/terraform/issues/7113))
* provider/azurerm: `azurerm_network_security_group` now waits for the provisioning state of `ready` before proceeding ([#7307](https://github.com/hashicorp/terraform/issues/7307))
* provider/azurerm: `computer_name` is now required for `azurerm_virtual_machine` resources ([#7308](https://github.com/hashicorp/terraform/issues/7308))
* provider/azurerm: destroy azurerm_virtual_machine OS Disk VHD on deletion ([#7584](https://github.com/hashicorp/terraform/issues/7584))
* provider/azurerm: catch `azurerm_template_deployment` erroring silently ([#7644](https://github.com/hashicorp/terraform/issues/7644))
* provider/azurerm: changing the name of an `azurerm_virtual_machine` now forces a new resource ([#7646](https://github.com/hashicorp/terraform/issues/7646))
* provider/azurerm: azurerm_storage_account now returns storage keys value instead of their names ([#7674](https://github.com/hashicorp/terraform/issues/7674))
* provider/azurerm: `azurerm_virtual_machine` computer_name now Required ([#7308](https://github.com/hashicorp/terraform/issues/7308))
* provider/azurerm: Change of `availability_set_id` on `azurerm_virtual_machine` should ForceNew ([#7650](https://github.com/hashicorp/terraform/issues/7650))
* provider/azurerm: Wait for `azurerm_storage_account` to be available ([#7329](https://github.com/hashicorp/terraform/issues/7329))
* provider/cloudflare: Fix issue upgrading CloudFlare Records created before v0.6.15 ([#6969](https://github.com/hashicorp/terraform/issues/6969))
* provider/cloudstack: Fix using `cloudstack_network_acl` within a project ([#6743](https://github.com/hashicorp/terraform/issues/6743))
* provider/cloudstack: Fix refresing `cloudstack_network_acl_rule` when the associated ACL is deleted ([#7612](https://github.com/hashicorp/terraform/issues/7612))
* provider/cloudstack: Fix refresing `cloudstack_port_forward` when the associated IP address is no longer associated ([#7612](https://github.com/hashicorp/terraform/issues/7612))
* provider/cloudstack: Fix creating `cloudstack_network` with offerings that do not support specifying IP ranges ([#7612](https://github.com/hashicorp/terraform/issues/7612))
* provider/digitalocean: Stop `digitocean_droplet` forcing new resource on uppercase region ([#7044](https://github.com/hashicorp/terraform/issues/7044))
* provider/digitalocean: Reassign Floating IP when droplet changes ([#7411](https://github.com/hashicorp/terraform/issues/7411))
* provider/google: Fix a bug causing an error attempting to delete an already-deleted `google_compute_disk` ([#6689](https://github.com/hashicorp/terraform/issues/6689))
* provider/mysql: Specifying empty provider credentials no longer causes a panic ([#7211](https://github.com/hashicorp/terraform/issues/7211))
* provider/openstack: Reassociate Floating IP on network changes ([#6579](https://github.com/hashicorp/terraform/issues/6579))
* provider/openstack: Ensure CIDRs Are Lower Case ([#6864](https://github.com/hashicorp/terraform/issues/6864))
* provider/openstack: Rebuild Instances On Network Changes ([#6844](https://github.com/hashicorp/terraform/issues/6844))
* provider/openstack: Firewall rules are applied in the correct order ([#7194](https://github.com/hashicorp/terraform/issues/7194))
* provider/openstack: Fix Security Group EOF Error when Adding / Removing Multiple Groups ([#7468](https://github.com/hashicorp/terraform/issues/7468))
* provider/openstack: Fixing boot volumes interfering with block storage volumes list ([#7649](https://github.com/hashicorp/terraform/issues/7649))
* provider/vsphere: `gateway` and `ipv6_gateway` are now read from `vsphere_virtual_machine` resources ([#6522](https://github.com/hashicorp/terraform/issues/6522))
* provider/vsphere: `ipv*_gateway` parameters won't force a new `vsphere_virtual_machine` ([#6635](https://github.com/hashicorp/terraform/issues/6635))
* provider/vsphere: adding a `vsphere_virtual_machine` migration ([#7023](https://github.com/hashicorp/terraform/issues/7023))
* provider/vsphere: Don't require vsphere debug paths to be set ([#7027](https://github.com/hashicorp/terraform/issues/7027))
* provider/vsphere: Fix bug where `enable_disk_uuid` was not set on `vsphere_virtual_machine` resources ([#7275](https://github.com/hashicorp/terraform/issues/7275))
* provider/vsphere: Make `vsphere_virtual_machine` `product_key` optional ([#7410](https://github.com/hashicorp/terraform/issues/7410))
* provider/vsphere: Refreshing devices list after adding a disk or cdrom controller ([#7167](https://github.com/hashicorp/terraform/issues/7167))
* provider/vsphere: `vsphere_virtual_machine` no longer has to be powered on to delete ([#7206](https://github.com/hashicorp/terraform/issues/7206))
* provider/vSphere: Fixes the hasBootableVmdk flag when attaching multiple disks ([#7804](https://github.com/hashicorp/terraform/issues/7804))
* provisioner/remote-exec: Properly seed random script paths so they are not deterministic across runs ([#7413](https://github.com/hashicorp/terraform/issues/7413))
## 0.6.16 (May 9, 2016)
BACKWARDS INCOMPATIBILITIES / NOTES:
* provider/aws: `aws_eip` field `private_ip` is now a computed value, and cannot be set in your configuration.
* provider/aws: `aws_eip` field `private_ip` is now a computed value, and cannot be set in your configuration.
Use `associate_with_private_ip` instead. See ([#6521](https://github.com/hashicorp/terraform/issues/6521))
FEATURES:
@ -179,7 +551,7 @@ FEATURES:
* **New resource:** `aws_db_event_subscription` ([#6367](https://github.com/hashicorp/terraform/issues/6367))
* **New resource:** `aws_db_option_group` ([#4401](https://github.com/hashicorp/terraform/issues/4401))
* **New resource:** `aws_eip_association` ([#6552](https://github.com/hashicorp/terraform/issues/6552))
* **New resource:** `openstack_networking_secgroup_rule_v2` ([#6410](https://github.com/hashicorp/terraform/issues/6410))
* **New resource:** `openstack_networking_secgroup_rule_v2` ([#6410](https://github.com/hashicorp/terraform/issues/6410))
* **New resource:** `openstack_networking_secgroup_v2` ([#6410](https://github.com/hashicorp/terraform/issues/6410))
* **New resource:** `vsphere_file` ([#6401](https://github.com/hashicorp/terraform/issues/6401))
@ -187,7 +559,7 @@ IMPROVEMENTS:
* core: update HCL dependency to improve whitespace handling in `terraform fmt` ([#6347](https://github.com/hashicorp/terraform/issues/6347))
* core: Add support for marking outputs as sensitive ([#6559](https://github.com/hashicorp/terraform/issues/6559))
* provider/aws: Add agent_version argument to `aws_opswork_stack` ([#6493](https://github.com/hashicorp/terraform/issues/6493))
* provider/aws: Add agent_version argument to `aws_opswork_stack` ([#6493](https://github.com/hashicorp/terraform/issues/6493))
* provider/aws: Add support for request parameters to `api_gateway_method` & `api_gateway_integration` ([#6501](https://github.com/hashicorp/terraform/issues/6501))
* provider/aws: Add support for response parameters to `api_gateway_method_response` & `api_gateway_integration_response` ([#6344](https://github.com/hashicorp/terraform/issues/6344))
* provider/aws: Allow empty S3 config in Cloudfront Origin ([#6487](https://github.com/hashicorp/terraform/issues/6487))
@ -1007,7 +1379,7 @@ IMPROVEMENTS:
* provider/aws: read `iam_instance_profile` for `aws_instance` and save to state ([#3167](https://github.com/hashicorp/terraform/issues/3167))
* provider/aws: allow `instance` to be computed in `aws_eip` ([#3036](https://github.com/hashicorp/terraform/issues/3036))
* provider/aws: Add `versioning` option to `aws_s3_bucket` ([#2942](https://github.com/hashicorp/terraform/issues/2942))
* provider/aws: Add `configuation_endpoint` to `aws_elasticache_cluster` ([#3250](https://github.com/hashicorp/terraform/issues/3250))
* provider/aws: Add `configuration_endpoint` to `aws_elasticache_cluster` ([#3250](https://github.com/hashicorp/terraform/issues/3250))
* provider/aws: Add validation for `app_cookie_stickiness_policy.name` ([#3277](https://github.com/hashicorp/terraform/issues/3277))
* provider/aws: Add validation for `db_parameter_group.name` ([#3279](https://github.com/hashicorp/terraform/issues/3279))
* provider/aws: Set DynamoDB Table ARN after creation ([#3500](https://github.com/hashicorp/terraform/issues/3500))

View File

@ -1,4 +1,4 @@
TEST?=$$(go list ./... | grep -v '/vendor/' | grep -v '/builtin/bins/')
TEST?=$$(go list ./... | grep -v '/terraform/vendor/' | grep -v '/builtin/bins/')
VETARGS?=-all
GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor)
@ -29,7 +29,8 @@ core-dev: generate
# Shorthand for quickly testing the core of Terraform (i.e. "not providers")
core-test: generate
@echo "Testing core packages..." && go test -tags 'core' $(TESTARGS) $(shell go list ./... | grep -v -E 'builtin|vendor')
@echo "Testing core packages..." && \
go test -tags 'core' $(TESTARGS) $(shell go list ./... | grep -v -E 'terraform/(builtin|vendor)')
# Shorthand for building and installing just one plugin for local testing.
# Run as (for example): make plugin-dev PLUGIN=provider-aws
@ -38,7 +39,7 @@ plugin-dev: generate
mv $(GOPATH)/bin/$(PLUGIN) $(GOPATH)/bin/terraform-$(PLUGIN)
# test runs the unit tests
test: fmtcheck generate
test: fmtcheck errcheck generate
TF_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4
# testacc runs acceptance tests
@ -76,10 +77,10 @@ vet:
# generate runs `go generate` to build the dynamically generated
# source files.
generate:
@which stringer ; if [ $$? -ne 0 ]; then \
@which stringer > /dev/null; if [ $$? -ne 0 ]; then \
go get -u golang.org/x/tools/cmd/stringer; \
fi
go generate $$(go list ./... | grep -v /vendor/)
go generate $$(go list ./... | grep -v /terraform/vendor/)
@go fmt command/internal_plugin_list.go > /dev/null
fmt:
@ -88,4 +89,7 @@ fmt:
fmtcheck:
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
errcheck:
@sh -c "'$(CURDIR)/scripts/errcheck.sh'"
.PHONY: bin default generate test vet fmt fmtcheck tools

View File

@ -36,6 +36,7 @@ For local dev first make sure Go is properly installed, including setting up a [
Next, using [Git](https://git-scm.com/), clone this repository into `$GOPATH/src/github.com/hashicorp/terraform`. All the necessary dependencies are either vendored or automatically installed, so you just need to type `make`. This will compile the code and then run the tests. If this exits with exit status 0, then everything is working!
```sh
$ cd $GOPATH/src/github.com/hashicorp/terraform
$ make
```
@ -121,7 +122,7 @@ If you wish to cross-compile Terraform for another architecture, you can set the
For example, to compile 64-bit Linux binaries on Mac OS X Linux, you can run:
```sh
$ XC_OS=linux XC_ARCH=amd64 make bin
$ XC_OS=linux XC_ARCH=amd64 make bin
...
$ file pkg/linux_amd64/terraform
terraform: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

15
Vagrantfile vendored
View File

@ -5,7 +5,7 @@
VAGRANTFILE_API_VERSION = "2"
$script = <<SCRIPT
GOVERSION="1.6"
GOVERSION="1.7"
SRCROOT="/opt/go"
SRCPATH="/opt/gopath"
@ -37,13 +37,17 @@ export GOPATH="$SRCPATH"
export GOROOT="$SRCROOT"
export PATH="$SRCROOT/bin:$SRCPATH/bin:\$PATH"
EOF
cat <<EOF >>~/.bashrc
## After login, change to terraform directory
cd /opt/gopath/src/github.com/hashicorp/terraform
EOF
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
sudo chmod 0755 /etc/profile.d/gopath.sh
source /etc/profile.d/gopath.sh
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "bento/ubuntu-12.04"
config.vm.box = "bento/ubuntu-14.04"
config.vm.hostname = "terraform"
config.vm.provision "shell", inline: $script, privileged: false
@ -60,4 +64,9 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
v.memory = 4096
v.cpus = 2
end
config.vm.provider "parallels" do |prl|
prl.memory = 4096
prl.cpus = 2
end
end

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/archive"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: archive.Provider,
})
}

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/logentries"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: logentries.Provider,
})
}

View File

@ -0,0 +1 @@
package main

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/scaleway"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: scaleway.Provider,
})
}

1
builtin/providers/archive/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.zip

View File

@ -0,0 +1,47 @@
package archive
import (
"fmt"
"os"
)
type Archiver interface {
ArchiveContent(content []byte, infilename string) error
ArchiveFile(infilename string) error
ArchiveDir(indirname string) error
}
type ArchiverBuilder func(filepath string) Archiver
var archiverBuilders = map[string]ArchiverBuilder{
"zip": NewZipArchiver,
}
func getArchiver(archiveType string, filepath string) Archiver {
if builder, ok := archiverBuilders[archiveType]; ok {
return builder(filepath)
}
return nil
}
func assertValidFile(infilename string) (os.FileInfo, error) {
fi, err := os.Stat(infilename)
if err != nil && os.IsNotExist(err) {
return fi, fmt.Errorf("could not archive missing file: %s", infilename)
}
return fi, err
}
func assertValidDir(indirname string) (os.FileInfo, error) {
fi, err := os.Stat(indirname)
if err != nil {
if os.IsNotExist(err) {
return fi, fmt.Errorf("could not archive missing directory: %s", indirname)
}
return fi, err
}
if !fi.IsDir() {
return fi, fmt.Errorf("could not archive directory that is a file: %s", indirname)
}
return fi, nil
}

View File

@ -0,0 +1,16 @@
package archive
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{},
ResourcesMap: map[string]*schema.Resource{
"archive_file": resourceArchiveFile(),
},
}
}

View File

@ -0,0 +1,18 @@
package archive
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testProviders = map[string]terraform.ResourceProvider{
"archive": Provider(),
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}

View File

@ -0,0 +1,183 @@
package archive
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"path"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceArchiveFile() *schema.Resource {
return &schema.Resource{
Create: resourceArchiveFileCreate,
Read: resourceArchiveFileRead,
Update: resourceArchiveFileUpdate,
Delete: resourceArchiveFileDelete,
Exists: resourceArchiveFileExists,
Schema: map[string]*schema.Schema{
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"source_content": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"source_file", "source_dir"},
},
"source_content_filename": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"source_file", "source_dir"},
},
"source_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"source_content", "source_content_filename", "source_dir"},
},
"source_dir": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"source_content", "source_content_filename", "source_file"},
},
"output_path": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"output_size": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
ForceNew: true,
},
"output_sha": &schema.Schema{
Type: schema.TypeString,
Computed: true,
ForceNew: true,
Description: "SHA1 checksum of output file",
},
},
}
}
func resourceArchiveFileCreate(d *schema.ResourceData, meta interface{}) error {
if err := resourceArchiveFileUpdate(d, meta); err != nil {
return err
}
return resourceArchiveFileRead(d, meta)
}
func resourceArchiveFileRead(d *schema.ResourceData, meta interface{}) error {
outputPath := d.Get("output_path").(string)
fi, err := os.Stat(outputPath)
if os.IsNotExist(err) {
d.SetId("")
d.MarkNewResource()
return nil
}
sha, err := genFileSha1(outputPath)
if err != nil {
return fmt.Errorf("could not generate file checksum sha: %s", err)
}
d.Set("output_sha", sha)
d.Set("output_size", fi.Size())
d.SetId(d.Get("output_sha").(string))
return nil
}
func resourceArchiveFileUpdate(d *schema.ResourceData, meta interface{}) error {
archiveType := d.Get("type").(string)
outputPath := d.Get("output_path").(string)
outputDirectory := path.Dir(outputPath)
if outputDirectory != "" {
if _, err := os.Stat(outputDirectory); err != nil {
if err := os.MkdirAll(outputDirectory, 0777); err != nil {
return err
}
}
}
archiver := getArchiver(archiveType, outputPath)
if archiver == nil {
return fmt.Errorf("archive type not supported: %s", archiveType)
}
if dir, ok := d.GetOk("source_dir"); ok {
if err := archiver.ArchiveDir(dir.(string)); err != nil {
return fmt.Errorf("error archiving directory: %s", err)
}
} else if file, ok := d.GetOk("source_file"); ok {
if err := archiver.ArchiveFile(file.(string)); err != nil {
return fmt.Errorf("error archiving file: %s", err)
}
} else if filename, ok := d.GetOk("source_content_filename"); ok {
content := d.Get("source_content").(string)
if err := archiver.ArchiveContent([]byte(content), filename.(string)); err != nil {
return fmt.Errorf("error archiving content: %s", err)
}
} else {
return fmt.Errorf("one of 'source_dir', 'source_file', 'source_content_filename' must be specified")
}
// Generate archived file stats
fi, err := os.Stat(outputPath)
if err != nil {
return err
}
sha, err := genFileSha1(outputPath)
if err != nil {
return fmt.Errorf("could not generate file checksum sha: %s", err)
}
d.Set("output_sha", sha)
d.Set("output_size", fi.Size())
d.SetId(d.Get("output_sha").(string))
return nil
}
func resourceArchiveFileDelete(d *schema.ResourceData, meta interface{}) error {
outputPath := d.Get("output_path").(string)
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
return nil
}
if err := os.Remove(outputPath); err != nil {
return fmt.Errorf("could not delete zip file %q: %s", outputPath, err)
}
return nil
}
func resourceArchiveFileExists(d *schema.ResourceData, meta interface{}) (bool, error) {
outputPath := d.Get("output_path").(string)
_, err := os.Stat(outputPath)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
func genFileSha1(filename string) (string, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("could not compute file '%s' checksum: %s", filename, err)
}
h := sha1.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@ -0,0 +1,109 @@
package archive
import (
"fmt"
"os"
"testing"
r "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccArchiveFile_Basic(t *testing.T) {
var fileSize string
r.Test(t, r.TestCase{
Providers: testProviders,
CheckDestroy: r.ComposeTestCheckFunc(
testAccArchiveFileMissing("zip_file_acc_test.zip"),
),
Steps: []r.TestStep{
r.TestStep{
Config: testAccArchiveFileContentConfig,
Check: r.ComposeTestCheckFunc(
testAccArchiveFileExists("zip_file_acc_test.zip", &fileSize),
r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize),
),
},
r.TestStep{
Config: testAccArchiveFileFileConfig,
Check: r.ComposeTestCheckFunc(
testAccArchiveFileExists("zip_file_acc_test.zip", &fileSize),
r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize),
),
},
r.TestStep{
Config: testAccArchiveFileDirConfig,
Check: r.ComposeTestCheckFunc(
testAccArchiveFileExists("zip_file_acc_test.zip", &fileSize),
r.TestCheckResourceAttrPtr("archive_file.foo", "output_size", &fileSize),
),
},
r.TestStep{
Config: testAccArchiveFileOutputPath,
Check: r.ComposeTestCheckFunc(
testAccArchiveFileExists(fmt.Sprintf("%s/test.zip", tmpDir), &fileSize),
),
},
},
})
}
func testAccArchiveFileExists(filename string, fileSize *string) r.TestCheckFunc {
return func(s *terraform.State) error {
*fileSize = ""
fi, err := os.Stat(filename)
if err != nil {
return err
}
*fileSize = fmt.Sprintf("%d", fi.Size())
return nil
}
}
func testAccArchiveFileMissing(filename string) r.TestCheckFunc {
return func(s *terraform.State) error {
_, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return fmt.Errorf("found file expected to be deleted: %s", filename)
}
}
var testAccArchiveFileContentConfig = `
resource "archive_file" "foo" {
type = "zip"
source_content = "This is some content"
source_content_filename = "content.txt"
output_path = "zip_file_acc_test.zip"
}
`
var tmpDir = os.TempDir() + "/test"
var testAccArchiveFileOutputPath = fmt.Sprintf(`
resource "archive_file" "foo" {
type = "zip"
source_content = "This is some content"
source_content_filename = "content.txt"
output_path = "%s/test.zip"
}
`, tmpDir)
var testAccArchiveFileFileConfig = `
resource "archive_file" "foo" {
type = "zip"
source_file = "test-fixtures/test-file.txt"
output_path = "zip_file_acc_test.zip"
}
`
var testAccArchiveFileDirConfig = `
resource "archive_file" "foo" {
type = "zip"
source_dir = "test-fixtures/test-dir"
output_path = "zip_file_acc_test.zip"
}
`

View File

@ -0,0 +1 @@
This is file 1

View File

@ -0,0 +1 @@
This is file 2

View File

@ -0,0 +1 @@
This is file 3

View File

@ -0,0 +1 @@
This is test content

View File

@ -0,0 +1,107 @@
package archive
import (
"archive/zip"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
type ZipArchiver struct {
filepath string
filewriter *os.File
writer *zip.Writer
}
func NewZipArchiver(filepath string) Archiver {
return &ZipArchiver{
filepath: filepath,
}
}
func (a *ZipArchiver) ArchiveContent(content []byte, infilename string) error {
if err := a.open(); err != nil {
return err
}
defer a.close()
f, err := a.writer.Create(infilename)
if err != nil {
return err
}
_, err = f.Write(content)
return err
}
func (a *ZipArchiver) ArchiveFile(infilename string) error {
fi, err := assertValidFile(infilename)
if err != nil {
return err
}
content, err := ioutil.ReadFile(infilename)
if err != nil {
return err
}
return a.ArchiveContent(content, fi.Name())
}
func (a *ZipArchiver) ArchiveDir(indirname string) error {
_, err := assertValidDir(indirname)
if err != nil {
return err
}
if err := a.open(); err != nil {
return err
}
defer a.close()
return filepath.Walk(indirname, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if err != nil {
return err
}
relname, err := filepath.Rel(indirname, path)
if err != nil {
return fmt.Errorf("error relativizing file for archival: %s", err)
}
f, err := a.writer.Create(relname)
if err != nil {
return fmt.Errorf("error creating file inside archive: %s", err)
}
content, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("error reading file for archival: %s", err)
}
_, err = f.Write(content)
return err
})
}
func (a *ZipArchiver) open() error {
f, err := os.Create(a.filepath)
if err != nil {
return err
}
a.filewriter = f
a.writer = zip.NewWriter(f)
return nil
}
func (a *ZipArchiver) close() {
if a.writer != nil {
a.writer.Close()
a.writer = nil
}
if a.filewriter != nil {
a.filewriter.Close()
a.filewriter = nil
}
}

View File

@ -0,0 +1,84 @@
package archive
import (
"archive/zip"
"io/ioutil"
"testing"
)
func TestZipArchiver_Content(t *testing.T) {
zipfilepath := "archive-content.zip"
archiver := NewZipArchiver(zipfilepath)
if err := archiver.ArchiveContent([]byte("This is some content"), "content.txt"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ensureContents(t, zipfilepath, map[string][]byte{
"content.txt": []byte("This is some content"),
})
}
func TestZipArchiver_File(t *testing.T) {
zipfilepath := "archive-file.zip"
archiver := NewZipArchiver(zipfilepath)
if err := archiver.ArchiveFile("./test-fixtures/test-file.txt"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ensureContents(t, zipfilepath, map[string][]byte{
"test-file.txt": []byte("This is test content"),
})
}
func TestZipArchiver_Dir(t *testing.T) {
zipfilepath := "archive-dir.zip"
archiver := NewZipArchiver(zipfilepath)
if err := archiver.ArchiveDir("./test-fixtures/test-dir"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ensureContents(t, zipfilepath, map[string][]byte{
"file1.txt": []byte("This is file 1"),
"file2.txt": []byte("This is file 2"),
"file3.txt": []byte("This is file 3"),
})
}
func ensureContents(t *testing.T, zipfilepath string, wants map[string][]byte) {
r, err := zip.OpenReader(zipfilepath)
if err != nil {
t.Fatalf("could not open zip file: %s", err)
}
defer r.Close()
if len(r.File) != len(wants) {
t.Errorf("mismatched file count, got %d, want %d", len(r.File), len(wants))
}
for _, cf := range r.File {
ensureContent(t, wants, cf)
}
}
func ensureContent(t *testing.T, wants map[string][]byte, got *zip.File) {
want, ok := wants[got.Name]
if !ok {
t.Errorf("additional file in zip: %s", got.Name)
return
}
r, err := got.Open()
if err != nil {
t.Errorf("could not open file: %s", err)
}
defer r.Close()
gotContentBytes, err := ioutil.ReadAll(r)
if err != nil {
t.Errorf("could not read file: %s", err)
}
wantContent := string(want)
gotContent := string(gotContentBytes)
if gotContent != wantContent {
t.Errorf("mismatched content\ngot\n%s\nwant\n%s", gotContent, wantContent)
}
}

View File

@ -0,0 +1,149 @@
package atlas
import (
"fmt"
"github.com/hashicorp/atlas-go/v1"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAtlasArtifact() *schema.Resource {
return &schema.Resource{
Read: dataSourceArtifactRead,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"build": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"metadata_keys": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"metadata": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"file_url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"metadata_full": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
"slug": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"version_real": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceArtifactRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*atlas.Client)
// Parse the slug from the name given of the artifact since the API
// expects these to be split.
user, name, err := atlas.ParseSlug(d.Get("name").(string))
if err != nil {
return err
}
// Filter by version or build if given
var build, version string
if v, ok := d.GetOk("version"); ok {
version = v.(string)
} else if b, ok := d.GetOk("build"); ok {
build = b.(string)
}
// If we have neither, default to latest version
if build == "" && version == "" {
version = "latest"
}
// Compile the metadata search params
md := make(map[string]string)
for _, v := range d.Get("metadata_keys").(*schema.Set).List() {
md[v.(string)] = atlas.MetadataAnyValue
}
for k, v := range d.Get("metadata").(map[string]interface{}) {
md[k] = v.(string)
}
// Do the search!
vs, err := client.ArtifactSearch(&atlas.ArtifactSearchOpts{
User: user,
Name: name,
Type: d.Get("type").(string),
Build: build,
Version: version,
Metadata: md,
})
if err != nil {
return fmt.Errorf(
"Error searching for artifact '%s/%s': %s",
user, name, err)
}
if len(vs) == 0 {
return fmt.Errorf("No matching artifact for '%s/%s'", user, name)
} else if len(vs) > 1 {
return fmt.Errorf(
"Got %d results for '%s/%s', only one is allowed",
len(vs), user, name)
}
v := vs[0]
d.SetId(v.ID)
if v.ID == "" {
d.SetId(fmt.Sprintf("%s %d", v.Tag, v.Version))
}
d.Set("version_real", v.Version)
d.Set("metadata_full", cleanMetadata(v.Metadata))
d.Set("slug", v.Slug)
d.Set("file_url", "")
if u, err := client.ArtifactFileURL(v); err != nil {
return fmt.Errorf(
"Error reading file URL: %s", err)
} else if u != nil {
d.Set("file_url", u.String())
}
return nil
}

View File

@ -0,0 +1,150 @@
package atlas
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceArtifact_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataArtifact_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"),
),
},
},
})
}
func TestAccDataSourceArtifact_metadata(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataArtifact_metadata,
Check: resource.ComposeTestCheckFunc(
testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"),
testAccCheckDataArtifactState("id", "x86"),
testAccCheckDataArtifactState("metadata_full.arch", "x86"),
),
},
},
})
}
func TestAccDataSourceArtifact_metadataSet(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataArtifact_metadataSet,
Check: resource.ComposeTestCheckFunc(
testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"),
testAccCheckDataArtifactState("id", "x64"),
testAccCheckDataArtifactState("metadata_full.arch", "x64"),
),
},
},
})
}
func TestAccDataSourceArtifact_buildLatest(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataArtifact_buildLatest,
Check: resource.ComposeTestCheckFunc(
testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"),
),
},
},
})
}
func TestAccDataSourceArtifact_versionAny(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataArtifact_versionAny,
Check: resource.ComposeTestCheckFunc(
testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"),
),
},
},
})
}
func testAccCheckDataArtifactState(key, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["data.atlas_artifact.foobar"]
if !ok {
return fmt.Errorf("Not found: %s", "data.atlas_artifact.foobar")
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
p := rs.Primary
if p.Attributes[key] != value {
return fmt.Errorf(
"%s != %s (actual: %s)", key, value, p.Attributes[key])
}
return nil
}
}
const testAccDataArtifact_basic = `
data "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"
type = "foo"
}`
const testAccDataArtifact_metadata = `
data "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"
type = "foo"
metadata {
arch = "x86"
}
version = "any"
}`
const testAccDataArtifact_metadataSet = `
data "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"
type = "foo"
metadata_keys = ["arch"]
version = "any"
}`
const testAccDataArtifact_buildLatest = `
data "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"
type = "foo"
build = "latest"
metadata {
arch = "x86"
}
}`
const testAccDataArtifact_versionAny = `
data "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"
type = "foo"
version = "any"
}`

View File

@ -31,6 +31,10 @@ func Provider() terraform.ResourceProvider {
},
},
DataSourcesMap: map[string]*schema.Resource{
"atlas_artifact": dataSourceAtlasArtifact(),
},
ResourcesMap: map[string]*schema.Resource{
"atlas_artifact": resourceArtifact(),
},
@ -48,6 +52,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
return nil, err
}
}
client.DefaultHeader.Set(terraform.VersionHeader, terraform.VersionString())
client.Token = d.Get("token").(string)
return client, nil

View File

@ -22,9 +22,10 @@ func resourceArtifact() *schema.Resource {
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
Deprecated: `atlas_artifact is now deprecated. Use the Atlas Artifact Data Source instead. See https://terraform.io/docs/providers/atlas/d/artifact.html`,
},
"type": &schema.Schema{

View File

@ -2,7 +2,6 @@ package atlas
import (
"fmt"
"reflect"
"testing"
"github.com/hashicorp/terraform/helper/resource"
@ -109,21 +108,6 @@ func testAccCheckArtifactState(key, value string) resource.TestCheckFunc {
}
}
func TestCleanMetadata(t *testing.T) {
in := map[string]string{
"region.us-east-1": "in",
"what is this?": "out",
}
exp := map[string]string{
"region-us-east-1": "in",
"what-is-this-": "out",
}
out := cleanMetadata(in)
if !reflect.DeepEqual(out, exp) {
t.Fatalf("bad: %#v", out)
}
}
const testAccArtifact_basic = `
resource "atlas_artifact" "foobar" {
name = "hashicorp/tf-provider-test"

View File

@ -1,6 +1,7 @@
package aws
import (
"errors"
"fmt"
"log"
"os"
@ -11,10 +12,12 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
awsCredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
)
@ -25,7 +28,12 @@ func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (
cfg := &aws.Config{}
setOptionalEndpoint(cfg)
metadataClient := ec2metadata.New(session.New(cfg))
sess, err := session.NewSession(cfg)
if err != nil {
return "", errwrap.Wrapf("Error creating AWS session: %s", err)
}
metadataClient := ec2metadata.New(sess)
info, err := metadataClient.IAMInfo()
if err != nil {
// This can be triggered when no IAM Role is assigned
@ -69,7 +77,7 @@ func GetAccountId(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string) (
}
if len(outRoles.Roles) < 1 {
return "", fmt.Errorf("Failed getting account ID via 'iam:ListRoles': No roles available")
return "", errors.New("Failed getting account ID via 'iam:ListRoles': No roles available")
}
return parseAccountIdFromArn(*outRoles.Roles[0].Arn)
@ -86,18 +94,18 @@ func parseAccountIdFromArn(arn string) (string, error) {
// This function is responsible for reading credentials from the
// environment in the case that they're not explicitly specified
// in the Terraform configuration.
func GetCredentials(key, secret, token, profile, credsfile string) *awsCredentials.Credentials {
func GetCredentials(c *Config) (*awsCredentials.Credentials, error) {
// build a chain provider, lazy-evaulated by aws-sdk
providers := []awsCredentials.Provider{
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
AccessKeyID: key,
SecretAccessKey: secret,
SessionToken: token,
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
SessionToken: c.Token,
}},
&awsCredentials.EnvProvider{},
&awsCredentials.SharedCredentialsProvider{
Filename: credsfile,
Profile: profile,
Filename: c.CredsFilename,
Profile: c.Profile,
},
}
@ -111,25 +119,88 @@ func GetCredentials(key, secret, token, profile, credsfile string) *awsCredentia
}
usedEndpoint := setOptionalEndpoint(cfg)
// Real AWS should reply to a simple metadata request.
// We check it actually does to ensure something else didn't just
// happen to be listening on the same IP:Port
metadataClient := ec2metadata.New(session.New(cfg))
if metadataClient.Available() {
providers = append(providers, &ec2rolecreds.EC2RoleProvider{
Client: metadataClient,
})
log.Printf("[INFO] AWS EC2 instance detected via default metadata" +
" API endpoint, EC2RoleProvider added to the auth chain")
} else {
if usedEndpoint == "" {
usedEndpoint = "default location"
if !c.SkipMetadataApiCheck {
// Real AWS should reply to a simple metadata request.
// We check it actually does to ensure something else didn't just
// happen to be listening on the same IP:Port
metadataClient := ec2metadata.New(session.New(cfg))
if metadataClient.Available() {
providers = append(providers, &ec2rolecreds.EC2RoleProvider{
Client: metadataClient,
})
log.Print("[INFO] AWS EC2 instance detected via default metadata" +
" API endpoint, EC2RoleProvider added to the auth chain")
} else {
if usedEndpoint == "" {
usedEndpoint = "default location"
}
log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+
"as it doesn't return any instance-id", usedEndpoint)
}
log.Printf("[WARN] Ignoring AWS metadata API endpoint at %s "+
"as it doesn't return any instance-id", usedEndpoint)
}
return awsCredentials.NewChainCredentials(providers)
// This is the "normal" flow (i.e. not assuming a role)
if c.AssumeRoleARN == "" {
return awsCredentials.NewChainCredentials(providers), nil
}
// Otherwise we need to construct and STS client with the main credentials, and verify
// that we can assume the defined role.
log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q)",
c.AssumeRoleARN, c.AssumeRoleSessionName, c.AssumeRoleExternalID)
creds := awsCredentials.NewChainCredentials(providers)
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, errors.New(`No valid credential sources found for AWS Provider.
Please see https://terraform.io/docs/providers/aws/index.html for more information on
providing credentials for the AWS Provider`)
}
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}
stsclient := sts.New(session.New(awsConfig))
assumeRoleProvider := &stscreds.AssumeRoleProvider{
Client: stsclient,
RoleARN: c.AssumeRoleARN,
}
if c.AssumeRoleSessionName != "" {
assumeRoleProvider.RoleSessionName = c.AssumeRoleSessionName
}
if c.AssumeRoleExternalID != "" {
assumeRoleProvider.ExternalID = aws.String(c.AssumeRoleExternalID)
}
providers = []awsCredentials.Provider{assumeRoleProvider}
assumeRoleCreds := awsCredentials.NewChainCredentials(providers)
_, err = assumeRoleCreds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, fmt.Errorf("The role %q cannot be assumed.\n\n"+
" There are a number of possible causes of this - the most common are:\n"+
" * The credentials used in order to assume the role are invalid\n"+
" * The credentials do not have appropriate permission to assume the role\n"+
" * The role ARN is not valid",
c.AssumeRoleARN)
}
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
return assumeRoleCreds, nil
}
func setOptionalEndpoint(cfg *aws.Config) string {

View File

@ -51,7 +51,7 @@ func TestAWSGetAccountId_shouldBeValid_EC2RoleHasPriority(t *testing.T) {
defer awsTs()
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_GetUser_valid, "text/xml"},
},
@ -72,7 +72,7 @@ func TestAWSGetAccountId_shouldBeValid_EC2RoleHasPriority(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromIamUser(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_GetUser_valid, "text/xml"},
},
@ -94,11 +94,11 @@ func TestAWSGetAccountId_shouldBeValid_fromIamUser(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromGetCallerIdentity(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetCallerIdentity&Version=2011-06-15"},
Response: &iamResponse{200, stsResponse_GetCallerIdentity_valid, "text/xml"},
},
@ -119,15 +119,15 @@ func TestAWSGetAccountId_shouldBeValid_fromGetCallerIdentity(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_fromIamListRoles(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetCallerIdentity&Version=2011-06-15"},
Response: &iamResponse{403, stsResponse_GetCallerIdentity_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_ListRoles_valid, "text/xml"},
},
@ -148,11 +148,11 @@ func TestAWSGetAccountId_shouldBeValid_fromIamListRoles(t *testing.T) {
func TestAWSGetAccountId_shouldBeValid_federatedRole(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{400, iamResponse_GetUser_federatedFailure, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{200, iamResponse_ListRoles_valid, "text/xml"},
},
@ -173,11 +173,11 @@ func TestAWSGetAccountId_shouldBeValid_federatedRole(t *testing.T) {
func TestAWSGetAccountId_shouldError_unauthorizedFromIam(t *testing.T) {
iamEndpoints := []*iamEndpoint{
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=GetUser&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_GetUser_unauthorized, "text/xml"},
},
&iamEndpoint{
{
Request: &iamRequest{"POST", "/", "Action=ListRoles&MaxItems=1&Version=2010-05-08"},
Response: &iamResponse{403, iamResponse_ListRoles_unauthorized, "text/xml"},
},
@ -218,15 +218,20 @@ func TestAWSGetCredentials_shouldError(t *testing.T) {
defer resetEnv()
cfg := Config{}
c := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
_, err := c.Get()
c, err := GetCredentials(&cfg)
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatalf("Expected NoCredentialProviders error")
t.Fatal("Expected NoCredentialProviders error")
}
}
_, err = c.Get()
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
t.Fatal("Expected NoCredentialProviders error")
}
}
if err == nil {
t.Fatalf("Expected an error with empty env, keys, and IAM in AWS Config")
t.Fatal("Expected an error with empty env, keys, and IAM in AWS Config")
}
}
@ -251,14 +256,19 @@ func TestAWSGetCredentials_shouldBeStatic(t *testing.T) {
Token: c.Token,
}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
creds, err := GetCredentials(&cfg)
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if v.AccessKeyID != c.Key {
t.Fatalf("AccessKeyID mismatch, expected: (%s), got (%s)", c.Key, v.AccessKeyID)
}
@ -286,9 +296,12 @@ func TestAWSGetCredentials_shouldIAM(t *testing.T) {
// An empty config, no key supplied
cfg := Config{}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
creds, err := GetCredentials(&cfg)
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
@ -335,10 +348,14 @@ func TestAWSGetCredentials_shouldIgnoreIAM(t *testing.T) {
Token: c.Token,
}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
creds, err := GetCredentials(&cfg)
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -362,7 +379,14 @@ func TestAWSGetCredentials_shouldErrorWithInvalidEndpoint(t *testing.T) {
ts := invalidAwsEnv(t)
defer ts()
creds := GetCredentials("", "", "", "", "")
creds, err := GetCredentials(&Config{})
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err == nil {
t.Fatal("Expected error returned when getting creds w/ invalid EC2 endpoint")
@ -380,11 +404,17 @@ func TestAWSGetCredentials_shouldIgnoreInvalidEndpoint(t *testing.T) {
ts := invalidAwsEnv(t)
defer ts()
creds := GetCredentials("accessKey", "secretKey", "", "", "")
creds, err := GetCredentials(&Config{AccessKey: "accessKey", SecretKey: "secretKey"})
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Getting static credentials w/ invalid EC2 endpoint failed: %s", err)
}
if creds == nil {
t.Fatal("Expected a static creds provider to be returned")
}
if v.ProviderName != "StaticProvider" {
t.Fatalf("Expected provider name to be %q, %q given", "StaticProvider", v.ProviderName)
@ -406,10 +436,14 @@ func TestAWSGetCredentials_shouldCatchEC2RoleProvider(t *testing.T) {
ts := awsEnv(t)
defer ts()
creds := GetCredentials("", "", "", "", "")
if creds == nil {
t.Fatalf("Expected an EC2Role creds provider to be returned")
creds, err := GetCredentials(&Config{})
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected an EC2Role creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Expected no error when getting creds: %s", err)
@ -452,10 +486,14 @@ func TestAWSGetCredentials_shouldBeShared(t *testing.T) {
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
}
creds := GetCredentials("", "", "", "myprofile", file.Name())
if creds == nil {
t.Fatalf("Expected a provider chain to be returned")
creds, err := GetCredentials(&Config{Profile: "myprofile", CredsFilename: file.Name()})
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatal("Expected a provider chain to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -479,10 +517,14 @@ func TestAWSGetCredentials_shouldBeENV(t *testing.T) {
defer resetEnv()
cfg := Config{}
creds := GetCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
creds, err := GetCredentials(&cfg)
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
}
if creds == nil {
t.Fatalf("Expected a static creds provider to be returned")
}
v, err := creds.Get()
if err != nil {
t.Fatalf("Error gettings creds: %s", err)
@ -648,12 +690,15 @@ func getMockedAwsIamStsApi(endpoints []*iamEndpoint) (func(), *iam.IAM, *sts.STS
sc := awsCredentials.NewStaticCredentials("accessKey", "secretKey", "")
sess := session.New(&aws.Config{
sess, err := session.NewSession(&aws.Config{
Credentials: sc,
Region: aws.String("us-east-1"),
Endpoint: aws.String(ts.URL),
CredentialsChainVerboseErrors: aws.Bool(true),
})
if err != nil {
panic(fmt.Sprintf("Error creating AWS Session: %s", err))
}
iamConn := iam.New(sess)
stsConn := sts.New(sess)
return ts.Close, iamConn, stsConn

View File

@ -0,0 +1,14 @@
package aws
import (
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
)
func isAWSErr(err error, code string, message string) bool {
if err, ok := err.(awserr.Error); ok {
return err.Code() == code && strings.Contains(err.Message(), message)
}
return false
}

View File

@ -11,6 +11,7 @@ import (
"bytes"
"fmt"
"reflect"
"sort"
"strconv"
"time"
@ -25,6 +26,14 @@ import (
// is used to set the zone_id attribute.
const cloudFrontRoute53ZoneID = "Z2FDTNDATAQYW2"
// Define Sort interface for []*string so we can ensure the order of
// geo_restrictions.locations
type StringPtrSlice []*string
func (p StringPtrSlice) Len() int { return len(p) }
func (p StringPtrSlice) Less(i, j int) bool { return *p[i] < *p[j] }
func (p StringPtrSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Assemble the *cloudfront.DistributionConfig variable. Calls out to various
// expander functions to convert attributes and sub-attributes to the various
// complex structures which are necessary to properly build the
@ -873,6 +882,7 @@ func expandGeoRestriction(m map[string]interface{}) *cloudfront.GeoRestriction {
if v, ok := m["locations"]; ok {
gr.Quantity = aws.Int64(int64(len(v.([]interface{}))))
gr.Items = expandStringList(v.([]interface{}))
sort.Sort(StringPtrSlice(gr.Items))
} else {
gr.Quantity = aws.Int64(0)
}
@ -884,6 +894,7 @@ func flattenGeoRestriction(gr *cloudfront.GeoRestriction) map[string]interface{}
m["restriction_type"] = *gr.RestrictionType
if gr.Items != nil {
sort.Sort(StringPtrSlice(gr.Items))
m["locations"] = flattenStringList(gr.Items)
}
return m

View File

@ -1,23 +1,19 @@
package aws
import (
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"strings"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/terraform"
"crypto/tls"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/apigateway"
"github.com/aws/aws-sdk-go/service/applicationautoscaling"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudfront"
@ -38,6 +34,7 @@ import (
elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
"github.com/aws/aws-sdk-go/service/elastictranscoder"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/emr"
"github.com/aws/aws-sdk-go/service/firehose"
"github.com/aws/aws-sdk-go/service/glacier"
@ -50,9 +47,16 @@ import (
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/aws/aws-sdk-go/service/simpledb"
"github.com/aws/aws-sdk-go/service/sns"
"github.com/aws/aws-sdk-go/service/sqs"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/terraform"
)
type Config struct {
@ -64,6 +68,10 @@ type Config struct {
Region string
MaxRetries int
AssumeRoleARN string
AssumeRoleExternalID string
AssumeRoleSessionName string
AllowedAccountIds []interface{}
ForbiddenAccountIds []interface{}
@ -72,7 +80,13 @@ type Config struct {
Ec2Endpoint string
IamEndpoint string
ElbEndpoint string
S3Endpoint string
Insecure bool
SkipCredsValidation bool
SkipRequestingAccountId bool
SkipMetadataApiCheck bool
S3ForcePathStyle bool
}
type AWSClient struct {
@ -89,11 +103,15 @@ type AWSClient struct {
ecsconn *ecs.ECS
efsconn *efs.EFS
elbconn *elb.ELB
elbv2conn *elbv2.ELBV2
emrconn *emr.EMR
esconn *elasticsearch.ElasticsearchService
apigateway *apigateway.APIGateway
appautoscalingconn *applicationautoscaling.ApplicationAutoScaling
autoscalingconn *autoscaling.AutoScaling
s3conn *s3.S3
sesConn *ses.SES
simpledbconn *simpledb.SimpleDB
sqsconn *sqs.SQS
snsconn *sns.SNS
stsconn *sts.STS
@ -114,218 +132,170 @@ type AWSClient struct {
glacierconn *glacier.Glacier
codedeployconn *codedeploy.CodeDeploy
codecommitconn *codecommit.CodeCommit
ssmconn *ssm.SSM
}
// 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
// specified and we're attempting to use the environment.
var errs []error
log.Println("[INFO] Building AWS region structure")
err := c.ValidateRegion()
if err != nil {
errs = append(errs, err)
return nil, err
}
var client AWSClient
if len(errs) == 0 {
// store AWS region in client struct, for region specific operations such as
// bucket storage in S3
client.region = c.Region
// store AWS region in client struct, for region specific operations such as
// bucket storage in S3
client.region = c.Region
log.Println("[INFO] Building AWS auth structure")
creds := GetCredentials(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename)
// Call Get to check for credential provider. If nothing found, we'll get an
// error, and we can present it nicely to the user
cp, 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 Provider.
log.Println("[INFO] Building AWS auth structure")
creds, err := GetCredentials(c)
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
cp, err := creds.Get()
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
return nil, errors.New(`No valid credential sources found for AWS Provider.
Please see https://terraform.io/docs/providers/aws/index.html for more information on
providing credentials for the AWS Provider`))
} else {
errs = append(errs, fmt.Errorf("Error loading credentials for AWS Provider: %s", err))
}
return nil, &multierror.Error{Errors: errs}
providing credentials for the AWS Provider`)
}
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
}
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
awsConfig := &aws.Config{
Credentials: creds,
Region: aws.String(c.Region),
MaxRetries: aws.Int(c.MaxRetries),
HTTPClient: cleanhttp.DefaultClient(),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}
if logging.IsDebugOrHigher() {
awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
awsConfig.Logger = awsLogger{}
}
if c.Insecure {
transport := awsConfig.HTTPClient.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
if logging.IsDebugOrHigher() {
awsConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody)
awsConfig.Logger = awsLogger{}
}
// Set up base session
sess, err := session.NewSession(awsConfig)
if err != nil {
return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
}
sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
if c.Insecure {
transport := awsConfig.HTTPClient.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
// Some services exist only in us-east-1, e.g. because they manage
// resources that can span across multiple regions, or because
// signature format v4 requires region to be us-east-1 for global
// endpoints:
// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
// Set up base session
sess := session.New(awsConfig)
sess.Handlers.Build.PushFrontNamed(addTerraformVersionToUserAgent)
// Some services have user-configurable endpoints
awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
log.Println("[INFO] Initializing IAM Connection")
awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)})
client.iamconn = iam.New(awsIamSess)
log.Println("[INFO] Initializing STS connection")
client.stsconn = sts.New(sess)
// These two services need to be set up early so we can check on AccountID
client.iamconn = iam.New(awsIamSess)
client.stsconn = sts.New(sess)
if !c.SkipCredsValidation {
err = c.ValidateCredentials(client.stsconn)
if err != nil {
errs = append(errs, err)
return nil, &multierror.Error{Errors: errs}
return nil, err
}
}
// Some services exist only in us-east-1, e.g. because they manage
// resources that can span across multiple regions, or because
// signature format v4 requires region to be us-east-1 for global
// endpoints:
// http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")})
if !c.SkipRequestingAccountId {
accountId, err := GetAccountId(client.iamconn, client.stsconn, cp.ProviderName)
if err == nil {
client.accountid = accountId
}
log.Println("[INFO] Initializing DynamoDB connection")
dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)})
client.dynamodbconn = dynamodb.New(dynamoSess)
log.Println("[INFO] Initializing Cloudfront connection")
client.cloudfrontconn = cloudfront.New(sess)
log.Println("[INFO] Initializing ELB connection")
awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)})
client.elbconn = elb.New(awsElbSess)
log.Println("[INFO] Initializing S3 connection")
client.s3conn = s3.New(sess)
log.Println("[INFO] Initializing SQS connection")
client.sqsconn = sqs.New(sess)
log.Println("[INFO] Initializing SNS connection")
client.snsconn = sns.New(sess)
log.Println("[INFO] Initializing RDS Connection")
client.rdsconn = rds.New(sess)
log.Println("[INFO] Initializing Kinesis Connection")
kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)})
client.kinesisconn = kinesis.New(kinesisSess)
log.Println("[INFO] Initializing Elastic Beanstalk Connection")
client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
log.Println("[INFO] Initializing Elastic Transcoder Connection")
client.elastictranscoderconn = elastictranscoder.New(sess)
authErr := c.ValidateAccountId(client.accountid)
if authErr != nil {
errs = append(errs, authErr)
}
log.Println("[INFO] Initializing Kinesis Firehose Connection")
client.firehoseconn = firehose.New(sess)
log.Println("[INFO] Initializing AutoScaling connection")
client.autoscalingconn = autoscaling.New(sess)
log.Println("[INFO] Initializing EC2 Connection")
awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)})
client.ec2conn = ec2.New(awsEc2Sess)
log.Println("[INFO] Initializing ECR Connection")
client.ecrconn = ecr.New(sess)
log.Println("[INFO] Initializing API Gateway")
client.apigateway = apigateway.New(sess)
log.Println("[INFO] Initializing ECS Connection")
client.ecsconn = ecs.New(sess)
log.Println("[INFO] Initializing EFS Connection")
client.efsconn = efs.New(sess)
log.Println("[INFO] Initializing ElasticSearch Connection")
client.esconn = elasticsearch.New(sess)
log.Println("[INFO] Initializing EMR Connection")
client.emrconn = emr.New(sess)
log.Println("[INFO] Initializing Route 53 connection")
client.r53conn = route53.New(usEast1Sess)
log.Println("[INFO] Initializing Elasticache Connection")
client.elasticacheconn = elasticache.New(sess)
log.Println("[INFO] Initializing Lambda Connection")
client.lambdaconn = lambda.New(sess)
log.Println("[INFO] Initializing Cloudformation Connection")
client.cfconn = cloudformation.New(sess)
log.Println("[INFO] Initializing CloudWatch SDK connection")
client.cloudwatchconn = cloudwatch.New(sess)
log.Println("[INFO] Initializing CloudWatch Events connection")
client.cloudwatcheventsconn = cloudwatchevents.New(sess)
log.Println("[INFO] Initializing CloudTrail connection")
client.cloudtrailconn = cloudtrail.New(sess)
log.Println("[INFO] Initializing CloudWatch Logs connection")
client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
log.Println("[INFO] Initializing OpsWorks Connection")
client.opsworksconn = opsworks.New(usEast1Sess)
log.Println("[INFO] Initializing Directory Service connection")
client.dsconn = directoryservice.New(sess)
log.Println("[INFO] Initializing Glacier connection")
client.glacierconn = glacier.New(sess)
log.Println("[INFO] Initializing CodeDeploy Connection")
client.codedeployconn = codedeploy.New(sess)
log.Println("[INFO] Initializing CodeCommit SDK connection")
client.codecommitconn = codecommit.New(usEast1Sess)
log.Println("[INFO] Initializing Redshift SDK connection")
client.redshiftconn = redshift.New(sess)
log.Println("[INFO] Initializing KMS connection")
client.kmsconn = kms.New(sess)
}
if len(errs) > 0 {
return nil, &multierror.Error{Errors: errs}
authErr := c.ValidateAccountId(client.accountid)
if authErr != nil {
return nil, err
}
client.apigateway = apigateway.New(sess)
client.appautoscalingconn = applicationautoscaling.New(sess)
client.autoscalingconn = autoscaling.New(sess)
client.cfconn = cloudformation.New(sess)
client.cloudfrontconn = cloudfront.New(sess)
client.cloudtrailconn = cloudtrail.New(sess)
client.cloudwatchconn = cloudwatch.New(sess)
client.cloudwatcheventsconn = cloudwatchevents.New(sess)
client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
client.codecommitconn = codecommit.New(usEast1Sess)
client.codedeployconn = codedeploy.New(sess)
client.dsconn = directoryservice.New(sess)
client.dynamodbconn = dynamodb.New(dynamoSess)
client.ec2conn = ec2.New(awsEc2Sess)
client.ecrconn = ecr.New(sess)
client.ecsconn = ecs.New(sess)
client.efsconn = efs.New(sess)
client.elasticacheconn = elasticache.New(sess)
client.elasticbeanstalkconn = elasticbeanstalk.New(sess)
client.elastictranscoderconn = elastictranscoder.New(sess)
client.elbconn = elb.New(awsElbSess)
client.elbv2conn = elbv2.New(awsElbSess)
client.emrconn = emr.New(sess)
client.esconn = elasticsearch.New(sess)
client.firehoseconn = firehose.New(sess)
client.glacierconn = glacier.New(sess)
client.kinesisconn = kinesis.New(kinesisSess)
client.kmsconn = kms.New(sess)
client.lambdaconn = lambda.New(sess)
client.opsworksconn = opsworks.New(usEast1Sess)
client.r53conn = route53.New(usEast1Sess)
client.rdsconn = rds.New(sess)
client.redshiftconn = redshift.New(sess)
client.simpledbconn = simpledb.New(sess)
client.s3conn = s3.New(awsS3Sess)
client.sesConn = ses.New(sess)
client.snsconn = sns.New(sess)
client.sqsconn = sqs.New(sess)
client.ssmconn = ssm.New(sess)
return &client, nil
}
// ValidateRegion returns an error if the configured region is not a
// valid aws region and nil otherwise.
func (c *Config) ValidateRegion() error {
var regions = [12]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
"ap-northeast-2", "sa-east-1", "cn-north-1", "us-gov-west-1"}
var regions = [13]string{
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"cn-north-1",
"eu-central-1",
"eu-west-1",
"sa-east-1",
"us-east-1",
"us-gov-west-1",
"us-west-1",
"us-west-2",
}
for _, valid := range regions {
if c.Region == valid {
@ -348,7 +318,7 @@ func (c *Config) ValidateAccountId(accountId string) error {
return nil
}
log.Printf("[INFO] Validating account ID")
log.Println("[INFO] Validating account ID")
if c.ForbiddenAccountIds != nil {
for _, id := range c.ForbiddenAccountIds {
@ -375,7 +345,7 @@ func (c *Config) ValidateAccountId(accountId string) error {
var addTerraformVersionToUserAgent = request.NamedHandler{
Name: "terraform.TerraformVersionUserAgentHandler",
Fn: request.MakeAddToUserAgentHandler(
"terraform", terraform.Version, terraform.VersionPrerelease),
"terraform", terraform.VersionString()),
}
type awsLogger struct{}

View File

@ -21,6 +21,11 @@ func dataSourceAwsAvailabilityZones() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"state": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateStateType,
},
},
}
}
@ -28,25 +33,55 @@ func dataSourceAwsAvailabilityZones() *schema.Resource {
func dataSourceAwsAvailabilityZonesRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
log.Printf("[DEBUG] Reading availability zones")
log.Printf("[DEBUG] Reading Availability Zones.")
d.SetId(time.Now().UTC().String())
req := &ec2.DescribeAvailabilityZonesInput{DryRun: aws.Bool(false)}
azresp, err := conn.DescribeAvailabilityZones(req)
if err != nil {
return fmt.Errorf("Error listing availability zones: %s", err)
request := &ec2.DescribeAvailabilityZonesInput{}
if v, ok := d.GetOk("state"); ok {
request.Filters = []*ec2.Filter{
&ec2.Filter{
Name: aws.String("state"),
Values: []*string{aws.String(v.(string))},
},
}
}
raw := make([]string, len(azresp.AvailabilityZones))
for i, v := range azresp.AvailabilityZones {
log.Printf("[DEBUG] Availability Zones request options: %#v", *request)
resp, err := conn.DescribeAvailabilityZones(request)
if err != nil {
return fmt.Errorf("Error fetching Availability Zones: %s", err)
}
raw := make([]string, len(resp.AvailabilityZones))
for i, v := range resp.AvailabilityZones {
raw[i] = *v.ZoneName
}
sort.Strings(raw)
if err := d.Set("names", raw); err != nil {
return fmt.Errorf("[WARN] Error setting availability zones")
return fmt.Errorf("[WARN] Error setting Availability Zones: %s", err)
}
return nil
}
func validateStateType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
validState := map[string]bool{
"available": true,
"information": true,
"impaired": true,
"unavailable": true,
}
if !validState[value] {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Availability Zone state %q. Valid states are: %q, %q, %q and %q.",
k, value, "available", "information", "impaired", "unavailable"))
}
return
}

View File

@ -22,10 +22,44 @@ func TestAccAWSAvailabilityZones_basic(t *testing.T) {
testAccCheckAwsAvailabilityZonesMeta("data.aws_availability_zones.availability_zones"),
),
},
resource.TestStep{
Config: testAccCheckAwsAvailabilityZonesStateConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsAvailabilityZoneState("data.aws_availability_zones.state_filter"),
),
},
},
})
}
func TestResourceCheckAwsAvailabilityZones_validateStateType(t *testing.T) {
_, errors := validateStateType("incorrect", "state")
if len(errors) == 0 {
t.Fatalf("Expected to trigger a validation error")
}
var testCases = []struct {
Value string
ErrCount int
}{
{
Value: "available",
ErrCount: 0,
},
{
Value: "unavailable",
ErrCount: 0,
},
}
for _, tc := range testCases {
_, errors := validateStateType(tc.Value, "state")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
}
}
}
func testAccCheckAwsAvailabilityZonesMeta(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -34,7 +68,7 @@ func testAccCheckAwsAvailabilityZonesMeta(n string) resource.TestCheckFunc {
}
if rs.Primary.ID == "" {
return fmt.Errorf("AZ resource ID not set")
return fmt.Errorf("AZ resource ID not set.")
}
actual, err := testAccCheckAwsAvailabilityZonesBuildAvailable(rs.Primary.Attributes)
@ -51,10 +85,33 @@ func testAccCheckAwsAvailabilityZonesMeta(n string) resource.TestCheckFunc {
}
}
func testAccCheckAwsAvailabilityZoneState(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find AZ resource: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("AZ resource ID not set.")
}
if _, ok := rs.Primary.Attributes["state"]; !ok {
return fmt.Errorf("AZs state filter is missing, should be set.")
}
_, err := testAccCheckAwsAvailabilityZonesBuildAvailable(rs.Primary.Attributes)
if err != nil {
return err
}
return nil
}
}
func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([]string, error) {
v, ok := attrs["names.#"]
if !ok {
return nil, fmt.Errorf("Available AZ list is missing")
return nil, fmt.Errorf("Available AZ list is missing.")
}
qty, err := strconv.Atoi(v)
if err != nil {
@ -67,7 +124,7 @@ func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([]
for n := range zones {
zone, ok := attrs["names."+strconv.Itoa(n)]
if !ok {
return nil, fmt.Errorf("AZ list corrupt, this is definitely a bug")
return nil, fmt.Errorf("AZ list corrupt, this is definitely a bug.")
}
zones[n] = zone
}
@ -75,6 +132,11 @@ func testAccCheckAwsAvailabilityZonesBuildAvailable(attrs map[string]string) ([]
}
const testAccCheckAwsAvailabilityZonesConfig = `
data "aws_availability_zones" "availability_zones" {
data "aws_availability_zones" "availability_zones" { }
`
const testAccCheckAwsAvailabilityZonesStateConfig = `
data "aws_availability_zones" "state_filter" {
state = "available"
}
`

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"log"
"regexp"
"sort"
"time"
@ -43,6 +44,11 @@ func dataSourceAwsAmi() *schema.Resource {
},
},
},
"name_regex": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"most_recent": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
@ -206,10 +212,11 @@ func dataSourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error {
executableUsers, executableUsersOk := d.GetOk("executable_users")
filters, filtersOk := d.GetOk("filter")
nameRegex, nameRegexOk := d.GetOk("name_regex")
owners, ownersOk := d.GetOk("owners")
if executableUsersOk == false && filtersOk == false && ownersOk == false {
return fmt.Errorf("One of executable_users, filters, or owners must be assigned")
if executableUsersOk == false && filtersOk == false && nameRegexOk == false && ownersOk == false {
return fmt.Errorf("One of executable_users, filters, name_regex, or owners must be assigned")
}
params := &ec2.DescribeImagesInput{}
@ -227,20 +234,33 @@ func dataSourceAwsAmiRead(d *schema.ResourceData, meta interface{}) error {
if err != nil {
return err
}
var filteredImages []*ec2.Image
if nameRegexOk == true {
r := regexp.MustCompile(nameRegex.(string))
for _, image := range resp.Images {
if r.MatchString(*image.Name) == true {
filteredImages = append(filteredImages, image)
}
}
} else {
filteredImages = resp.Images[:]
}
var image *ec2.Image
if len(resp.Images) < 1 {
if len(filteredImages) < 1 {
return fmt.Errorf("Your query returned no results. Please change your filters and try again.")
} else if len(resp.Images) > 1 {
} else if len(filteredImages) > 1 {
if (d.Get("most_recent").(bool)) == true {
log.Printf("[DEBUG] aws_ami - multiple results found and most_recent is set")
image = mostRecentAmi(resp.Images)
image = mostRecentAmi(filteredImages)
} else {
log.Printf("[DEBUG] aws_ami - multiple results found and most_recent not set")
return fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.")
}
} else {
log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *resp.Images[0].ImageId)
image = resp.Images[0]
log.Printf("[DEBUG] aws_ami - Single AMI found: %s", *filteredImages[0].ImageId)
image = filteredImages[0]
}
return amiDescriptionAttributes(d, image)
}
@ -283,39 +303,39 @@ func mostRecentAmi(images []*ec2.Image) *ec2.Image {
func amiDescriptionAttributes(d *schema.ResourceData, image *ec2.Image) error {
// Simple attributes first
d.SetId(*image.ImageId)
d.Set("architecture", *image.Architecture)
d.Set("creation_date", *image.CreationDate)
d.Set("architecture", image.Architecture)
d.Set("creation_date", image.CreationDate)
if image.Description != nil {
d.Set("description", *image.Description)
d.Set("description", image.Description)
}
d.Set("hypervisor", *image.Hypervisor)
d.Set("image_id", *image.ImageId)
d.Set("image_location", *image.ImageLocation)
d.Set("hypervisor", image.Hypervisor)
d.Set("image_id", image.ImageId)
d.Set("image_location", image.ImageLocation)
if image.ImageOwnerAlias != nil {
d.Set("image_owner_alias", *image.ImageOwnerAlias)
d.Set("image_owner_alias", image.ImageOwnerAlias)
}
d.Set("image_type", *image.ImageType)
d.Set("image_type", image.ImageType)
if image.KernelId != nil {
d.Set("kernel_id", *image.KernelId)
d.Set("kernel_id", image.KernelId)
}
d.Set("name", *image.Name)
d.Set("owner_id", *image.OwnerId)
d.Set("name", image.Name)
d.Set("owner_id", image.OwnerId)
if image.Platform != nil {
d.Set("platform", *image.Platform)
d.Set("platform", image.Platform)
}
d.Set("public", *image.Public)
d.Set("public", image.Public)
if image.RamdiskId != nil {
d.Set("ramdisk_id", *image.RamdiskId)
d.Set("ramdisk_id", image.RamdiskId)
}
if image.RootDeviceName != nil {
d.Set("root_device_name", *image.RootDeviceName)
d.Set("root_device_name", image.RootDeviceName)
}
d.Set("root_device_type", *image.RootDeviceType)
d.Set("root_device_type", image.RootDeviceType)
if image.SriovNetSupport != nil {
d.Set("sriov_net_support", *image.SriovNetSupport)
d.Set("sriov_net_support", image.SriovNetSupport)
}
d.Set("state", *image.State)
d.Set("virtualization_type", *image.VirtualizationType)
d.Set("state", image.State)
d.Set("virtualization_type", image.VirtualizationType)
// Complex types get their own functions
if err := d.Set("block_device_mappings", amiBlockDeviceMappings(image.BlockDeviceMappings)); err != nil {
return err

View File

@ -139,6 +139,22 @@ func TestAccAWSAmiDataSource_owners(t *testing.T) {
})
}
func TestAccAWSAmiDataSource_localNameFilter(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckAwsAmiDataSourceNameRegexConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsAmiDataSourceID("data.aws_ami.name_regex_filtered_ami"),
resource.TestMatchResourceAttr("data.aws_ami.name_regex_filtered_ami", "image_id", regexp.MustCompile("^ami-")),
),
},
},
})
}
func testAccCheckAwsAmiDataSourceDestroy(s *terraform.State) error {
return nil
}
@ -245,3 +261,16 @@ data "aws_ami" "amazon_ami" {
owners = ["amazon"]
}
`
// Testing name_regex parameter
const testAccCheckAwsAmiDataSourceNameRegexConfig = `
data "aws_ami" "name_regex_filtered_ami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn-ami-*"]
}
name_regex = "^amzn-ami-\\d{3}[5].*-ecs-optimized"
}
`

View File

@ -0,0 +1,40 @@
package aws
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsCallerIdentity() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsCallerIdentityRead,
Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceAwsCallerIdentityRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*AWSClient)
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.")
}
log.Printf("[DEBUG] Setting AWS Account ID to %s.", client.accountid)
d.Set("account_id", meta.(*AWSClient).accountid)
return nil
}

View File

@ -0,0 +1,48 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCallerIdentity_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAwsCallerIdentityConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsCallerIdentityAccountId("data.aws_caller_identity.current"),
),
},
},
})
}
func testAccCheckAwsCallerIdentityAccountId(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find AccountID resource: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("Account Id resource ID not set.")
}
expected := testAccProvider.Meta().(*AWSClient).accountid
if rs.Primary.Attributes["account_id"] != expected {
return fmt.Errorf("Incorrect Account ID: expected %q, got %q", expected, rs.Primary.ID)
}
return nil
}
}
const testAccCheckAwsCallerIdentityConfig_basic = `
data "aws_caller_identity" "current" { }
`

View File

@ -0,0 +1,109 @@
package aws
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsCloudFormationStack() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsCloudFormationStackRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"template_body": {
Type: schema.TypeString,
Computed: true,
StateFunc: normalizeJson,
},
"capabilities": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"description": {
Type: schema.TypeString,
Computed: true,
},
"disable_rollback": {
Type: schema.TypeBool,
Computed: true,
},
"notification_arns": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"parameters": {
Type: schema.TypeMap,
Computed: true,
},
"outputs": {
Type: schema.TypeMap,
Computed: true,
},
"timeout_in_minutes": {
Type: schema.TypeInt,
Computed: true,
},
"tags": {
Type: schema.TypeMap,
Computed: true,
},
},
}
}
func dataSourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cfconn
name := d.Get("name").(string)
input := cloudformation.DescribeStacksInput{
StackName: aws.String(name),
}
out, err := conn.DescribeStacks(&input)
if err != nil {
return fmt.Errorf("Failed describing CloudFormation stack (%s): %s", name, err)
}
if l := len(out.Stacks); l != 1 {
return fmt.Errorf("Expected 1 CloudFormation stack (%s), found %d", name, l)
}
stack := out.Stacks[0]
d.SetId(*stack.StackId)
d.Set("description", stack.Description)
d.Set("disable_rollback", stack.DisableRollback)
d.Set("timeout_in_minutes", stack.TimeoutInMinutes)
if len(stack.NotificationARNs) > 0 {
d.Set("notification_arns", schema.NewSet(schema.HashString, flattenStringList(stack.NotificationARNs)))
}
d.Set("parameters", flattenAllCloudFormationParameters(stack.Parameters))
d.Set("tags", flattenCloudFormationTags(stack.Tags))
d.Set("outputs", flattenCloudFormationOutputs(stack.Outputs))
if len(stack.Capabilities) > 0 {
d.Set("capabilities", schema.NewSet(schema.HashString, flattenStringList(stack.Capabilities)))
}
tInput := cloudformation.GetTemplateInput{
StackName: aws.String(name),
}
tOut, err := conn.GetTemplate(&tInput)
if err != nil {
return err
}
d.Set("template_body", normalizeJson(*tOut.TemplateBody))
return nil
}

View File

@ -0,0 +1,78 @@
package aws
import (
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudFormationStack_dataSource_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckAwsCloudFormationStackDataSourceConfig_basic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "outputs.%", "1"),
resource.TestMatchResourceAttr("data.aws_cloudformation_stack.network", "outputs.VPCId",
regexp.MustCompile("^vpc-[a-z0-9]{8}$")),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "capabilities.#", "0"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "disable_rollback", "false"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "notification_arns.#", "0"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "parameters.%", "1"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "parameters.CIDR", "10.10.10.0/24"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "timeout_in_minutes", "6"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "tags.%", "2"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "tags.Name", "Form the Cloud"),
resource.TestCheckResourceAttr("data.aws_cloudformation_stack.network", "tags.Second", "meh"),
),
},
},
})
}
const testAccCheckAwsCloudFormationStackDataSourceConfig_basic = `
resource "aws_cloudformation_stack" "cfs" {
name = "tf-acc-ds-networking-stack"
parameters {
CIDR = "10.10.10.0/24"
}
timeout_in_minutes = 6
template_body = <<STACK
{
"Parameters": {
"CIDR": {
"Type": "String"
}
},
"Resources" : {
"myvpc": {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : { "Ref" : "CIDR" },
"Tags" : [
{"Key": "Name", "Value": "Primary_CF_VPC"}
]
}
}
},
"Outputs" : {
"VPCId" : {
"Value" : { "Ref" : "myvpc" },
"Description" : "VPC ID"
}
}
}
STACK
tags {
Name = "Form the Cloud"
Second = "meh"
}
}
data "aws_cloudformation_stack" "network" {
name = "${aws_cloudformation_stack.cfs.name}"
}
`

View File

@ -0,0 +1,107 @@
package aws
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourceAwsEcsContainerDefinition() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsEcsContainerDefinitionRead,
Schema: map[string]*schema.Schema{
"task_definition": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"container_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
// Computed values.
"image": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"image_digest": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"cpu": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
"memory": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
"memory_reservation": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
"disable_networking": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
},
"docker_labels": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: schema.TypeString,
},
"environment": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: schema.TypeString,
},
},
}
}
func dataSourceAwsEcsContainerDefinitionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecsconn
desc, err := conn.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{
TaskDefinition: aws.String(d.Get("task_definition").(string)),
})
if err != nil {
return err
}
taskDefinition := *desc.TaskDefinition
for _, def := range taskDefinition.ContainerDefinitions {
if aws.StringValue(def.Name) != d.Get("container_name").(string) {
continue
}
d.SetId(fmt.Sprintf("%s/%s", aws.StringValue(taskDefinition.TaskDefinitionArn), d.Get("container_name").(string)))
d.Set("image", aws.StringValue(def.Image))
image := aws.StringValue(def.Image)
if strings.Contains(image, ":") {
d.Set("image_digest", strings.Split(image, ":")[1])
}
d.Set("cpu", aws.Int64Value(def.Cpu))
d.Set("memory", aws.Int64Value(def.Memory))
d.Set("memory_reservation", aws.Int64Value(def.MemoryReservation))
d.Set("disable_networking", aws.BoolValue(def.DisableNetworking))
d.Set("docker_labels", aws.StringValueMap(def.DockerLabels))
var environment = map[string]string{}
for _, keyValuePair := range def.Environment {
environment[aws.StringValue(keyValuePair.Name)] = aws.StringValue(keyValuePair.Value)
}
d.Set("environment", environment)
}
if d.Id() == "" {
return fmt.Errorf("container with name %q not found in task definition %q", d.Get("container_name").(string), d.Get("task_definition").(string))
}
return nil
}

View File

@ -0,0 +1,65 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSEcsDataSource_ecsContainerDefinition(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckAwsEcsContainerDefinitionDataSourceConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "image", "mongo:latest"),
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "image_digest", "latest"),
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "memory", "128"),
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "memory_reservation", "64"),
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "cpu", "128"),
resource.TestCheckResourceAttr("data.aws_ecs_container_definition.mongo", "environment.SECRET", "KEY"),
),
},
},
})
}
const testAccCheckAwsEcsContainerDefinitionDataSourceConfig = `
resource "aws_ecs_cluster" "default" {
name = "terraformecstest1"
}
resource "aws_ecs_task_definition" "mongo" {
family = "mongodb"
container_definitions = <<DEFINITION
[
{
"cpu": 128,
"environment": [{
"name": "SECRET",
"value": "KEY"
}],
"essential": true,
"image": "mongo:latest",
"memory": 128,
"memoryReservation": 64,
"name": "mongodb"
}
]
DEFINITION
}
resource "aws_ecs_service" "mongo" {
name = "mongodb"
cluster = "${aws_ecs_cluster.default.id}"
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
}
data "aws_ecs_container_definition" "mongo" {
task_definition = "${aws_ecs_task_definition.mongo.id}"
container_name = "mongodb"
}
`

View File

@ -0,0 +1,51 @@
package aws
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)
// See http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy
var elbAccountIdPerRegionMap = map[string]string{
"ap-northeast-1": "582318560864",
"ap-northeast-2": "600734575887",
"ap-south-1": "718504428378",
"ap-southeast-1": "114774131450",
"ap-southeast-2": "783225319266",
"cn-north-1": "638102146993",
"eu-central-1": "054676820928",
"eu-west-1": "156460612806",
"sa-east-1": "507241528517",
"us-east-1": "127311923021",
"us-gov-west": "048591011584",
"us-west-1": "027434742980",
"us-west-2": "797873946194",
}
func dataSourceAwsElbServiceAccount() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsElbServiceAccountRead,
Schema: map[string]*schema.Schema{
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
}
func dataSourceAwsElbServiceAccountRead(d *schema.ResourceData, meta interface{}) error {
region := meta.(*AWSClient).region
if v, ok := d.GetOk("region"); ok {
region = v.(string)
}
if accid, ok := elbAccountIdPerRegionMap[region]; ok {
d.SetId(accid)
return nil
}
return fmt.Errorf("Unknown region (%q)", region)
}

View File

@ -0,0 +1,38 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSElbServiceAccount_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckAwsElbServiceAccountConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_elb_service_account.main", "id", "797873946194"),
),
},
resource.TestStep{
Config: testAccCheckAwsElbServiceAccountExplicitRegionConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_elb_service_account.regional", "id", "156460612806"),
),
},
},
})
}
const testAccCheckAwsElbServiceAccountConfig = `
data "aws_elb_service_account" "main" { }
`
const testAccCheckAwsElbServiceAccountExplicitRegionConfig = `
data "aws_elb_service_account" "regional" {
region = "eu-west-1"
}
`

View File

@ -24,20 +24,20 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource {
Read: dataSourceAwsIamPolicyDocumentRead,
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
"policy_id": {
Type: schema.TypeString,
Optional: true,
},
"statement": &schema.Schema{
Type: schema.TypeSet,
"statement": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
"sid": {
Type: schema.TypeString,
Optional: true,
},
"effect": &schema.Schema{
"effect": {
Type: schema.TypeString,
Optional: true,
Default: "Allow",
@ -48,20 +48,20 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource {
"not_resources": setOfString,
"principals": dataSourceAwsIamPolicyPrincipalSchema(),
"not_principals": dataSourceAwsIamPolicyPrincipalSchema(),
"condition": &schema.Schema{
"condition": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"test": &schema.Schema{
"test": {
Type: schema.TypeString,
Required: true,
},
"variable": &schema.Schema{
"variable": {
Type: schema.TypeString,
Required: true,
},
"values": &schema.Schema{
"values": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
@ -74,7 +74,7 @@ func dataSourceAwsIamPolicyDocument() *schema.Resource {
},
},
},
"json": &schema.Schema{
"json": {
Type: schema.TypeString,
Computed: true,
},
@ -87,11 +87,11 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}
Version: "2012-10-17",
}
if policyId, hasPolicyId := d.GetOk("id"); hasPolicyId {
if policyId, hasPolicyId := d.GetOk("policy_id"); hasPolicyId {
doc.Id = policyId.(string)
}
var cfgStmts = d.Get("statement").(*schema.Set).List()
var cfgStmts = d.Get("statement").([]interface{})
stmts := make([]*IAMPolicyStatement, len(cfgStmts))
doc.Statements = stmts
for i, stmtI := range cfgStmts {
@ -100,6 +100,10 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}
Effect: cfgStmt["effect"].(string),
}
if sid, ok := cfgStmt["sid"]; ok {
stmt.Sid = sid.(string)
}
if actions := cfgStmt["actions"].(*schema.Set).List(); len(actions) > 0 {
stmt.Actions = iamPolicyDecodeConfigStringList(actions)
}
@ -146,12 +150,19 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}
return nil
}
func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in []string) []string {
out := make([]string, len(in))
for i, item := range in {
out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item)
func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in interface{}) interface{} {
switch v := in.(type) {
case string:
return dataSourceAwsIamPolicyDocumentVarReplacer.Replace(v)
case []string:
out := make([]string, len(v))
for i, item := range v {
out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item)
}
return out
default:
panic("dataSourceAwsIamPolicyDocumentReplaceVarsInList: input not string nor []string")
}
return out
}
func dataSourceAwsIamPolicyDocumentMakeConditions(in []interface{}) IAMPolicyStatementConditionSet {

View File

@ -16,7 +16,7 @@ func TestAccAWSIAMPolicyDocument(t *testing.T) {
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
{
Config: testAccAWSIAMPolicyDocumentConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckStateValue(
@ -52,7 +52,9 @@ func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
var testAccAWSIAMPolicyDocumentConfig = `
data "aws_iam_policy_document" "test" {
policy_id = "policy_id"
statement {
sid = "1"
actions = [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
@ -73,7 +75,6 @@ data "aws_iam_policy_document" "test" {
test = "StringLike"
variable = "s3:prefix"
values = [
"",
"home/",
"home/&{aws:username}/",
]
@ -110,63 +111,51 @@ data "aws_iam_policy_document" "test" {
var testAccAWSIAMPolicyDocumentExpectedJSON = `{
"Version": "2012-10-17",
"Id": "policy_id",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListAllMyBuckets"
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::*"
]
"Resource": "arn:aws:s3:::*"
},
{
"Sid": "",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::foo"
],
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::foo",
"NotPrincipal": {
"AWS": [
"arn:blahblah:example"
]
"AWS": "arn:blahblah:example"
},
"Condition": {
"StringLike": {
"s3:prefix": [
"",
"home/",
"home/${aws:username}/"
"home/${aws:username}/",
"home/"
]
}
}
},
{
"Sid": "",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::foo/home/${aws:username}/*",
"arn:aws:s3:::foo/home/${aws:username}"
],
"Principal": {
"AWS": [
"arn:blahblah:example"
]
"AWS": "arn:blahblah:example"
}
},
{
"Sid": "",
"Effect": "Deny",
"NotAction": [
"s3:*"
],
"NotResource": [
"arn:aws:s3:::*"
]
"NotAction": "s3:*",
"NotResource": "arn:aws:s3:::*"
}
]
}`

View File

@ -0,0 +1,151 @@
package aws
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sort"
"strconv"
"strings"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/helper/schema"
)
type dataSourceAwsIPRangesResult struct {
CreateDate string
Prefixes []dataSourceAwsIPRangesPrefix
SyncToken string
}
type dataSourceAwsIPRangesPrefix struct {
IpPrefix string `json:"ip_prefix"`
Region string
Service string
}
func dataSourceAwsIPRanges() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsIPRangesRead,
Schema: map[string]*schema.Schema{
"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"create_date": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"regions": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"services": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"sync_token": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
},
}
}
func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error {
conn := cleanhttp.DefaultClient()
log.Printf("[DEBUG] Reading IP ranges")
res, err := conn.Get("https://ip-ranges.amazonaws.com/ip-ranges.json")
if err != nil {
return fmt.Errorf("Error listing IP ranges: %s", err)
}
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %s", err)
}
result := new(dataSourceAwsIPRangesResult)
if err := json.Unmarshal(data, result); err != nil {
return fmt.Errorf("Error parsing result: %s", err)
}
if err := d.Set("create_date", result.CreateDate); err != nil {
return fmt.Errorf("Error setting create date: %s", err)
}
syncToken, err := strconv.Atoi(result.SyncToken)
if err != nil {
return fmt.Errorf("Error while converting sync token: %s", err)
}
d.SetId(result.SyncToken)
if err := d.Set("sync_token", syncToken); err != nil {
return fmt.Errorf("Error setting sync token: %s", err)
}
get := func(key string) *schema.Set {
set := d.Get(key).(*schema.Set)
for _, e := range set.List() {
s := e.(string)
set.Remove(s)
set.Add(strings.ToLower(s))
}
return set
}
var (
regions = get("regions")
services = get("services")
noRegionFilter = regions.Len() == 0
prefixes []string
)
for _, e := range result.Prefixes {
var (
matchRegion = noRegionFilter || regions.Contains(strings.ToLower(e.Region))
matchService = services.Contains(strings.ToLower(e.Service))
)
if matchRegion && matchService {
prefixes = append(prefixes, e.IpPrefix)
}
}
if len(prefixes) == 0 {
return fmt.Errorf(" No IP ranges result from filters")
}
sort.Strings(prefixes)
if err := d.Set("cidr_blocks", prefixes); err != nil {
return fmt.Errorf("Error setting ip ranges: %s", err)
}
return nil
}

View File

@ -0,0 +1,128 @@
package aws
import (
"fmt"
"net"
"regexp"
"sort"
"strconv"
"testing"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSIPRanges(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSIPRangesConfig,
Check: resource.ComposeTestCheckFunc(
testAccAWSIPRanges("data.aws_ip_ranges.some"),
),
},
},
})
}
func testAccAWSIPRanges(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
r := s.RootModule().Resources[n]
a := r.Primary.Attributes
var (
cidrBlockSize int
createDate time.Time
err error
syncToken int
)
if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil {
return err
}
if cidrBlockSize < 10 {
return fmt.Errorf("cidr_blocks for eu-west-1 seem suspiciously low: %d", cidrBlockSize)
}
if createDate, err = time.Parse("2006-01-02-15-04-05", a["create_date"]); err != nil {
return err
}
if syncToken, err = strconv.Atoi(a["sync_token"]); err != nil {
return err
}
if syncToken != int(createDate.Unix()) {
return fmt.Errorf("sync_token %d does not match create_date %s", syncToken, createDate)
}
var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize)
for i := range make([]string, cidrBlockSize) {
block := a[fmt.Sprintf("cidr_blocks.%d", i)]
if _, _, err := net.ParseCIDR(block); err != nil {
return fmt.Errorf("malformed CIDR block %s: %s", block, err)
}
cidrBlocks[i] = block
}
if !sort.IsSorted(cidrBlocks) {
return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks)
}
var (
regionMember = regexp.MustCompile(`regions\.\d+`)
regions, services int
serviceMember = regexp.MustCompile(`services\.\d+`)
)
for k, v := range a {
if regionMember.MatchString(k) {
if !(v == "eu-west-1" || v == "EU-central-1") {
return fmt.Errorf("unexpected region %s", v)
}
regions = regions + 1
}
if serviceMember.MatchString(k) {
if v != "EC2" {
return fmt.Errorf("unexpected service %s", v)
}
services = services + 1
}
}
if regions != 2 {
return fmt.Errorf("unexpected number of regions: %d", regions)
}
if services != 1 {
return fmt.Errorf("unexpected number of services: %d", services)
}
return nil
}
}
const testAccAWSIPRangesConfig = `
data "aws_ip_ranges" "some" {
regions = [ "eu-west-1", "EU-central-1" ]
services = [ "EC2" ]
}
`

View File

@ -0,0 +1,48 @@
package aws
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)
// See http://docs.aws.amazon.com/redshift/latest/mgmt/db-auditing.html#db-auditing-enable-logging
var redshiftServiceAccountPerRegionMap = map[string]string{
"us-east-1": "193672423079",
"us-west-1": "262260360010",
"us-west-2": "902366379725",
"ap-south-1": "865932855811",
"ap-northeast-2": "760740231472",
"ap-southeast-1": "361669875840",
"ap-southeast-2": "762762565011",
"ap-northeast-1": "404641285394",
"eu-central-1": "053454850223",
"eu-west-1": "210876761215",
}
func dataSourceAwsRedshiftServiceAccount() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsRedshiftServiceAccountRead,
Schema: map[string]*schema.Schema{
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
}
func dataSourceAwsRedshiftServiceAccountRead(d *schema.ResourceData, meta interface{}) error {
region := meta.(*AWSClient).region
if v, ok := d.GetOk("region"); ok {
region = v.(string)
}
if accid, ok := redshiftServiceAccountPerRegionMap[region]; ok {
d.SetId(accid)
return nil
}
return fmt.Errorf("Unknown region (%q)", region)
}

View File

@ -0,0 +1,38 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRedshiftServiceAccount_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckAwsRedshiftServiceAccountConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_redshift_service_account.main", "id", "902366379725"),
),
},
resource.TestStep{
Config: testAccCheckAwsRedshiftServiceAccountExplicitRegionConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_redshift_service_account.regional", "id", "210876761215"),
),
},
},
})
}
const testAccCheckAwsRedshiftServiceAccountConfig = `
data "aws_redshift_service_account" "main" { }
`
const testAccCheckAwsRedshiftServiceAccountExplicitRegionConfig = `
data "aws_redshift_service_account" "regional" {
region = "eu-west-1"
}
`

View File

@ -155,10 +155,16 @@ func dataSourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) e
d.Set("metadata", pointersMapToStringList(out.Metadata))
d.Set("server_side_encryption", out.ServerSideEncryption)
d.Set("sse_kms_key_id", out.SSEKMSKeyId)
d.Set("storage_class", out.StorageClass)
d.Set("version_id", out.VersionId)
d.Set("website_redirect_location", out.WebsiteRedirectLocation)
// The "STANDARD" (which is also the default) storage
// class when set would not be included in the results.
d.Set("storage_class", s3.StorageClassStandard)
if out.StorageClass != nil {
d.Set("storage_class", out.StorageClass)
}
if isContentTypeAllowed(out.ContentType) {
input := s3.GetObjectInput{
Bucket: aws.String(bucket),

View File

@ -154,12 +154,12 @@ func TestAccDataSourceAWSS3BucketObject_allParams(t *testing.T) {
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "server_side_encryption", ""),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "sse_kms_key_id", ""),
// Supported, but difficult to reproduce in short testing time
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "storage_class", ""),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "storage_class", "STANDARD"),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "expiration", ""),
// Currently unsupported in aws_s3_bucket_object resource
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "expires", ""),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "website_redirect_location", ""),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "metadata.#", "0"),
resource.TestCheckResourceAttr("data.aws_s3_bucket_object.obj", "metadata.%", "0"),
),
},
},

View File

@ -0,0 +1,15 @@
package aws
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/jen20/awspolicyequivalence"
)
func suppressEquivalentAwsPolicyDiffs(k, old, new string, d *schema.ResourceData) bool {
equivalent, err := awspolicy.PoliciesAreEquivalent(old, new)
if err != nil {
return false
}
return equivalent
}

View File

@ -9,6 +9,7 @@ var hostedZoneIDsMap = map[string]string{
"us-west-1": "Z2F56UZL2M1ACD",
"eu-west-1": "Z1BKCTXD74EZPE",
"eu-central-1": "Z21DNDUVLTQW6Q",
"ap-south-1": "Z11RGJOFQNVJUP",
"ap-southeast-1": "Z3O0J2DXBE1FTB",
"ap-southeast-2": "Z1WCIGYICN2BYD",
"ap-northeast-1": "Z2M4EHUR26P7ZW",

View File

@ -2,21 +2,22 @@ package aws
import (
"encoding/json"
"sort"
)
type IAMPolicyDoc struct {
Id string `json:",omitempty"`
Version string `json:",omitempty"`
Id string `json:",omitempty"`
Statements []*IAMPolicyStatement `json:"Statement"`
}
type IAMPolicyStatement struct {
Sid string `json:",omitempty"`
Sid string
Effect string `json:",omitempty"`
Actions []string `json:"Action,omitempty"`
NotActions []string `json:"NotAction,omitempty"`
Resources []string `json:"Resource,omitempty"`
NotResources []string `json:"NotResource,omitempty"`
Actions interface{} `json:"Action,omitempty"`
NotActions interface{} `json:"NotAction,omitempty"`
Resources interface{} `json:"Resource,omitempty"`
NotResources interface{} `json:"NotResource,omitempty"`
Principals IAMPolicyStatementPrincipalSet `json:"Principal,omitempty"`
NotPrincipals IAMPolicyStatementPrincipalSet `json:"NotPrincipal,omitempty"`
Conditions IAMPolicyStatementConditionSet `json:"Condition,omitempty"`
@ -24,51 +25,71 @@ type IAMPolicyStatement struct {
type IAMPolicyStatementPrincipal struct {
Type string
Identifiers []string
Identifiers interface{}
}
type IAMPolicyStatementCondition struct {
Test string
Variable string
Values []string
Values interface{}
}
type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal
type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition
func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) {
raw := map[string][]string{}
raw := map[string]interface{}{}
for _, p := range ps {
if _, ok := raw[p.Type]; !ok {
raw[p.Type] = make([]string, 0, len(p.Identifiers))
switch i := p.Identifiers.(type) {
case []string:
if _, ok := raw[p.Type]; !ok {
raw[p.Type] = make([]string, 0, len(i))
}
sort.Sort(sort.Reverse(sort.StringSlice(i)))
raw[p.Type] = append(raw[p.Type].([]string), i...)
case string:
raw[p.Type] = i
default:
panic("Unsupported data type for IAMPolicyStatementPrincipalSet")
}
raw[p.Type] = append(raw[p.Type], p.Identifiers...)
}
return json.Marshal(&raw)
}
func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) {
raw := map[string]map[string][]string{}
raw := map[string]map[string]interface{}{}
for _, c := range cs {
if _, ok := raw[c.Test]; !ok {
raw[c.Test] = map[string][]string{}
raw[c.Test] = map[string]interface{}{}
}
if _, ok := raw[c.Test][c.Variable]; !ok {
raw[c.Test][c.Variable] = make([]string, 0, len(c.Values))
switch i := c.Values.(type) {
case []string:
if _, ok := raw[c.Test][c.Variable]; !ok {
raw[c.Test][c.Variable] = make([]string, 0, len(i))
}
sort.Sort(sort.Reverse(sort.StringSlice(i)))
raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable].([]string), i...)
case string:
raw[c.Test][c.Variable] = i
default:
panic("Unsupported data type for IAMPolicyStatementConditionSet")
}
raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable], c.Values...)
}
return json.Marshal(&raw)
}
func iamPolicyDecodeConfigStringList(lI []interface{}) []string {
func iamPolicyDecodeConfigStringList(lI []interface{}) interface{} {
if len(lI) == 1 {
return lI[0].(string)
}
ret := make([]string, len(lI))
for i, vI := range lI {
ret[i] = vI.(string)
}
sort.Sort(sort.Reverse(sort.StringSlice(ret)))
return ret
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSAPIGatewayAccount_importBasic(t *testing.T) {
resourceName := "aws_api_gateway_account.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAPIGatewayAccountDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAPIGatewayAccountConfig_empty,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSAPIGatewayApiKey_importBasic(t *testing.T) {
resourceName := "aws_api_gateway_api_key.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAPIGatewayApiKeyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAPIGatewayApiKeyConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsCloudFrontDistributionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
conn := meta.(*AWSClient).cloudfrontconn
id := d.Id()
resp, err := conn.GetDistributionConfig(&cloudfront.GetDistributionConfigInput{
Id: aws.String(id),
})
if err != nil {
return nil, err
}
distConfig := resp.DistributionConfig
results := make([]*schema.ResourceData, 1)
err = flattenDistributionConfig(d, distConfig)
if err != nil {
return nil, err
}
results[0] = d
return results, nil
}

View File

@ -0,0 +1,30 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudFrontDistribution_importBasic(t *testing.T) {
resourceName := "aws_cloudfront_distribution.s3_distribution"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontDistributionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudFrontDistributionS3Config,
},
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

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudFrontOriginAccessIdentity_importBasic(t *testing.T) {
resourceName := "aws_cloudfront_origin_access_identity.origin_access_identity"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontOriginAccessIdentityDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudFrontOriginAccessIdentityConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudWatchMetricAlarm_importBasic(t *testing.T) {
resourceName := "aws_cloudwatch_metric_alarm.foobar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchMetricAlarmDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchMetricAlarmConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,33 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDBInstance_importBasic(t *testing.T) {
resourceName := "aws_db_instance.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBInstanceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBInstanceConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"password",
"skip_final_snapshot",
"final_snapshot_identifier",
},
},
},
})
}

View File

@ -0,0 +1,31 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDBOptionGroup_importBasic(t *testing.T) {
resourceName := "aws_db_option_group.bar"
rName := fmt.Sprintf("option-group-test-terraform-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBOptionGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBOptionGroupBasicConfig(rName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,31 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDBParameterGroup_importBasic(t *testing.T) {
resourceName := "aws_db_parameter_group.bar"
groupName := fmt.Sprintf("parameter-group-test-terraform-%d", acctest.RandInt())
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBParameterGroupConfig(groupName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,33 @@
package aws
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDBSecurityGroup_importBasic(t *testing.T) {
oldvar := os.Getenv("AWS_DEFAULT_REGION")
os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
defer os.Setenv("AWS_DEFAULT_REGION", oldvar)
resourceName := "aws_db_security_group.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBSecurityGroupConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDynamoDbTable_importBasic(t *testing.T) {
resourceName := "aws_dynamodb_table.basic-dynamodb-table"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDynamoDbConfigInitialState(),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,29 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSEFSFileSystem_importBasic(t *testing.T) {
resourceName := "aws_efs_file_system.foo-with-tags"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckEfsFileSystemDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSEFSFileSystemConfigWithTags,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"reference_name", "creation_token"},
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSEFSMountTarget_importBasic(t *testing.T) {
resourceName := "aws_efs_mount_target.alpha"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckEfsMountTargetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSEFSMountTargetConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAWSElasticBeanstalkApplication_importBasic(t *testing.T) {
resourceName := "aws_elastic_beanstalk_application.tftest"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckBeanstalkAppDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccBeanstalkAppConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAWSElasticBeanstalkEnvironment_importBasic(t *testing.T) {
resourceName := "aws_elastic_beanstalk_application.tftest"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckBeanstalkAppDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccBeanstalkEnvConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSElasticacheParameterGroup_importBasic(t *testing.T) {
resourceName := "aws_elasticache_parameter_group.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSElasticacheParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSElasticacheParameterGroupConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSGlacierVault_importBasic(t *testing.T) {
resourceName := "aws_glacier_vault.full"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGlacierVaultDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccGlacierVault_full,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSIAMSamlProvider_importBasic(t *testing.T) {
resourceName := "aws_iam_saml_provider.salesforce"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMSamlProviderDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMSamlProviderConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -22,6 +22,8 @@ func TestAccAWSUser_importBasic(t *testing.T) {
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"force_destroy"},
},
},
})

View File

@ -0,0 +1,29 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSKMSKey_importBasic(t *testing.T) {
resourceName := "aws_kms_key.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSKmsKeyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSKmsKey,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_window_in_days"},
},
},
})
}

View File

@ -0,0 +1,81 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSLambdaFunction_importLocalFile(t *testing.T) {
resourceName := "aws_lambda_function.lambda_function_test"
rName := fmt.Sprintf("tf_test_%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSLambdaConfigBasic(rName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"filename", "publish"},
},
},
})
}
func TestAccAWSLambdaFunction_importLocalFile_VPC(t *testing.T) {
resourceName := "aws_lambda_function.lambda_function_test"
rName := fmt.Sprintf("tf_test_%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSLambdaConfigWithVPC(rName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"filename", "publish"},
},
},
})
}
func TestAccAWSLambdaFunction_importS3(t *testing.T) {
resourceName := "aws_lambda_function.lambda_function_s3test"
rName := fmt.Sprintf("tf_test_%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLambdaFunctionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSLambdaConfigS3(rName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"s3_bucket", "s3_key", "publish"},
},
},
})
}

View File

@ -0,0 +1,29 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRDSClusterInstance_importBasic(t *testing.T) {
resourceName := "aws_rds_cluster_instance.cluster_instances"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSClusterInstanceConfig(acctest.RandInt()),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSDBClusterParameterGroup_importBasic(t *testing.T) {
resourceName := "aws_rds_cluster_parameter_group.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBClusterParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBClusterParameterGroupConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,32 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRDSCluster_importBasic(t *testing.T) {
resourceName := "aws_rds_cluster.default"
ri := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSClusterConfig(ri),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"master_password", "skip_final_snapshot"},
},
},
})
}

View File

@ -0,0 +1,32 @@
package aws
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRedshiftCluster_importBasic(t *testing.T) {
resourceName := "aws_redshift_cluster.default"
config := fmt.Sprintf(testAccAWSRedshiftClusterConfig_basic, acctest.RandInt())
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftClusterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: config,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"master_password", "skip_final_snapshot"},
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRedshiftParameterGroup_importBasic(t *testing.T) {
resourceName := "aws_redshift_parameter_group.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftParameterGroupConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,33 @@
package aws
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSRedshiftSecurityGroup_importBasic(t *testing.T) {
oldvar := os.Getenv("AWS_DEFAULT_REGION")
os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
defer os.Setenv("AWS_DEFAULT_REGION", oldvar)
resourceName := "aws_redshift_security_group.bar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftSecurityGroupConfig_ingressCidr,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -19,9 +19,10 @@ func TestAccAWSRoute53Zone_importBasic(t *testing.T) {
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"force_destroy"},
},
},
})

View File

@ -0,0 +1,66 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSS3BucketNotification_importBasic(t *testing.T) {
resourceName := "aws_s3_bucket_notification.notification"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketNotificationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfigWithTopicNotification(acctest.RandInt()),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"bucket"},
},
},
})
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketNotificationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfigWithQueueNotification(acctest.RandInt()),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"bucket"},
},
},
})
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketNotificationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfigWithLambdaNotification(acctest.RandInt()),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"bucket"},
},
},
})
}

View File

@ -0,0 +1,32 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSS3Bucket_importBasic(t *testing.T) {
resourceName := "aws_s3_bucket.bucket"
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfig(rInt),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"force_destroy", "acl"},
},
},
})
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
@ -49,10 +50,40 @@ func resourceAwsSecurityGroupImportState(
d.SetType("aws_security_group_rule")
d.Set("security_group_id", sgId)
d.Set("type", ruleType)
// 'self' is false by default. Below, we range over the group ids and set true
// if the parent sg id is found
d.Set("self", false)
if len(perm.UserIdGroupPairs) > 0 {
s := perm.UserIdGroupPairs[0]
// Check for Pair that is the same as the Security Group, to denote self.
// Otherwise, mark the group id in source_security_group_id
isVPC := sg.VpcId != nil && *sg.VpcId != ""
if isVPC {
if *s.GroupId == *sg.GroupId {
d.Set("self", true)
// prune the self reference from the UserIdGroupPairs, so we don't
// have duplicate sg ids (both self and in source_security_group_id)
perm.UserIdGroupPairs = append(perm.UserIdGroupPairs[:0], perm.UserIdGroupPairs[0+1:]...)
}
} else {
if *s.GroupName == *sg.GroupName {
d.Set("self", true)
// prune the self reference from the UserIdGroupPairs, so we don't
// have duplicate sg ids (both self and in source_security_group_id)
perm.UserIdGroupPairs = append(perm.UserIdGroupPairs[:0], perm.UserIdGroupPairs[0+1:]...)
}
}
}
// XXX If the rule contained more than one source security group, this
// will choose one of them. We actually need to create one rule for each
// source security group.
setFromIPPerm(d, sg, perm)
if err := setFromIPPerm(d, sg, perm); err != nil {
return nil, errwrap.Wrapf("Error importing AWS Security Group: {{err}}", err)
}
results = append(results, d)
}
}

View File

@ -35,3 +35,22 @@ func TestAccAWSSecurityGroup_importBasic(t *testing.T) {
},
})
}
func TestAccAWSSecurityGroup_importSelf(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSecurityGroupConfig_importSelf,
},
resource.TestStep{
ResourceName: "aws_security_group.allow_all",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSESReceiptFilter_importBasic(t *testing.T) {
resourceName := "aws_ses_receipt_filter.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSESReceiptFilterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSESReceiptFilterConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSESReceiptRuleSet_importBasic(t *testing.T) {
resourceName := "aws_ses_receipt_rule_set.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSESReceiptRuleSetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSESReceiptRuleSetConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSimpleDBDomain_importBasic(t *testing.T) {
resourceName := "aws_simpledb_domain.test_domain"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSimpleDBDomainDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSimpleDBDomainConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,30 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSNSTopicSubscription_importBasic(t *testing.T) {
resourceName := "aws_sns_topic.test_topic"
ri := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSNSTopicSubscriptionConfig(ri),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,30 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSpotDatafeedSubscription_importBasic(t *testing.T) {
resourceName := "aws_spot_datafeed_subscription.default"
ri := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSpotDatafeedSubscriptionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSpotDatafeedSubscription(ri),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,32 @@
package aws
import (
"testing"
"fmt"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSSQSQueue_importBasic(t *testing.T) {
resourceName := "aws_sqs_queue.queue"
queueName := fmt.Sprintf("sqs-queue-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSQSQueueDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSQSConfigWithDefaults(queueName),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,30 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSVPCPeeringConnection_importBasic(t *testing.T) {
resourceName := "aws_vpc_peering_connection.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSVpcPeeringConnectionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccVpcPeeringConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"auto_accept"},
},
},
})
}

View File

@ -0,0 +1,28 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSVpnConnection_importBasic(t *testing.T) {
resourceName := "aws_vpn_connection.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccAwsVpnConnectionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAwsVpnConnectionConfig,
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -39,8 +39,8 @@ type opsworksLayerType struct {
}
var (
opsworksTrueString = "1"
opsworksFalseString = "0"
opsworksTrueString = "true"
opsworksFalseString = "false"
)
func (lt *opsworksLayerType) SchemaResource() *schema.Resource {

View File

@ -3,6 +3,7 @@ package aws
import (
"bytes"
"fmt"
"log"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/mutexkv"
@ -18,42 +19,44 @@ func Provider() terraform.ResourceProvider {
// The actual provider
return &schema.Provider{
Schema: map[string]*schema.Schema{
"access_key": &schema.Schema{
"access_key": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["access_key"],
},
"secret_key": &schema.Schema{
"secret_key": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["secret_key"],
},
"profile": &schema.Schema{
"profile": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["profile"],
},
"shared_credentials_file": &schema.Schema{
"assume_role": assumeRoleSchema(),
"shared_credentials_file": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["shared_credentials_file"],
},
"token": &schema.Schema{
"token": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["token"],
},
"region": &schema.Schema{
"region": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
@ -64,14 +67,14 @@ func Provider() terraform.ResourceProvider {
InputDefault: "us-east-1",
},
"max_retries": &schema.Schema{
"max_retries": {
Type: schema.TypeInt,
Optional: true,
Default: 11,
Description: descriptions["max_retries"],
},
"allowed_account_ids": &schema.Schema{
"allowed_account_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
@ -79,7 +82,7 @@ func Provider() terraform.ResourceProvider {
Set: schema.HashString,
},
"forbidden_account_ids": &schema.Schema{
"forbidden_account_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
@ -87,44 +90,87 @@ func Provider() terraform.ResourceProvider {
Set: schema.HashString,
},
"dynamodb_endpoint": &schema.Schema{
"dynamodb_endpoint": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["dynamodb_endpoint"],
},
"kinesis_endpoint": &schema.Schema{
"kinesis_endpoint": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["kinesis_endpoint"],
},
"endpoints": endpointsSchema(),
"insecure": &schema.Schema{
"insecure": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["insecure"],
},
"skip_credentials_validation": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["skip_credentials_validation"],
},
"skip_requesting_account_id": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["skip_requesting_account_id"],
},
"skip_metadata_api_check": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["skip_metadata_api_check"],
},
"s3_force_path_style": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: descriptions["s3_force_path_style"],
},
},
DataSourcesMap: map[string]*schema.Resource{
"aws_ami": dataSourceAwsAmi(),
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
"aws_s3_bucket_object": dataSourceAwsS3BucketObject(),
"aws_ami": dataSourceAwsAmi(),
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
"aws_caller_identity": dataSourceAwsCallerIdentity(),
"aws_cloudformation_stack": dataSourceAwsCloudFormationStack(),
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
"aws_elb_service_account": dataSourceAwsElbServiceAccount(),
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
"aws_ip_ranges": dataSourceAwsIPRanges(),
"aws_redshift_service_account": dataSourceAwsRedshiftServiceAccount(),
"aws_s3_bucket_object": dataSourceAwsS3BucketObject(),
},
ResourcesMap: map[string]*schema.Resource{
"aws_alb": resourceAwsAlb(),
"aws_alb_listener": resourceAwsAlbListener(),
"aws_alb_listener_rule": resourceAwsAlbListenerRule(),
"aws_alb_target_group": resourceAwsAlbTargetGroup(),
"aws_alb_target_group_attachment": resourceAwsAlbTargetGroupAttachment(),
"aws_ami": resourceAwsAmi(),
"aws_ami_copy": resourceAwsAmiCopy(),
"aws_ami_from_instance": resourceAwsAmiFromInstance(),
"aws_ami_launch_permission": resourceAwsAmiLaunchPermission(),
"aws_api_gateway_account": resourceAwsApiGatewayAccount(),
"aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(),
"aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(),
"aws_api_gateway_base_path_mapping": resourceAwsApiGatewayBasePathMapping(),
"aws_api_gateway_deployment": resourceAwsApiGatewayDeployment(),
"aws_api_gateway_domain_name": resourceAwsApiGatewayDomainName(),
"aws_api_gateway_integration": resourceAwsApiGatewayIntegration(),
"aws_api_gateway_integration_response": resourceAwsApiGatewayIntegrationResponse(),
"aws_api_gateway_method": resourceAwsApiGatewayMethod(),
@ -133,6 +179,8 @@ func Provider() terraform.ResourceProvider {
"aws_api_gateway_resource": resourceAwsApiGatewayResource(),
"aws_api_gateway_rest_api": resourceAwsApiGatewayRestApi(),
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
"aws_appautoscaling_target": resourceAwsAppautoscalingTarget(),
"aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(),
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_autoscaling_notification": resourceAwsAutoscalingNotification(),
"aws_autoscaling_policy": resourceAwsAutoscalingPolicy(),
@ -145,6 +193,7 @@ func Provider() terraform.ResourceProvider {
"aws_cloudwatch_event_target": resourceAwsCloudWatchEventTarget(),
"aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(),
"aws_cloudwatch_log_metric_filter": resourceAwsCloudWatchLogMetricFilter(),
"aws_cloudwatch_log_stream": resourceAwsCloudWatchLogStream(),
"aws_cloudwatch_log_subscription_filter": resourceAwsCloudwatchLogSubscriptionFilter(),
"aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(),
"aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(),
@ -172,6 +221,7 @@ func Provider() terraform.ResourceProvider {
"aws_eip_association": resourceAwsEipAssociation(),
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
"aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(),
"aws_elasticache_replication_group": resourceAwsElasticacheReplicationGroup(),
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
"aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(),
"aws_elastic_beanstalk_application": resourceAwsElasticBeanstalkApplication(),
@ -215,10 +265,15 @@ func Provider() terraform.ResourceProvider {
"aws_lambda_permission": resourceAwsLambdaPermission(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_lb_cookie_stickiness_policy": resourceAwsLBCookieStickinessPolicy(),
"aws_load_balancer_policy": resourceAwsLoadBalancerPolicy(),
"aws_load_balancer_backend_server_policy": resourceAwsLoadBalancerBackendServerPolicies(),
"aws_load_balancer_listener_policy": resourceAwsLoadBalancerListenerPolicies(),
"aws_lb_ssl_negotiation_policy": resourceAwsLBSSLNegotiationPolicy(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
"aws_nat_gateway": resourceAwsNatGateway(),
"aws_network_acl": resourceAwsNetworkAcl(),
"aws_default_network_acl": resourceAwsDefaultNetworkAcl(),
"aws_default_route_table": resourceAwsDefaultRouteTable(),
"aws_network_acl_rule": resourceAwsNetworkAclRule(),
"aws_network_interface": resourceAwsNetworkInterface(),
"aws_opsworks_application": resourceAwsOpsworksApplication(),
@ -234,6 +289,8 @@ func Provider() terraform.ResourceProvider {
"aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(),
"aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(),
"aws_opsworks_instance": resourceAwsOpsworksInstance(),
"aws_opsworks_user_profile": resourceAwsOpsworksUserProfile(),
"aws_opsworks_permission": resourceAwsOpsworksPermission(),
"aws_placement_group": resourceAwsPlacementGroup(),
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_rds_cluster": resourceAwsRDSCluster(),
@ -251,15 +308,26 @@ func Provider() terraform.ResourceProvider {
"aws_route": resourceAwsRoute(),
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_ses_active_receipt_rule_set": resourceAwsSesActiveReceiptRuleSet(),
"aws_ses_receipt_filter": resourceAwsSesReceiptFilter(),
"aws_ses_receipt_rule": resourceAwsSesReceiptRule(),
"aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_simpledb_domain": resourceAwsSimpleDBDomain(),
"aws_ssm_association": resourceAwsSsmAssociation(),
"aws_ssm_document": resourceAwsSsmDocument(),
"aws_spot_datafeed_subscription": resourceAwsSpotDataFeedSubscription(),
"aws_spot_instance_request": resourceAwsSpotInstanceRequest(),
"aws_spot_fleet_request": resourceAwsSpotFleetRequest(),
"aws_sqs_queue": resourceAwsSqsQueue(),
"aws_sqs_queue_policy": resourceAwsSqsQueuePolicy(),
"aws_sns_topic": resourceAwsSnsTopic(),
"aws_sns_topic_policy": resourceAwsSnsTopicPolicy(),
"aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(),
"aws_subnet": resourceAwsSubnet(),
"aws_volume_attachment": resourceAwsVolumeAttachment(),
@ -271,6 +339,7 @@ func Provider() terraform.ResourceProvider {
"aws_vpn_connection": resourceAwsVpnConnection(),
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
"aws_vpn_gateway": resourceAwsVpnGateway(),
"aws_vpn_gateway_attachment": resourceAwsVpnGatewayAttachment(),
},
ConfigureFunc: providerConfigure,
}
@ -314,23 +383,63 @@ func init() {
"elb_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",
"s3_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",
"insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted," +
"default value is `false`",
"skip_credentials_validation": "Skip the credentials validation via STS API. " +
"Used for AWS API implementations that do not have STS available/implemented.",
"skip_requesting_account_id": "Skip requesting the account ID. " +
"Used for AWS API implementations that do not have IAM/STS API and/or metadata API.",
"skip_medatadata_api_check": "Skip the AWS Metadata API check. " +
"Used for AWS API implementations that do not have a metadata api endpoint.",
"s3_force_path_style": "Set this to true to force the request to use path-style addressing,\n" +
"i.e., http://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\n" +
"use virtual hosted bucket addressing when possible\n" +
"(http://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.",
"assume_role_role_arn": "The ARN of an IAM role to assume prior to making API calls.",
"assume_role_session_name": "The session name to use when assuming the role. If ommitted," +
" no session name is passed to the AssumeRole call.",
"assume_role_external_id": "The external ID to use when assuming the role. If ommitted," +
" no external ID is passed to the AssumeRole call.",
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
AccessKey: d.Get("access_key").(string),
SecretKey: d.Get("secret_key").(string),
Profile: d.Get("profile").(string),
CredsFilename: d.Get("shared_credentials_file").(string),
Token: d.Get("token").(string),
Region: d.Get("region").(string),
MaxRetries: d.Get("max_retries").(int),
DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string),
KinesisEndpoint: d.Get("kinesis_endpoint").(string),
Insecure: d.Get("insecure").(bool),
AccessKey: d.Get("access_key").(string),
SecretKey: d.Get("secret_key").(string),
Profile: d.Get("profile").(string),
CredsFilename: d.Get("shared_credentials_file").(string),
Token: d.Get("token").(string),
Region: d.Get("region").(string),
MaxRetries: d.Get("max_retries").(int),
DynamoDBEndpoint: d.Get("dynamodb_endpoint").(string),
KinesisEndpoint: d.Get("kinesis_endpoint").(string),
Insecure: d.Get("insecure").(bool),
SkipCredsValidation: d.Get("skip_credentials_validation").(bool),
SkipRequestingAccountId: d.Get("skip_requesting_account_id").(bool),
SkipMetadataApiCheck: d.Get("skip_metadata_api_check").(bool),
S3ForcePathStyle: d.Get("s3_force_path_style").(bool),
}
assumeRoleList := d.Get("assume_role").(*schema.Set).List()
if len(assumeRoleList) == 1 {
assumeRole := assumeRoleList[0].(map[string]interface{})
config.AssumeRoleARN = assumeRole["role_arn"].(string)
config.AssumeRoleSessionName = assumeRole["session_name"].(string)
config.AssumeRoleExternalID = assumeRole["external_id"].(string)
log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)",
config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID)
} else {
log.Printf("[INFO] No assume_role block read from configuration")
}
endpointsSet := d.Get("endpoints").(*schema.Set)
@ -340,6 +449,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config.IamEndpoint = endpoints["iam"].(string)
config.Ec2Endpoint = endpoints["ec2"].(string)
config.ElbEndpoint = endpoints["elb"].(string)
config.S3Endpoint = endpoints["s3"].(string)
}
if v, ok := d.GetOk("allowed_account_ids"); ok {
@ -356,32 +466,77 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
// This is a global MutexKV for use within this plugin.
var awsMutexKV = mutexkv.NewMutexKV()
func assumeRoleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"role_arn": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_role_arn"],
},
"session_name": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_session_name"],
},
"external_id": {
Type: schema.TypeString,
Optional: true,
Description: descriptions["assume_role_external_id"],
},
},
},
Set: assumeRoleToHash,
}
}
func assumeRoleToHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["role_arn"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["session_name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["external_id"].(string)))
return hashcode.String(buf.String())
}
func endpointsSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"iam": &schema.Schema{
"iam": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["iam_endpoint"],
},
"ec2": &schema.Schema{
"ec2": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["ec2_endpoint"],
},
"elb": &schema.Schema{
"elb": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["elb_endpoint"],
},
"s3": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["s3_endpoint"],
},
},
},
Set: endpointsToHash,
@ -394,6 +549,7 @@ func endpointsToHash(v interface{}) int {
buf.WriteString(fmt.Sprintf("%s-", m["iam"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["ec2"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["elb"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["s3"].(string)))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,327 @@
package aws
import (
"fmt"
"log"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsAlb() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAlbCreate,
Read: resourceAwsAlbRead,
Update: resourceAwsAlbUpdate,
Delete: resourceAwsAlbDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateElbName,
},
"internal": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"security_groups": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
ForceNew: true,
Optional: true,
Set: schema.HashString,
},
"subnets": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
ForceNew: true,
Required: true,
Set: schema.HashString,
},
"access_logs": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"bucket": {
Type: schema.TypeString,
Required: true,
},
"prefix": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"enable_deletion_protection": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"idle_timeout": {
Type: schema.TypeInt,
Optional: true,
Default: 60,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
"zone_id": {
Type: schema.TypeString,
Computed: true,
},
"dns_name": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(),
},
}
}
func resourceAwsAlbCreate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
elbOpts := &elbv2.CreateLoadBalancerInput{
Name: aws.String(d.Get("name").(string)),
Tags: tagsFromMapELBv2(d.Get("tags").(map[string]interface{})),
}
if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) {
elbOpts.Scheme = aws.String("internal")
}
if v, ok := d.GetOk("security_groups"); ok {
elbOpts.SecurityGroups = expandStringList(v.(*schema.Set).List())
}
if v, ok := d.GetOk("subnets"); ok {
elbOpts.Subnets = expandStringList(v.(*schema.Set).List())
}
log.Printf("[DEBUG] ALB create configuration: %#v", elbOpts)
resp, err := elbconn.CreateLoadBalancer(elbOpts)
if err != nil {
return errwrap.Wrapf("Error creating Application Load Balancer: {{err}}", err)
}
if len(resp.LoadBalancers) != 1 {
return fmt.Errorf("No load balancers returned following creation of %s", d.Get("name").(string))
}
d.SetId(*resp.LoadBalancers[0].LoadBalancerArn)
log.Printf("[INFO] ALB ID: %s", d.Id())
return resourceAwsAlbUpdate(d, meta)
}
func resourceAwsAlbRead(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
albArn := d.Id()
describeAlbOpts := &elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{aws.String(albArn)},
}
describeResp, err := elbconn.DescribeLoadBalancers(describeAlbOpts)
if err != nil {
if isLoadBalancerNotFound(err) {
// The ALB is gone now, so just remove it from the state
log.Printf("[WARN] ALB %s not found in AWS, removing from state", d.Id())
d.SetId("")
return nil
}
return errwrap.Wrapf("Error retrieving ALB: {{err}}", err)
}
if len(describeResp.LoadBalancers) != 1 {
return fmt.Errorf("Unable to find ALB: %#v", describeResp.LoadBalancers)
}
alb := describeResp.LoadBalancers[0]
d.Set("arn", alb.LoadBalancerArn)
d.Set("name", alb.LoadBalancerName)
d.Set("internal", (alb.Scheme != nil && *alb.Scheme == "internal"))
d.Set("security_groups", flattenStringList(alb.SecurityGroups))
d.Set("subnets", flattenSubnetsFromAvailabilityZones(alb.AvailabilityZones))
d.Set("vpc_id", alb.VpcId)
d.Set("zone_id", alb.CanonicalHostedZoneId)
d.Set("dns_name", alb.DNSName)
respTags, err := elbconn.DescribeTags(&elbv2.DescribeTagsInput{
ResourceArns: []*string{alb.LoadBalancerArn},
})
if err != nil {
return errwrap.Wrapf("Error retrieving ALB Tags: {{err}}", err)
}
var et []*elbv2.Tag
if len(respTags.TagDescriptions) > 0 {
et = respTags.TagDescriptions[0].Tags
}
d.Set("tags", tagsToMapELBv2(et))
attributesResp, err := elbconn.DescribeLoadBalancerAttributes(&elbv2.DescribeLoadBalancerAttributesInput{
LoadBalancerArn: aws.String(d.Id()),
})
if err != nil {
return errwrap.Wrapf("Error retrieving ALB Attributes: {{err}}", err)
}
accessLogMap := map[string]interface{}{}
for _, attr := range attributesResp.Attributes {
switch *attr.Key {
case "access_logs.s3.bucket":
accessLogMap["bucket"] = *attr.Value
case "access_logs.s3.prefix":
accessLogMap["prefix"] = *attr.Value
case "idle_timeout.timeout_seconds":
timeout, err := strconv.Atoi(*attr.Value)
if err != nil {
return errwrap.Wrapf("Error parsing ALB timeout: {{err}}", err)
}
log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout)
d.Set("idle_timeout", timeout)
case "deletion_protection.enabled":
protectionEnabled := (*attr.Value) == "true"
log.Printf("[DEBUG] Setting ALB Deletion Protection Enabled: %t", protectionEnabled)
d.Set("enable_deletion_protection", protectionEnabled)
}
}
log.Printf("[DEBUG] Setting ALB Access Logs: %#v", accessLogMap)
if accessLogMap["bucket"] != "" || accessLogMap["prefix"] != "" {
d.Set("access_logs", []interface{}{accessLogMap})
} else {
d.Set("access_logs", []interface{}{})
}
return nil
}
func resourceAwsAlbUpdate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
if !d.IsNewResource() {
if err := setElbV2Tags(elbconn, d); err != nil {
return errwrap.Wrapf("Error Modifying Tags on ALB: {{err}}", err)
}
}
attributes := make([]*elbv2.LoadBalancerAttribute, 0)
if d.HasChange("access_logs") {
logs := d.Get("access_logs").([]interface{})
if len(logs) == 1 {
log := logs[0].(map[string]interface{})
attributes = append(attributes,
&elbv2.LoadBalancerAttribute{
Key: aws.String("access_logs.s3.enabled"),
Value: aws.String("true"),
},
&elbv2.LoadBalancerAttribute{
Key: aws.String("access_logs.s3.bucket"),
Value: aws.String(log["bucket"].(string)),
})
if prefix, ok := log["prefix"]; ok {
attributes = append(attributes, &elbv2.LoadBalancerAttribute{
Key: aws.String("access_logs.s3.prefix"),
Value: aws.String(prefix.(string)),
})
}
} else if len(logs) == 0 {
attributes = append(attributes, &elbv2.LoadBalancerAttribute{
Key: aws.String("access_logs.s3.enabled"),
Value: aws.String("false"),
})
}
}
if d.HasChange("enable_deletion_protection") {
attributes = append(attributes, &elbv2.LoadBalancerAttribute{
Key: aws.String("deletion_protection.enabled"),
Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))),
})
}
if d.HasChange("idle_timeout") {
attributes = append(attributes, &elbv2.LoadBalancerAttribute{
Key: aws.String("idle_timeout.timeout_seconds"),
Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))),
})
}
if len(attributes) != 0 {
input := &elbv2.ModifyLoadBalancerAttributesInput{
LoadBalancerArn: aws.String(d.Id()),
Attributes: attributes,
}
log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input)
_, err := elbconn.ModifyLoadBalancerAttributes(input)
if err != nil {
return fmt.Errorf("Failure configuring ALB attributes: %s", err)
}
}
return resourceAwsAlbRead(d, meta)
}
func resourceAwsAlbDelete(d *schema.ResourceData, meta interface{}) error {
albconn := meta.(*AWSClient).elbv2conn
log.Printf("[INFO] Deleting ALB: %s", d.Id())
// Destroy the load balancer
deleteElbOpts := elbv2.DeleteLoadBalancerInput{
LoadBalancerArn: aws.String(d.Id()),
}
if _, err := albconn.DeleteLoadBalancer(&deleteElbOpts); err != nil {
return fmt.Errorf("Error deleting ALB: %s", err)
}
return nil
}
// flattenSubnetsFromAvailabilityZones creates a slice of strings containing the subnet IDs
// for the ALB based on the AvailabilityZones structure returned by the API.
func flattenSubnetsFromAvailabilityZones(availabilityZones []*elbv2.AvailabilityZone) []string {
var result []string
for _, az := range availabilityZones {
result = append(result, *az.SubnetId)
}
return result
}

View File

@ -0,0 +1,262 @@
package aws
import (
"errors"
"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/elbv2"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsAlbListener() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAlbListenerCreate,
Read: resourceAwsAlbListenerRead,
Update: resourceAwsAlbListenerUpdate,
Delete: resourceAwsAlbListenerDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"load_balancer_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateAwsAlbListenerPort,
},
"protocol": {
Type: schema.TypeString,
Optional: true,
Default: "HTTP",
StateFunc: func(v interface{}) string {
return strings.ToUpper(v.(string))
},
ValidateFunc: validateAwsAlbListenerProtocol,
},
"ssl_policy": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"certificate_arn": {
Type: schema.TypeString,
Optional: true,
},
"default_action": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"target_group_arn": {
Type: schema.TypeString,
Required: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateAwsAlbListenerActionType,
},
},
},
},
},
}
}
func resourceAwsAlbListenerCreate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
params := &elbv2.CreateListenerInput{
LoadBalancerArn: aws.String(d.Get("load_balancer_arn").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
Protocol: aws.String(d.Get("protocol").(string)),
}
if sslPolicy, ok := d.GetOk("ssl_policy"); ok {
params.SslPolicy = aws.String(sslPolicy.(string))
}
if certificateArn, ok := d.GetOk("certificate_arn"); ok {
params.Certificates = make([]*elbv2.Certificate, 1)
params.Certificates[0] = &elbv2.Certificate{
CertificateArn: aws.String(certificateArn.(string)),
}
}
if defaultActions := d.Get("default_action").([]interface{}); len(defaultActions) == 1 {
params.DefaultActions = make([]*elbv2.Action, len(defaultActions))
for i, defaultAction := range defaultActions {
defaultActionMap := defaultAction.(map[string]interface{})
params.DefaultActions[i] = &elbv2.Action{
TargetGroupArn: aws.String(defaultActionMap["target_group_arn"].(string)),
Type: aws.String(defaultActionMap["type"].(string)),
}
}
}
resp, err := elbconn.CreateListener(params)
if err != nil {
return errwrap.Wrapf("Error creating ALB Listener: {{err}}", err)
}
if len(resp.Listeners) == 0 {
return errors.New("Error creating ALB Listener: no listeners returned in response")
}
d.SetId(*resp.Listeners[0].ListenerArn)
return resourceAwsAlbListenerRead(d, meta)
}
func resourceAwsAlbListenerRead(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
resp, err := elbconn.DescribeListeners(&elbv2.DescribeListenersInput{
ListenerArns: []*string{aws.String(d.Id())},
})
if err != nil {
if isListenerNotFound(err) {
log.Printf("[WARN] DescribeListeners - removing %s from state", d.Id())
d.SetId("")
return nil
}
return errwrap.Wrapf("Error retrieving Listener: {{err}}", err)
}
if len(resp.Listeners) != 1 {
return fmt.Errorf("Error retrieving Listener %q", d.Id())
}
listener := resp.Listeners[0]
d.Set("arn", listener.ListenerArn)
d.Set("load_balancer_arn", listener.LoadBalancerArn)
d.Set("port", listener.Port)
d.Set("protocol", listener.Protocol)
d.Set("ssl_policy", listener.SslPolicy)
if listener.Certificates != nil && len(listener.Certificates) == 1 {
d.Set("certificate_arn", listener.Certificates[0].CertificateArn)
}
defaultActions := make([]map[string]interface{}, 0)
if listener.DefaultActions != nil && len(listener.DefaultActions) > 0 {
for _, defaultAction := range listener.DefaultActions {
action := map[string]interface{}{
"target_group_arn": *defaultAction.TargetGroupArn,
"type": *defaultAction.Type,
}
defaultActions = append(defaultActions, action)
}
}
d.Set("default_action", defaultActions)
return nil
}
func resourceAwsAlbListenerUpdate(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
params := &elbv2.ModifyListenerInput{
ListenerArn: aws.String(d.Id()),
Port: aws.Int64(int64(d.Get("port").(int))),
Protocol: aws.String(d.Get("protocol").(string)),
}
if sslPolicy, ok := d.GetOk("ssl_policy"); ok {
params.SslPolicy = aws.String(sslPolicy.(string))
}
if certificateArn, ok := d.GetOk("certificate_arn"); ok {
params.Certificates = make([]*elbv2.Certificate, 1)
params.Certificates[0] = &elbv2.Certificate{
CertificateArn: aws.String(certificateArn.(string)),
}
}
if defaultActions := d.Get("default_action").([]interface{}); len(defaultActions) == 1 {
params.DefaultActions = make([]*elbv2.Action, len(defaultActions))
for i, defaultAction := range defaultActions {
defaultActionMap := defaultAction.(map[string]interface{})
params.DefaultActions[i] = &elbv2.Action{
TargetGroupArn: aws.String(defaultActionMap["target_group_arn"].(string)),
Type: aws.String(defaultActionMap["type"].(string)),
}
}
}
_, err := elbconn.ModifyListener(params)
if err != nil {
return errwrap.Wrapf("Error modifying ALB Listener: {{err}}", err)
}
return resourceAwsAlbListenerRead(d, meta)
}
func resourceAwsAlbListenerDelete(d *schema.ResourceData, meta interface{}) error {
elbconn := meta.(*AWSClient).elbv2conn
_, err := elbconn.DeleteListener(&elbv2.DeleteListenerInput{
ListenerArn: aws.String(d.Id()),
})
if err != nil {
return errwrap.Wrapf("Error deleting Listener: {{err}}", err)
}
return nil
}
func validateAwsAlbListenerPort(v interface{}, k string) (ws []string, errors []error) {
port := v.(int)
if port < 1 || port > 65536 {
errors = append(errors, fmt.Errorf("%q must be a valid port number (1-65536)", k))
}
return
}
func validateAwsAlbListenerProtocol(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
if value == "http" || value == "https" {
return
}
errors = append(errors, fmt.Errorf("%q must be either %q or %q", k, "HTTP", "HTTPS"))
return
}
func validateAwsAlbListenerActionType(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
if value != "forward" {
errors = append(errors, fmt.Errorf("%q must have the value %q", k, "forward"))
}
return
}
func isListenerNotFound(err error) bool {
elberr, ok := err.(awserr.Error)
return ok && elberr.Code() == "ListenerNotFound"
}

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