Merge branch 'master' into cloud_router
This commit is contained in:
commit
8e69d75936
|
@ -2,7 +2,7 @@ dist: trusty
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
- 1.8.x
|
||||
|
||||
# add TF_CONSUL_TEST=1 to run consul tests
|
||||
# they were causing timouts in travis
|
||||
|
@ -25,7 +25,7 @@ install:
|
|||
- bash scripts/gogetcookie.sh
|
||||
- go get github.com/kardianos/govendor
|
||||
script:
|
||||
- make vet vendor-status test
|
||||
- make vendor-status test vet
|
||||
- GOOS=windows go build
|
||||
branches:
|
||||
only:
|
||||
|
@ -39,4 +39,4 @@ notifications:
|
|||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
- go: tip
|
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -3,16 +3,57 @@
|
|||
BACKWARDS INCOMPATIBILITIES / NOTES:
|
||||
|
||||
* provider/aws: Users of aws_cloudfront_distributions with custom_origins have been broken due to changes in the AWS API requiring `OriginReadTimeout` being set for updates. This has been fixed and will show as a change in terraform plan / apply. [GH-13367]
|
||||
* provider/aws: Users of China and Gov clouds, cannot use the new tagging of volumes created as part of aws_instances [GH-14055]
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **New Provider:** `gitlab` [GH-13898]
|
||||
* **New Provider:** `gitlab` [GH-13898]
|
||||
* **New Resource:** `aws_emr_security_configuration` [GH-14080]
|
||||
* **New Resource:** `azurerm_sql_elasticpool` [GH-14099]
|
||||
* **New Resource:** `google_compute_backend_bucket` [GH-14015]
|
||||
* **New Resource:** `google_compute_snapshot` [GH-12482]
|
||||
* **New Resource:** `heroku_app_feature` [GH-14035]
|
||||
* **New Resource:** `heroku_pipeline` [GH-14078]
|
||||
* **New Resource:** `heroku_pipeline_coupling` [GH-14078]
|
||||
* **New Resource:** `vault_auth_backend` [GH-10988]
|
||||
* **New Data Source:** `aws_efs_file_system` [GH-14041]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: `sha512` and `base64sha512` interpolation functions, similar to their `sha256` equivalents. [GH-14100]
|
||||
* provider/aws: Add support for CustomOrigin timeouts to aws_cloudfront_distribution [GH-13367]
|
||||
* provider/aws: Add support for IAMDatabaseAuthenticationEnabled [GH-14092]
|
||||
* provider/aws: aws_dynamodb_table Add support for TimeToLive [GH-14104]
|
||||
* provider/aws: Add `security_configuration` support to `aws_emr_cluster` [GH-14133]
|
||||
* provider/aws: Add support for the tenancy placement option in `aws_spot_fleet_request` [GH-14163]
|
||||
* provider/azurerm: `azurerm_template_deployment` now supports String/Int/Boolean outputs [GH-13670]
|
||||
* provider/azurerm: Expose the Private IP Address for a Load Balancer, if available [GH-13965]
|
||||
* provider/dnsimple: Add support for import for dnsimple_records [GH-9130]
|
||||
* provider/google: Add support for networkIP in compute instance templates [GH-13515]
|
||||
* provider/google: google_dns_managed_zone is now importable [GH-13824]
|
||||
* provider/nomad: Add TLS options [GH-13956]
|
||||
* provider/triton: Add support for reading provider configuration from `TRITON_*` environment variables in addition to `SDC_*`[GH-14000]
|
||||
* provider/triton: Add `cloud_config` argument to `triton_machine` resources for Linux containers [GH-12840]
|
||||
* provider/triton: Add `insecure_skip_tls_verify` [GH-14077]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: `module` blocks without names are now caught in validation, along with various other block types [GH-14162]
|
||||
* provider/aws: Update aws_ebs_volume when attached [GH-14005]
|
||||
* provider/aws: Set aws_instance volume_tags to be Computed [GH-14007]
|
||||
* provider/aws: Fix issue getting partition for federated users [GH-13992]
|
||||
* provider/aws: aws_spot_instance_request not forcenew on volume_tags [GH-14046]
|
||||
* provider/aws: Exclude aws_instance volume tagging for China and Gov Clouds [GH-14055]
|
||||
* provider/aws: Fix source_dest_check with network_interface [GH-14079]
|
||||
* provider/aws: Fixes the bug where SNS delivery policy get always recreated [GH-14064]
|
||||
* provider/digitalocean: Prevent diffs when using IDs of images instead of slugs [GH-13879]
|
||||
* provider/fastly: Changes setting conditionals to optional [GH-14103]
|
||||
* provider/google: Ignore certain project services that can't be enabled directly via the api [GH-13730]
|
||||
* provider/google: Ability to add more than 25 project services [GH-13758]
|
||||
* provider/google: Fix compute instance panic with bad disk config [GH-14169]
|
||||
* providers/heroku: Configure buildpacks correctly for both Org Apps and non-org Apps [GH-13990]
|
||||
* provider/postgres grant role when creating database [GH-11452]
|
||||
* provisioner/remote-exec: Fix panic from remote_exec provisioner [GH-14134]
|
||||
|
||||
## 0.9.4 (26th April 2017)
|
||||
|
||||
|
|
4
Makefile
4
Makefile
|
@ -75,8 +75,8 @@ cover:
|
|||
# vet runs the Go source code static analysis tool `vet` to find
|
||||
# any common errors.
|
||||
vet:
|
||||
@echo "go vet ."
|
||||
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
@echo 'go vet $$(go list ./... | grep -v /terraform/vendor/)'
|
||||
@go vet $$(go list ./... | grep -v /terraform/vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
# Software version variables
|
||||
GOVERSION = "1.8"
|
||||
GOVERSION = "1.8.1"
|
||||
UBUNTUVERSION = "16.04"
|
||||
|
||||
# CPU and RAM can be adjusted depending on your system
|
||||
|
|
|
@ -54,7 +54,7 @@ func GetAccountInfo(iamconn *iam.IAM, stsconn *sts.STS, authProviderName string)
|
|||
awsErr, ok := err.(awserr.Error)
|
||||
// AccessDenied and ValidationError can be raised
|
||||
// if credentials belong to federated profile, so we ignore these
|
||||
if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError") {
|
||||
if !ok || (awsErr.Code() != "AccessDenied" && awsErr.Code() != "ValidationError" && awsErr.Code() != "InvalidClientTokenId") {
|
||||
return "", "", fmt.Errorf("Failed getting account ID via 'iam:GetUser': %s", err)
|
||||
}
|
||||
log.Printf("[DEBUG] Getting account ID via iam:GetUser failed: %s", err)
|
||||
|
|
|
@ -171,6 +171,20 @@ func (c *AWSClient) DynamoDB() *dynamodb.DynamoDB {
|
|||
return c.dynamodbconn
|
||||
}
|
||||
|
||||
func (c *AWSClient) IsGovCloud() bool {
|
||||
if c.region == "us-gov-west-1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *AWSClient) IsChinaCloud() bool {
|
||||
if c.region == "cn-north-1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/efs"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func dataSourceAwsEfsFileSystem() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Read: dataSourceAwsEfsFileSystemRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"creation_token": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateMaxLength(64),
|
||||
},
|
||||
"file_system_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"performance_mode": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"tags": tagsSchemaComputed(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceAwsEfsFileSystemRead(d *schema.ResourceData, meta interface{}) error {
|
||||
efsconn := meta.(*AWSClient).efsconn
|
||||
|
||||
describeEfsOpts := &efs.DescribeFileSystemsInput{}
|
||||
|
||||
if v, ok := d.GetOk("creation_token"); ok {
|
||||
describeEfsOpts.CreationToken = aws.String(v.(string))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("file_system_id"); ok {
|
||||
describeEfsOpts.FileSystemId = aws.String(v.(string))
|
||||
}
|
||||
|
||||
describeResp, err := efsconn.DescribeFileSystems(describeEfsOpts)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error retrieving EFS: {{err}}", err)
|
||||
}
|
||||
if len(describeResp.FileSystems) != 1 {
|
||||
return fmt.Errorf("Search returned %d results, please revise so only one is returned", len(describeResp.FileSystems))
|
||||
}
|
||||
|
||||
d.SetId(*describeResp.FileSystems[0].FileSystemId)
|
||||
|
||||
tags := make([]*efs.Tag, 0)
|
||||
var marker string
|
||||
for {
|
||||
params := &efs.DescribeTagsInput{
|
||||
FileSystemId: aws.String(d.Id()),
|
||||
}
|
||||
if marker != "" {
|
||||
params.Marker = aws.String(marker)
|
||||
}
|
||||
|
||||
tagsResp, err := efsconn.DescribeTags(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving EC2 tags for EFS file system (%q): %s",
|
||||
d.Id(), err.Error())
|
||||
}
|
||||
|
||||
for _, tag := range tagsResp.Tags {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
if tagsResp.NextMarker != nil {
|
||||
marker = *tagsResp.NextMarker
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = d.Set("tags", tagsToMapEFS(tags))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fs *efs.FileSystemDescription
|
||||
for _, f := range describeResp.FileSystems {
|
||||
if d.Id() == *f.FileSystemId {
|
||||
fs = f
|
||||
break
|
||||
}
|
||||
}
|
||||
if fs == nil {
|
||||
log.Printf("[WARN] EFS (%s) not found, removing from state", d.Id())
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
d.Set("creation_token", fs.CreationToken)
|
||||
d.Set("performance_mode", fs.PerformanceMode)
|
||||
d.Set("file_system_id", fs.FileSystemId)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccDataSourceAwsEfsFileSystem(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccDataSourceAwsEfsFileSystemConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccDataSourceAwsEfsFileSystemCheck("data.aws_efs_file_system.by_creation_token"),
|
||||
testAccDataSourceAwsEfsFileSystemCheck("data.aws_efs_file_system.by_id"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccDataSourceAwsEfsFileSystemCheck(name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("root module has no resource called %s", name)
|
||||
}
|
||||
|
||||
efsRs, ok := s.RootModule().Resources["aws_efs_file_system.test"]
|
||||
if !ok {
|
||||
return fmt.Errorf("can't find aws_efs_file_system.test in state")
|
||||
}
|
||||
|
||||
attr := rs.Primary.Attributes
|
||||
|
||||
if attr["creation_token"] != efsRs.Primary.Attributes["creation_token"] {
|
||||
return fmt.Errorf(
|
||||
"creation_token is %s; want %s",
|
||||
attr["creation_token"],
|
||||
efsRs.Primary.Attributes["creation_token"],
|
||||
)
|
||||
}
|
||||
|
||||
if attr["id"] != efsRs.Primary.Attributes["id"] {
|
||||
return fmt.Errorf(
|
||||
"file_system_id is %s; want %s",
|
||||
attr["id"],
|
||||
efsRs.Primary.Attributes["id"],
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccDataSourceAwsEfsFileSystemConfig = `
|
||||
resource "aws_efs_file_system" "test" {}
|
||||
|
||||
data "aws_efs_file_system" "by_creation_token" {
|
||||
creation_token = "${aws_efs_file_system.test.creation_token}"
|
||||
}
|
||||
|
||||
data "aws_efs_file_system" "by_id" {
|
||||
file_system_id = "${aws_efs_file_system.test.id}"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,28 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
)
|
||||
|
||||
func TestAccAWSEmrSecurityConfiguration_importBasic(t *testing.T) {
|
||||
resourceName := "aws_emr_security_configuration.foo"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckEmrSecurityConfigurationDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccEmrSecurityConfigurationConfig,
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -178,6 +178,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"aws_ecs_cluster": dataSourceAwsEcsCluster(),
|
||||
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
|
||||
"aws_ecs_task_definition": dataSourceAwsEcsTaskDefinition(),
|
||||
"aws_efs_file_system": dataSourceAwsEfsFileSystem(),
|
||||
"aws_eip": dataSourceAwsEip(),
|
||||
"aws_elb_hosted_zone_id": dataSourceAwsElbHostedZoneId(),
|
||||
"aws_elb_service_account": dataSourceAwsElbServiceAccount(),
|
||||
|
@ -313,6 +314,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"aws_elb_attachment": resourceAwsElbAttachment(),
|
||||
"aws_emr_cluster": resourceAwsEMRCluster(),
|
||||
"aws_emr_instance_group": resourceAwsEMRInstanceGroup(),
|
||||
"aws_emr_security_configuration": resourceAwsEMRSecurityConfiguration(),
|
||||
"aws_flow_log": resourceAwsFlowLog(),
|
||||
"aws_glacier_vault": resourceAwsGlacierVault(),
|
||||
"aws_iam_access_key": resourceAwsIamAccessKey(),
|
||||
|
|
|
@ -335,6 +335,11 @@ func resourceAwsDbInstance() *schema.Resource {
|
|||
ForceNew: true,
|
||||
},
|
||||
|
||||
"iam_database_authentication_enabled": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
},
|
||||
}
|
||||
|
@ -634,6 +639,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
|
|||
opts.KmsKeyId = aws.String(attr.(string))
|
||||
}
|
||||
|
||||
if attr, ok := d.GetOk("iam_database_authentication_enabled"); ok {
|
||||
opts.EnableIAMDatabaseAuthentication = aws.Bool(attr.(bool))
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] DB Instance create configuration: %#v", opts)
|
||||
var err error
|
||||
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
|
||||
|
@ -710,6 +719,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
|||
d.Set("multi_az", v.MultiAZ)
|
||||
d.Set("kms_key_id", v.KmsKeyId)
|
||||
d.Set("port", v.DbInstancePort)
|
||||
d.Set("iam_database_authentication_enabled", v.IAMDatabaseAuthenticationEnabled)
|
||||
if v.DBSubnetGroup != nil {
|
||||
d.Set("db_subnet_group_name", v.DBSubnetGroup.DBSubnetGroupName)
|
||||
}
|
||||
|
@ -994,6 +1004,11 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error
|
|||
requestUpdate = true
|
||||
}
|
||||
|
||||
if d.HasChange("iam_database_authentication_enabled") {
|
||||
req.EnableIAMDatabaseAuthentication = aws.Bool(d.Get("iam_database_authentication_enabled").(bool))
|
||||
requestUpdate = true
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Send DB Instance Modification request: %t", requestUpdate)
|
||||
if requestUpdate {
|
||||
log.Printf("[DEBUG] DB Instance Modification request: %s", req)
|
||||
|
|
|
@ -170,6 +170,27 @@ func TestAccAWSDBInstance_optionGroup(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSDBInstance_iamAuth(t *testing.T) {
|
||||
var v rds.DBInstance
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSDBInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckAWSDBIAMAuth(acctest.RandInt()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v),
|
||||
testAccCheckAWSDBInstanceAttributes(&v),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_db_instance.bar", "iam_database_authentication_enabled", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSDBInstanceReplica(t *testing.T) {
|
||||
var s, r rds.DBInstance
|
||||
|
||||
|
@ -773,6 +794,24 @@ resource "aws_db_instance" "bar" {
|
|||
}`, rName, acctest.RandInt())
|
||||
}
|
||||
|
||||
func testAccCheckAWSDBIAMAuth(n int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_db_instance" "bar" {
|
||||
identifier = "foobarbaz-test-terraform-%d"
|
||||
allocated_storage = 10
|
||||
engine = "mysql"
|
||||
engine_version = "5.6.34"
|
||||
instance_class = "db.t2.micro"
|
||||
name = "baz"
|
||||
password = "barbarbarbar"
|
||||
username = "foo"
|
||||
backup_retention_period = 0
|
||||
skip_final_snapshot = true
|
||||
parameter_group_name = "default.mysql5.6"
|
||||
iam_database_authentication_enabled = true
|
||||
}`, n)
|
||||
}
|
||||
|
||||
func testAccReplicaInstanceConfig(val int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_db_instance" "bar" {
|
||||
|
|
|
@ -92,6 +92,23 @@ func resourceAwsDynamoDbTable() *schema.Resource {
|
|||
return hashcode.String(buf.String())
|
||||
},
|
||||
},
|
||||
"ttl": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"attribute_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"enabled": {
|
||||
Type: schema.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"local_secondary_index": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
|
@ -296,6 +313,7 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er
|
|||
log.Printf("[DEBUG] Adding StreamSpecifications to the table")
|
||||
}
|
||||
|
||||
_, timeToLiveOk := d.GetOk("ttl")
|
||||
_, tagsOk := d.GetOk("tags")
|
||||
|
||||
attemptCount := 1
|
||||
|
@ -326,12 +344,28 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er
|
|||
if err := d.Set("arn", tableArn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait, till table is active before imitating any TimeToLive changes
|
||||
if err := waitForTableToBeActive(d.Id(), meta); err != nil {
|
||||
log.Printf("[DEBUG] Error waiting for table to be active: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Setting DynamoDB TimeToLive on arn: %s", tableArn)
|
||||
if timeToLiveOk {
|
||||
if err := updateTimeToLive(d, meta); err != nil {
|
||||
log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if tagsOk {
|
||||
log.Printf("[DEBUG] Setting DynamoDB Tags on arn: %s", tableArn)
|
||||
if err := createTableTags(d, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return resourceAwsDynamoDbTableRead(d, meta)
|
||||
}
|
||||
}
|
||||
|
@ -587,6 +621,13 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er
|
|||
|
||||
}
|
||||
|
||||
if d.HasChange("ttl") {
|
||||
if err := updateTimeToLive(d, meta); err != nil {
|
||||
log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update tags
|
||||
if err := setTagsDynamoDb(dynamodbconn, d); err != nil {
|
||||
return err
|
||||
|
@ -595,6 +636,46 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er
|
|||
return resourceAwsDynamoDbTableRead(d, meta)
|
||||
}
|
||||
|
||||
func updateTimeToLive(d *schema.ResourceData, meta interface{}) error {
|
||||
dynamodbconn := meta.(*AWSClient).dynamodbconn
|
||||
|
||||
if ttl, ok := d.GetOk("ttl"); ok {
|
||||
|
||||
timeToLiveSet := ttl.(*schema.Set)
|
||||
|
||||
spec := &dynamodb.TimeToLiveSpecification{}
|
||||
|
||||
timeToLive := timeToLiveSet.List()[0].(map[string]interface{})
|
||||
spec.AttributeName = aws.String(timeToLive["attribute_name"].(string))
|
||||
spec.Enabled = aws.Bool(timeToLive["enabled"].(bool))
|
||||
|
||||
req := &dynamodb.UpdateTimeToLiveInput{
|
||||
TableName: aws.String(d.Id()),
|
||||
TimeToLiveSpecification: spec,
|
||||
}
|
||||
|
||||
_, err := dynamodbconn.UpdateTimeToLive(req)
|
||||
|
||||
if err != nil {
|
||||
// If ttl was not set within the .tf file before and has now been added we still run this command to update
|
||||
// But there has been no change so lets continue
|
||||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ValidationException" && awsErr.Message() == "TimeToLive is already disabled" {
|
||||
return nil
|
||||
}
|
||||
log.Printf("[DEBUG] Error updating TimeToLive on table: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Updated TimeToLive on table")
|
||||
|
||||
if err := waitForTimeToLiveUpdateToBeCompleted(d.Id(), timeToLive["enabled"].(bool), meta); err != nil {
|
||||
return errwrap.Wrapf("Error waiting for Dynamo DB TimeToLive to be updated: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error {
|
||||
dynamodbconn := meta.(*AWSClient).dynamodbconn
|
||||
log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id())
|
||||
|
@ -711,6 +792,23 @@ func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) erro
|
|||
|
||||
d.Set("arn", table.TableArn)
|
||||
|
||||
timeToLiveReq := &dynamodb.DescribeTimeToLiveInput{
|
||||
TableName: aws.String(d.Id()),
|
||||
}
|
||||
timeToLiveOutput, err := dynamodbconn.DescribeTimeToLive(timeToLiveReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeToLive := []interface{}{}
|
||||
attribute := map[string]*string{
|
||||
"name": timeToLiveOutput.TimeToLiveDescription.AttributeName,
|
||||
"type": timeToLiveOutput.TimeToLiveDescription.TimeToLiveStatus,
|
||||
}
|
||||
timeToLive = append(timeToLive, attribute)
|
||||
d.Set("timeToLive", timeToLive)
|
||||
|
||||
log.Printf("[DEBUG] Loaded TimeToLive data for DynamoDB table '%s'", d.Id())
|
||||
|
||||
tags, err := readTableTags(d, meta)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -910,6 +1008,39 @@ func waitForTableToBeActive(tableName string, meta interface{}) error {
|
|||
|
||||
}
|
||||
|
||||
func waitForTimeToLiveUpdateToBeCompleted(tableName string, enabled bool, meta interface{}) error {
|
||||
dynamodbconn := meta.(*AWSClient).dynamodbconn
|
||||
req := &dynamodb.DescribeTimeToLiveInput{
|
||||
TableName: aws.String(tableName),
|
||||
}
|
||||
|
||||
stateMatched := false
|
||||
for stateMatched == false {
|
||||
result, err := dynamodbconn.DescribeTimeToLive(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enabled {
|
||||
stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusEnabled
|
||||
} else {
|
||||
stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusDisabled
|
||||
}
|
||||
|
||||
// Wait for a few seconds, this may take a long time...
|
||||
if !stateMatched {
|
||||
log.Printf("[DEBUG] Sleeping for 5 seconds before checking TimeToLive state again")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] TimeToLive update complete")
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func createTableTags(d *schema.ResourceData, meta interface{}) error {
|
||||
// DynamoDB Table has to be in the ACTIVE state in order to tag the resource
|
||||
if err := waitForTableToBeActive(d.Id(), meta); err != nil {
|
||||
|
|
|
@ -110,6 +110,71 @@ func TestAccAWSDynamoDbTable_gsiUpdate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSDynamoDbTable_ttl(t *testing.T) {
|
||||
var conf dynamodb.DescribeTableOutput
|
||||
|
||||
rName := acctest.RandomWithPrefix("TerraformTestTable-")
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAWSDynamoDbConfigInitialState(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &conf),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccAWSDynamoDbConfigAddTimeToLive(rName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDynamoDbTableTimeToLiveWasUpdated("aws_dynamodb_table.basic-dynamodb-table"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
func testAccCheckDynamoDbTableTimeToLiveWasUpdated(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
log.Printf("[DEBUG] Trying to create initial table state!")
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No DynamoDB table name specified!")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).dynamodbconn
|
||||
|
||||
params := &dynamodb.DescribeTimeToLiveInput{
|
||||
TableName: aws.String(rs.Primary.ID),
|
||||
}
|
||||
|
||||
resp, err := conn.DescribeTimeToLive(params)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] Problem describing time to live for table '%s': %s", rs.Primary.ID, err)
|
||||
}
|
||||
|
||||
ttlDescription := resp.TimeToLiveDescription
|
||||
|
||||
log.Printf("[DEBUG] Checking on table %s", rs.Primary.ID)
|
||||
|
||||
if *ttlDescription.TimeToLiveStatus != dynamodb.TimeToLiveStatusEnabled {
|
||||
return fmt.Errorf("TimeToLiveStatus %s, not ENABLED!", *ttlDescription.TimeToLiveStatus)
|
||||
}
|
||||
|
||||
if *ttlDescription.AttributeName != "TestTTL" {
|
||||
return fmt.Errorf("AttributeName was %s, not TestTTL!", *ttlDescription.AttributeName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceAWSDynamoDbTableStreamViewType_validation(t *testing.T) {
|
||||
cases := []struct {
|
||||
Value string
|
||||
|
@ -678,3 +743,55 @@ resource "aws_dynamodb_table" "test" {
|
|||
}
|
||||
`, name)
|
||||
}
|
||||
|
||||
func testAccAWSDynamoDbConfigAddTimeToLive(rName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_dynamodb_table" "basic-dynamodb-table" {
|
||||
name = "%s"
|
||||
read_capacity = 10
|
||||
write_capacity = 20
|
||||
hash_key = "TestTableHashKey"
|
||||
range_key = "TestTableRangeKey"
|
||||
|
||||
attribute {
|
||||
name = "TestTableHashKey"
|
||||
type = "S"
|
||||
}
|
||||
|
||||
attribute {
|
||||
name = "TestTableRangeKey"
|
||||
type = "S"
|
||||
}
|
||||
|
||||
attribute {
|
||||
name = "TestLSIRangeKey"
|
||||
type = "N"
|
||||
}
|
||||
|
||||
attribute {
|
||||
name = "TestGSIRangeKey"
|
||||
type = "S"
|
||||
}
|
||||
|
||||
local_secondary_index {
|
||||
name = "TestTableLSI"
|
||||
range_key = "TestLSIRangeKey"
|
||||
projection_type = "ALL"
|
||||
}
|
||||
|
||||
ttl {
|
||||
attribute_name = "TestTTL"
|
||||
enabled = true
|
||||
}
|
||||
|
||||
global_secondary_index {
|
||||
name = "InitialTestTableGSI"
|
||||
hash_key = "TestTableHashKey"
|
||||
range_key = "TestGSIRangeKey"
|
||||
write_capacity = 10
|
||||
read_capacity = 10
|
||||
projection_type = "KEYS_ONLY"
|
||||
}
|
||||
}
|
||||
`, rName)
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ func resourceAWSEbsVolumeUpdate(d *schema.ResourceData, meta interface{}) error
|
|||
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: []string{"creating", "modifying"},
|
||||
Target: []string{"available"},
|
||||
Target: []string{"available", "in-use"},
|
||||
Refresh: volumeStateRefreshFunc(conn, *result.VolumeModification.VolumeId),
|
||||
Timeout: 5 * time.Minute,
|
||||
Delay: 10 * time.Second,
|
||||
|
|
|
@ -30,6 +30,31 @@ func TestAccAWSEBSVolume_basic(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSEBSVolume_updateAttachedEbsVolume(t *testing.T) {
|
||||
var v ec2.Volume
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
IDRefreshName: "aws_ebs_volume.test",
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAwsEbsAttachedVolumeConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVolumeExists("aws_ebs_volume.test", &v),
|
||||
resource.TestCheckResourceAttr("aws_ebs_volume.test", "size", "10"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccAwsEbsAttachedVolumeConfigUpdateSize,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckVolumeExists("aws_ebs_volume.test", &v),
|
||||
resource.TestCheckResourceAttr("aws_ebs_volume.test", "size", "20"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSEBSVolume_updateSize(t *testing.T) {
|
||||
var v ec2.Volume
|
||||
resource.Test(t, resource.TestCase{
|
||||
|
@ -200,6 +225,124 @@ resource "aws_ebs_volume" "test" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccAwsEbsAttachedVolumeConfig = `
|
||||
data "aws_ami" "debian_jessie_latest" {
|
||||
most_recent = true
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["debian-jessie-*"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "architecture"
|
||||
values = ["x86_64"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "root-device-type"
|
||||
values = ["ebs"]
|
||||
}
|
||||
|
||||
owners = ["379101102735"] # Debian
|
||||
}
|
||||
|
||||
resource "aws_instance" "test" {
|
||||
ami = "${data.aws_ami.debian_jessie_latest.id}"
|
||||
associate_public_ip_address = true
|
||||
count = 1
|
||||
instance_type = "t2.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_size = "10"
|
||||
volume_type = "standard"
|
||||
delete_on_termination = true
|
||||
}
|
||||
|
||||
tags {
|
||||
Name = "test-terraform"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ebs_volume" "test" {
|
||||
depends_on = ["aws_instance.test"]
|
||||
availability_zone = "${aws_instance.test.availability_zone}"
|
||||
type = "gp2"
|
||||
size = "10"
|
||||
}
|
||||
|
||||
resource "aws_volume_attachment" "test" {
|
||||
depends_on = ["aws_ebs_volume.test"]
|
||||
device_name = "/dev/xvdg"
|
||||
volume_id = "${aws_ebs_volume.test.id}"
|
||||
instance_id = "${aws_instance.test.id}"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccAwsEbsAttachedVolumeConfigUpdateSize = `
|
||||
data "aws_ami" "debian_jessie_latest" {
|
||||
most_recent = true
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["debian-jessie-*"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "architecture"
|
||||
values = ["x86_64"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "root-device-type"
|
||||
values = ["ebs"]
|
||||
}
|
||||
|
||||
owners = ["379101102735"] # Debian
|
||||
}
|
||||
|
||||
resource "aws_instance" "test" {
|
||||
ami = "${data.aws_ami.debian_jessie_latest.id}"
|
||||
associate_public_ip_address = true
|
||||
count = 1
|
||||
instance_type = "t2.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_size = "10"
|
||||
volume_type = "standard"
|
||||
delete_on_termination = true
|
||||
}
|
||||
|
||||
tags {
|
||||
Name = "test-terraform"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ebs_volume" "test" {
|
||||
depends_on = ["aws_instance.test"]
|
||||
availability_zone = "${aws_instance.test.availability_zone}"
|
||||
type = "gp2"
|
||||
size = "20"
|
||||
}
|
||||
|
||||
resource "aws_volume_attachment" "test" {
|
||||
depends_on = ["aws_ebs_volume.test"]
|
||||
device_name = "/dev/xvdg"
|
||||
volume_id = "${aws_ebs_volume.test.id}"
|
||||
instance_id = "${aws_instance.test.id}"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccAwsEbsVolumeConfigUpdateSize = `
|
||||
resource "aws_ebs_volume" "test" {
|
||||
availability_zone = "us-west-2a"
|
||||
|
|
|
@ -157,6 +157,11 @@ func resourceAwsEMRCluster() *schema.Resource {
|
|||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
"security_configuration": {
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Optional: true,
|
||||
},
|
||||
"autoscaling_role": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
|
@ -268,6 +273,10 @@ func resourceAwsEMRClusterCreate(d *schema.ResourceData, meta interface{}) error
|
|||
params.AutoScalingRole = aws.String(v.(string))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("security_configuration"); ok {
|
||||
params.SecurityConfiguration = aws.String(v.(string))
|
||||
}
|
||||
|
||||
if instanceProfile != "" {
|
||||
params.JobFlowRole = aws.String(instanceProfile)
|
||||
}
|
||||
|
@ -361,6 +370,7 @@ func resourceAwsEMRClusterRead(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
d.Set("name", cluster.Name)
|
||||
d.Set("service_role", cluster.ServiceRole)
|
||||
d.Set("security_configuration", cluster.SecurityConfiguration)
|
||||
d.Set("autoscaling_role", cluster.AutoScalingRole)
|
||||
d.Set("release_label", cluster.ReleaseLabel)
|
||||
d.Set("log_uri", cluster.LogUri)
|
||||
|
|
|
@ -30,6 +30,22 @@ func TestAccAWSEMRCluster_basic(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSEMRCluster_security_config(t *testing.T) {
|
||||
var cluster emr.Cluster
|
||||
r := acctest.RandInt()
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSEmrDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAWSEmrClusterConfig_SecurityConfiguration(r),
|
||||
Check: testAccCheckAWSEmrClusterExists("aws_emr_cluster.tf-test-cluster", &cluster),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSEMRCluster_bootstrap_ordering(t *testing.T) {
|
||||
var cluster emr.Cluster
|
||||
rName := acctest.RandomWithPrefix("tf-emr-bootstrap")
|
||||
|
@ -881,6 +897,356 @@ resource "aws_iam_role_policy_attachment" "emr-autoscaling-role" {
|
|||
`, r, r, r, r, r, r, r, r, r, r)
|
||||
}
|
||||
|
||||
func testAccAWSEmrClusterConfig_SecurityConfiguration(r int) string {
|
||||
return fmt.Sprintf(`
|
||||
provider "aws" {
|
||||
region = "us-west-2"
|
||||
}
|
||||
|
||||
resource "aws_emr_cluster" "tf-test-cluster" {
|
||||
name = "emr-test-%d"
|
||||
release_label = "emr-5.5.0"
|
||||
applications = ["Spark"]
|
||||
|
||||
ec2_attributes {
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
emr_managed_master_security_group = "${aws_security_group.allow_all.id}"
|
||||
emr_managed_slave_security_group = "${aws_security_group.allow_all.id}"
|
||||
instance_profile = "${aws_iam_instance_profile.emr_profile.arn}"
|
||||
}
|
||||
|
||||
master_instance_type = "m3.xlarge"
|
||||
core_instance_type = "m3.xlarge"
|
||||
core_instance_count = 1
|
||||
|
||||
security_configuration = "${aws_emr_security_configuration.foo.name}"
|
||||
|
||||
tags {
|
||||
role = "rolename"
|
||||
dns_zone = "env_zone"
|
||||
env = "env"
|
||||
name = "name-env"
|
||||
}
|
||||
|
||||
keep_job_flow_alive_when_no_steps = true
|
||||
termination_protection = false
|
||||
|
||||
bootstrap_action {
|
||||
path = "s3://elasticmapreduce/bootstrap-actions/run-if"
|
||||
name = "runif"
|
||||
args = ["instance.isMaster=true", "echo running on master node"]
|
||||
}
|
||||
|
||||
configurations = "test-fixtures/emr_configurations.json"
|
||||
|
||||
depends_on = ["aws_main_route_table_association.a"]
|
||||
|
||||
service_role = "${aws_iam_role.iam_emr_default_role.arn}"
|
||||
autoscaling_role = "${aws_iam_role.emr-autoscaling-role.arn}"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "allow_all" {
|
||||
name = "allow_all_%d"
|
||||
description = "Allow all inbound traffic"
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
|
||||
ingress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
depends_on = ["aws_subnet.main"]
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = ["ingress", "egress"]
|
||||
}
|
||||
|
||||
tags {
|
||||
name = "emr_test"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_vpc" "main" {
|
||||
cidr_block = "168.31.0.0/16"
|
||||
enable_dns_hostnames = true
|
||||
|
||||
tags {
|
||||
name = "emr_test_%d"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_subnet" "main" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
cidr_block = "168.31.0.0/20"
|
||||
|
||||
tags {
|
||||
name = "emr_test_%d"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "gw" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
}
|
||||
|
||||
resource "aws_route_table" "r" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
|
||||
route {
|
||||
cidr_block = "0.0.0.0/0"
|
||||
gateway_id = "${aws_internet_gateway.gw.id}"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_main_route_table_association" "a" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
route_table_id = "${aws_route_table.r.id}"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# IAM things
|
||||
|
||||
###
|
||||
|
||||
# IAM role for EMR Service
|
||||
resource "aws_iam_role" "iam_emr_default_role" {
|
||||
name = "iam_emr_default_role_%d"
|
||||
|
||||
assume_role_policy = <<EOT
|
||||
{
|
||||
"Version": "2008-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "elasticmapreduce.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "service-attach" {
|
||||
role = "${aws_iam_role.iam_emr_default_role.id}"
|
||||
policy_arn = "${aws_iam_policy.iam_emr_default_policy.arn}"
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "iam_emr_default_policy" {
|
||||
name = "iam_emr_default_policy_%d"
|
||||
|
||||
policy = <<EOT
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Resource": "*",
|
||||
"Action": [
|
||||
"ec2:AuthorizeSecurityGroupEgress",
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:CancelSpotInstanceRequests",
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:CreateSecurityGroup",
|
||||
"ec2:CreateTags",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:DeleteSecurityGroup",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeDhcpOptions",
|
||||
"ec2:DescribeInstanceStatus",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DescribeNetworkAcls",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribePrefixLists",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSpotInstanceRequests",
|
||||
"ec2:DescribeSpotPriceHistory",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcEndpointServices",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DetachNetworkInterface",
|
||||
"ec2:ModifyImageAttribute",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:RequestSpotInstances",
|
||||
"ec2:RevokeSecurityGroupEgress",
|
||||
"ec2:RunInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:DeleteVolume",
|
||||
"ec2:DescribeVolumeStatus",
|
||||
"ec2:DescribeVolumes",
|
||||
"ec2:DetachVolume",
|
||||
"iam:GetRole",
|
||||
"iam:GetRolePolicy",
|
||||
"iam:ListInstanceProfiles",
|
||||
"iam:ListRolePolicies",
|
||||
"iam:PassRole",
|
||||
"s3:CreateBucket",
|
||||
"s3:Get*",
|
||||
"s3:List*",
|
||||
"sdb:BatchPutAttributes",
|
||||
"sdb:Select",
|
||||
"sqs:CreateQueue",
|
||||
"sqs:Delete*",
|
||||
"sqs:GetQueue*",
|
||||
"sqs:PurgeQueue",
|
||||
"sqs:ReceiveMessage"
|
||||
]
|
||||
}]
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
# IAM Role for EC2 Instance Profile
|
||||
resource "aws_iam_role" "iam_emr_profile_role" {
|
||||
name = "iam_emr_profile_role_%d"
|
||||
|
||||
assume_role_policy = <<EOT
|
||||
{
|
||||
"Version": "2008-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "ec2.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "aws_iam_instance_profile" "emr_profile" {
|
||||
name = "emr_profile_%d"
|
||||
roles = ["${aws_iam_role.iam_emr_profile_role.name}"]
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "profile-attach" {
|
||||
role = "${aws_iam_role.iam_emr_profile_role.id}"
|
||||
policy_arn = "${aws_iam_policy.iam_emr_profile_policy.arn}"
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "iam_emr_profile_policy" {
|
||||
name = "iam_emr_profile_policy_%d"
|
||||
|
||||
policy = <<EOT
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Resource": "*",
|
||||
"Action": [
|
||||
"cloudwatch:*",
|
||||
"dynamodb:*",
|
||||
"ec2:Describe*",
|
||||
"elasticmapreduce:Describe*",
|
||||
"elasticmapreduce:ListBootstrapActions",
|
||||
"elasticmapreduce:ListClusters",
|
||||
"elasticmapreduce:ListInstanceGroups",
|
||||
"elasticmapreduce:ListInstances",
|
||||
"elasticmapreduce:ListSteps",
|
||||
"kinesis:CreateStream",
|
||||
"kinesis:DeleteStream",
|
||||
"kinesis:DescribeStream",
|
||||
"kinesis:GetRecords",
|
||||
"kinesis:GetShardIterator",
|
||||
"kinesis:MergeShards",
|
||||
"kinesis:PutRecord",
|
||||
"kinesis:SplitShard",
|
||||
"rds:Describe*",
|
||||
"s3:*",
|
||||
"sdb:*",
|
||||
"sns:*",
|
||||
"sqs:*"
|
||||
]
|
||||
}]
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
# IAM Role for autoscaling
|
||||
resource "aws_iam_role" "emr-autoscaling-role" {
|
||||
name = "EMR_AutoScaling_DefaultRole_%d"
|
||||
assume_role_policy = "${data.aws_iam_policy_document.emr-autoscaling-role-policy.json}"
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "emr-autoscaling-role-policy" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
principals = {
|
||||
type = "Service"
|
||||
identifiers = ["elasticmapreduce.amazonaws.com","application-autoscaling.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "emr-autoscaling-role" {
|
||||
role = "${aws_iam_role.emr-autoscaling-role.name}"
|
||||
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforAutoScalingRole"
|
||||
}
|
||||
|
||||
resource "aws_emr_security_configuration" "foo" {
|
||||
configuration = <<EOF
|
||||
{
|
||||
"EncryptionConfiguration": {
|
||||
"AtRestEncryptionConfiguration": {
|
||||
"S3EncryptionConfiguration": {
|
||||
"EncryptionMode": "SSE-S3"
|
||||
},
|
||||
"LocalDiskEncryptionConfiguration": {
|
||||
"EncryptionKeyProviderType": "AwsKms",
|
||||
"AwsKmsKey": "${aws_kms_key.foo.arn}"
|
||||
}
|
||||
},
|
||||
"EnableInTransitEncryption": false,
|
||||
"EnableAtRestEncryption": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "foo" {
|
||||
description = "Terraform acc test %d"
|
||||
deletion_window_in_days = 7
|
||||
policy = <<POLICY
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Id": "kms-tf-1",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Enable IAM User Permissions",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": "*"
|
||||
},
|
||||
"Action": "kms:*",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
POLICY
|
||||
}
|
||||
`, r, r, r, r, r, r, r, r, r, r, r)
|
||||
}
|
||||
|
||||
func testAccAWSEmrClusterConfigTerminationPolicyUpdated(r int) string {
|
||||
return fmt.Sprintf(`
|
||||
provider "aws" {
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/emr"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceAwsEMRSecurityConfiguration() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceAwsEmrSecurityConfigurationCreate,
|
||||
Read: resourceAwsEmrSecurityConfigurationRead,
|
||||
Delete: resourceAwsEmrSecurityConfigurationDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
ConflictsWith: []string{"name_prefix"},
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if len(value) > 10280 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q cannot be longer than 10280 characters", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"name_prefix": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if len(value) > 10000 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q cannot be longer than 10000 characters, name is limited to 10280", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"configuration": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateJsonString,
|
||||
},
|
||||
|
||||
"creation_date": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceAwsEmrSecurityConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
conn := meta.(*AWSClient).emrconn
|
||||
|
||||
var emrSCName string
|
||||
if v, ok := d.GetOk("name"); ok {
|
||||
emrSCName = v.(string)
|
||||
} else {
|
||||
if v, ok := d.GetOk("name_prefix"); ok {
|
||||
emrSCName = resource.PrefixedUniqueId(v.(string))
|
||||
} else {
|
||||
emrSCName = resource.PrefixedUniqueId("tf-emr-sc-")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := conn.CreateSecurityConfiguration(&emr.CreateSecurityConfigurationInput{
|
||||
Name: aws.String(emrSCName),
|
||||
SecurityConfiguration: aws.String(d.Get("configuration").(string)),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(*resp.Name)
|
||||
return resourceAwsEmrSecurityConfigurationRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceAwsEmrSecurityConfigurationRead(d *schema.ResourceData, meta interface{}) error {
|
||||
conn := meta.(*AWSClient).emrconn
|
||||
|
||||
resp, err := conn.DescribeSecurityConfiguration(&emr.DescribeSecurityConfigurationInput{
|
||||
Name: aws.String(d.Id()),
|
||||
})
|
||||
if err != nil {
|
||||
if isAWSErr(err, "InvalidRequestException", "does not exist") {
|
||||
log.Printf("[WARN] EMR Security Configuraiton (%s) not found, removing from state", d.Id())
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("creation_date", resp.CreationDateTime)
|
||||
d.Set("name", resp.Name)
|
||||
d.Set("configuration", resp.SecurityConfiguration)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAwsEmrSecurityConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
conn := meta.(*AWSClient).emrconn
|
||||
|
||||
_, err := conn.DeleteSecurityConfiguration(&emr.DeleteSecurityConfigurationInput{
|
||||
Name: aws.String(d.Id()),
|
||||
})
|
||||
if err != nil {
|
||||
if isAWSErr(err, "InvalidRequestException", "does not exist") {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/emr"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSEmrSecurityConfiguration_basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckEmrSecurityConfigurationDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccEmrSecurityConfigurationConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckEmrSecurityConfigurationExists("aws_emr_security_configuration.foo"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckEmrSecurityConfigurationDestroy(s *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).emrconn
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "aws_emr_security_configuration" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find the Security Configuration
|
||||
resp, err := conn.DescribeSecurityConfiguration(&emr.DescribeSecurityConfigurationInput{
|
||||
Name: aws.String(rs.Primary.ID),
|
||||
})
|
||||
if err == nil {
|
||||
if resp.Name != nil && *resp.Name == rs.Primary.ID {
|
||||
// assume this means the resource still exists
|
||||
return fmt.Errorf("Error: EMR Security Configuration still exists: %s", *resp.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify the error is what we want
|
||||
if err != nil {
|
||||
if isAWSErr(err, "InvalidRequestException", "does not exist") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckEmrSecurityConfigurationExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No EMR Security Configuration ID is set")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).emrconn
|
||||
resp, err := conn.DescribeSecurityConfiguration(&emr.DescribeSecurityConfigurationInput{
|
||||
Name: aws.String(rs.Primary.ID),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Name == nil {
|
||||
return fmt.Errorf("EMR Security Configuration had nil name which shouldn't happen")
|
||||
}
|
||||
|
||||
if *resp.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("EMR Security Configuration name mismatch, got (%s), expected (%s)", *resp.Name, rs.Primary.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccEmrSecurityConfigurationConfig = `
|
||||
resource "aws_emr_security_configuration" "foo" {
|
||||
configuration = <<EOF
|
||||
{
|
||||
"EncryptionConfiguration": {
|
||||
"AtRestEncryptionConfiguration": {
|
||||
"S3EncryptionConfiguration": {
|
||||
"EncryptionMode": "SSE-S3"
|
||||
},
|
||||
"LocalDiskEncryptionConfiguration": {
|
||||
"EncryptionKeyProviderType": "AwsKms",
|
||||
"AwsKmsKey": "arn:aws:kms:us-west-2:187416307283:alias/tf_emr_test_key"
|
||||
}
|
||||
},
|
||||
"EnableInTransitEncryption": false,
|
||||
"EnableAtRestEncryption": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
`
|
|
@ -90,6 +90,11 @@ func resourceAwsInstance() *schema.Resource {
|
|||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
// Suppress diff if network_interface is set
|
||||
_, ok := d.GetOk("network_interface")
|
||||
return ok
|
||||
},
|
||||
},
|
||||
|
||||
"user_data": {
|
||||
|
@ -234,7 +239,7 @@ func resourceAwsInstance() *schema.Resource {
|
|||
|
||||
"tags": tagsSchema(),
|
||||
|
||||
"volume_tags": tagsSchema(),
|
||||
"volume_tags": tagsSchemaComputed(),
|
||||
|
||||
"block_device": {
|
||||
Type: schema.TypeMap,
|
||||
|
@ -432,32 +437,35 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
runOpts.Ipv6Addresses = ipv6Addresses
|
||||
}
|
||||
|
||||
tagsSpec := make([]*ec2.TagSpecification, 0)
|
||||
restricted := meta.(*AWSClient).IsGovCloud() || meta.(*AWSClient).IsChinaCloud()
|
||||
if !restricted {
|
||||
tagsSpec := make([]*ec2.TagSpecification, 0)
|
||||
|
||||
if v, ok := d.GetOk("tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
if v, ok := d.GetOk("tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
if v, ok := d.GetOk("volume_tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
|
||||
if v, ok := d.GetOk("volume_tags"); ok {
|
||||
tags := tagsFromMap(v.(map[string]interface{}))
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
spec := &ec2.TagSpecification{
|
||||
ResourceType: aws.String("volume"),
|
||||
Tags: tags,
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
tagsSpec = append(tagsSpec, spec)
|
||||
}
|
||||
|
||||
if len(tagsSpec) > 0 {
|
||||
runOpts.TagSpecifications = tagsSpec
|
||||
if len(tagsSpec) > 0 {
|
||||
runOpts.TagSpecifications = tagsSpec
|
||||
}
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
|
@ -639,6 +647,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
|||
d.Set("primary_network_interface_id", primaryNetworkInterface.NetworkInterfaceId)
|
||||
d.Set("associate_public_ip_address", primaryNetworkInterface.Association != nil)
|
||||
d.Set("ipv6_address_count", len(primaryNetworkInterface.Ipv6Addresses))
|
||||
d.Set("source_dest_check", *primaryNetworkInterface.SourceDestCheck)
|
||||
|
||||
for _, address := range primaryNetworkInterface.Ipv6Addresses {
|
||||
ipv6Addresses = append(ipv6Addresses, *address.Ipv6Address)
|
||||
|
@ -713,19 +722,24 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
d.Partial(true)
|
||||
|
||||
if d.HasChange("tags") && !d.IsNewResource() {
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
restricted := meta.(*AWSClient).IsGovCloud() || meta.(*AWSClient).IsChinaCloud()
|
||||
|
||||
if d.HasChange("tags") {
|
||||
if !d.IsNewResource() || !restricted {
|
||||
if err := setTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("tags")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange("volume_tags") && !d.IsNewResource() {
|
||||
if err := setVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("volume_tags")
|
||||
if d.HasChange("volume_tags") {
|
||||
if !d.IsNewResource() || !restricted {
|
||||
if err := setVolumeTags(conn, d); err != nil {
|
||||
return err
|
||||
} else {
|
||||
d.SetPartial("volume_tags")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -678,6 +678,25 @@ func TestAccAWSInstance_volumeTags(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_volumeTagsComputed(t *testing.T) {
|
||||
var v ec2.Instance
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckInstanceConfigWithAttachedVolume,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &v),
|
||||
),
|
||||
ExpectNonEmptyPlan: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_instanceProfileChange(t *testing.T) {
|
||||
var v ec2.Instance
|
||||
rName := acctest.RandString(5)
|
||||
|
@ -947,6 +966,27 @@ func TestAccAWSInstance_primaryNetworkInterface(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_primaryNetworkInterfaceSourceDestCheck(t *testing.T) {
|
||||
var instance ec2.Instance
|
||||
var ini ec2.NetworkInterface
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckInstanceDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccInstanceConfigPrimaryNetworkInterfaceSourceDestCheck,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckInstanceExists("aws_instance.foo", &instance),
|
||||
testAccCheckAWSENIExists("aws_network_interface.bar", &ini),
|
||||
resource.TestCheckResourceAttr("aws_instance.foo", "source_dest_check", "false"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSInstance_addSecondaryInterface(t *testing.T) {
|
||||
var before ec2.Instance
|
||||
var after ec2.Instance
|
||||
|
@ -1382,6 +1422,69 @@ resource "aws_instance" "foo" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigWithAttachedVolume = `
|
||||
data "aws_ami" "debian_jessie_latest" {
|
||||
most_recent = true
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["debian-jessie-*"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "architecture"
|
||||
values = ["x86_64"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "root-device-type"
|
||||
values = ["ebs"]
|
||||
}
|
||||
|
||||
owners = ["379101102735"] # Debian
|
||||
}
|
||||
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "${data.aws_ami.debian_jessie_latest.id}"
|
||||
associate_public_ip_address = true
|
||||
count = 1
|
||||
instance_type = "t2.medium"
|
||||
|
||||
root_block_device {
|
||||
volume_size = "10"
|
||||
volume_type = "standard"
|
||||
delete_on_termination = true
|
||||
}
|
||||
|
||||
tags {
|
||||
Name = "test-terraform"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ebs_volume" "test" {
|
||||
depends_on = ["aws_instance.foo"]
|
||||
availability_zone = "${aws_instance.foo.availability_zone}"
|
||||
type = "gp2"
|
||||
size = "10"
|
||||
|
||||
tags {
|
||||
Name = "test-terraform"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_volume_attachment" "test" {
|
||||
depends_on = ["aws_ebs_volume.test"]
|
||||
device_name = "/dev/xvdg"
|
||||
volume_id = "${aws_ebs_volume.test.id}"
|
||||
instance_id = "${aws_instance.foo.id}"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccCheckInstanceConfigNoVolumeTags = `
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-55a7ea65"
|
||||
|
@ -1784,6 +1887,42 @@ resource "aws_instance" "foo" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccInstanceConfigPrimaryNetworkInterfaceSourceDestCheck = `
|
||||
resource "aws_vpc" "foo" {
|
||||
cidr_block = "172.16.0.0/16"
|
||||
tags {
|
||||
Name = "tf-instance-test"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_subnet" "foo" {
|
||||
vpc_id = "${aws_vpc.foo.id}"
|
||||
cidr_block = "172.16.10.0/24"
|
||||
availability_zone = "us-west-2a"
|
||||
tags {
|
||||
Name = "tf-instance-test"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_network_interface" "bar" {
|
||||
subnet_id = "${aws_subnet.foo.id}"
|
||||
private_ips = ["172.16.10.100"]
|
||||
source_dest_check = false
|
||||
tags {
|
||||
Name = "primary_network_interface"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "foo" {
|
||||
ami = "ami-22b9a343"
|
||||
instance_type = "t2.micro"
|
||||
network_interface {
|
||||
network_interface_id = "${aws_network_interface.bar.id}"
|
||||
device_index = 0
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const testAccInstanceConfigAddSecondaryNetworkInterfaceBefore = `
|
||||
resource "aws_vpc" "foo" {
|
||||
cidr_block = "172.16.0.0/16"
|
||||
|
|
|
@ -320,19 +320,33 @@ func updateKmsKeyStatus(conn *kms.KMS, id string, shouldBeEnabled bool) error {
|
|||
}
|
||||
|
||||
func updateKmsKeyRotationStatus(conn *kms.KMS, d *schema.ResourceData) error {
|
||||
var err error
|
||||
shouldEnableRotation := d.Get("enable_key_rotation").(bool)
|
||||
if shouldEnableRotation {
|
||||
log.Printf("[DEBUG] Enabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.EnableKeyRotation(&kms.EnableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
} else {
|
||||
log.Printf("[DEBUG] Disabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.DisableKeyRotation(&kms.DisableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
}
|
||||
|
||||
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
|
||||
var err error
|
||||
if shouldEnableRotation {
|
||||
log.Printf("[DEBUG] Enabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.EnableKeyRotation(&kms.EnableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
} else {
|
||||
log.Printf("[DEBUG] Disabling key rotation for KMS key %q", d.Id())
|
||||
_, err = conn.DisableKeyRotation(&kms.DisableKeyRotationInput{
|
||||
KeyId: aws.String(d.Id()),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
if ok && awsErr.Code() == "DisabledException" {
|
||||
return resource.RetryableError(err)
|
||||
}
|
||||
|
||||
return resource.NonRetryableError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to set key rotation for %q to %t: %q",
|
||||
|
|
|
@ -216,6 +216,11 @@ func resourceAwsRDSCluster() *schema.Resource {
|
|||
Optional: true,
|
||||
},
|
||||
|
||||
"iam_database_authentication_enabled": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
},
|
||||
}
|
||||
|
@ -428,6 +433,10 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error
|
|||
createOpts.KmsKeyId = aws.String(attr.(string))
|
||||
}
|
||||
|
||||
if attr, ok := d.GetOk("iam_database_authentication_enabled"); ok {
|
||||
createOpts.EnableIAMDatabaseAuthentication = aws.Bool(attr.(bool))
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] RDS Cluster create options: %s", createOpts)
|
||||
resp, err := conn.CreateDBCluster(createOpts)
|
||||
if err != nil {
|
||||
|
@ -520,6 +529,7 @@ func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error {
|
|||
d.Set("kms_key_id", dbc.KmsKeyId)
|
||||
d.Set("reader_endpoint", dbc.ReaderEndpoint)
|
||||
d.Set("replication_source_identifier", dbc.ReplicationSourceIdentifier)
|
||||
d.Set("iam_database_authentication_enabled", dbc.IAMDatabaseAuthenticationEnabled)
|
||||
|
||||
var vpcg []string
|
||||
for _, g := range dbc.VpcSecurityGroups {
|
||||
|
@ -594,6 +604,11 @@ func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error
|
|||
requestUpdate = true
|
||||
}
|
||||
|
||||
if d.HasChange("iam_database_authentication_enabled") {
|
||||
req.EnableIAMDatabaseAuthentication = aws.Bool(d.Get("iam_database_authentication_enabled").(bool))
|
||||
requestUpdate = true
|
||||
}
|
||||
|
||||
if requestUpdate {
|
||||
_, err := conn.ModifyDBCluster(req)
|
||||
if err != nil {
|
||||
|
|
|
@ -225,6 +225,26 @@ func TestAccAWSRDSCluster_backupsUpdate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSRDSCluster_iamAuth(t *testing.T) {
|
||||
var v rds.DBCluster
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSClusterDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAWSClusterConfig_iamAuth(acctest.RandInt()),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSClusterExists("aws_rds_cluster.default", &v),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_rds_cluster.default", "iam_database_authentication_enabled", "true"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAWSClusterDestroy(s *terraform.State) error {
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "aws_rds_cluster" {
|
||||
|
@ -550,3 +570,16 @@ resource "aws_rds_cluster" "default" {
|
|||
skip_final_snapshot = true
|
||||
}`, n)
|
||||
}
|
||||
|
||||
func testAccAWSClusterConfig_iamAuth(n int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_rds_cluster" "default" {
|
||||
cluster_identifier = "tf-aurora-cluster-%d"
|
||||
availability_zones = ["us-west-2a","us-west-2b","us-west-2c"]
|
||||
database_name = "mydb"
|
||||
master_username = "foo"
|
||||
master_password = "mustbeeightcharaters"
|
||||
iam_database_authentication_enabled = true
|
||||
skip_final_snapshot = true
|
||||
}`, n)
|
||||
}
|
||||
|
|
|
@ -484,7 +484,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro
|
|||
}
|
||||
}
|
||||
|
||||
err = d.Set("records", flattenResourceRecords(record.ResourceRecords))
|
||||
err = d.Set("records", flattenResourceRecords(record.ResourceRecords, *record.Type))
|
||||
if err != nil {
|
||||
return fmt.Errorf("[DEBUG] Error setting records for: %s, error: %#v", d.Id(), err)
|
||||
}
|
||||
|
|
|
@ -55,9 +55,15 @@ func resourceAwsSnsTopic() *schema.Resource {
|
|||
},
|
||||
},
|
||||
"delivery_policy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: false,
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: false,
|
||||
ValidateFunc: validateJsonString,
|
||||
DiffSuppressFunc: suppressEquivalentJsonDiffs,
|
||||
StateFunc: func(v interface{}) string {
|
||||
json, _ := normalizeJsonString(v)
|
||||
return json
|
||||
},
|
||||
},
|
||||
"arn": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
|
|
@ -67,6 +67,25 @@ func TestAccAWSSNSTopic_withIAMRole(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSSNSTopic_withDeliveryPolicy(t *testing.T) {
|
||||
expectedPolicy := `{"http":{"defaultHealthyRetryPolicy": {"minDelayTarget": 20,"maxDelayTarget": 20,"numMaxDelayRetries": 0,"numRetries": 3,"numNoDelayRetries": 0,"numMinDelayRetries": 0,"backoffFunction": "linear"},"disableSubscriptionOverrides": false}}`
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
IDRefreshName: "aws_sns_topic.test_topic",
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSSNSTopicDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccAWSSNSTopicConfig_withDeliveryPolicy,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSSNSTopicExists("aws_sns_topic.test_topic"),
|
||||
testAccCheckAWSNSTopicHasDeliveryPolicy("aws_sns_topic.test_topic", expectedPolicy),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAWSNSTopicHasPolicy(n string, expectedPolicyText string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
@ -117,6 +136,46 @@ func testAccCheckAWSNSTopicHasPolicy(n string, expectedPolicyText string) resour
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckAWSNSTopicHasDeliveryPolicy(n string, expectedPolicyText string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Queue URL specified!")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).snsconn
|
||||
|
||||
params := &sns.GetTopicAttributesInput{
|
||||
TopicArn: aws.String(rs.Primary.ID),
|
||||
}
|
||||
resp, err := conn.GetTopicAttributes(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var actualPolicyText string
|
||||
for k, v := range resp.Attributes {
|
||||
if k == "DeliveryPolicy" {
|
||||
actualPolicyText = *v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
equivalent := suppressEquivalentJsonDiffs("", actualPolicyText, expectedPolicyText, nil)
|
||||
|
||||
if !equivalent {
|
||||
return fmt.Errorf("Non-equivalent delivery policy error:\n\nexpected: %s\n\n got: %s\n",
|
||||
expectedPolicyText, actualPolicyText)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAWSSNSTopicDestroy(s *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).snsconn
|
||||
|
||||
|
@ -244,3 +303,26 @@ resource "aws_sns_topic" "test_topic" {
|
|||
EOF
|
||||
}
|
||||
`
|
||||
|
||||
// Test for https://github.com/hashicorp/terraform/issues/14024
|
||||
const testAccAWSSNSTopicConfig_withDeliveryPolicy = `
|
||||
resource "aws_sns_topic" "test_topic" {
|
||||
name = "test_delivery_policy"
|
||||
delivery_policy = <<EOF
|
||||
{
|
||||
"http": {
|
||||
"defaultHealthyRetryPolicy": {
|
||||
"minDelayTarget": 20,
|
||||
"maxDelayTarget": 20,
|
||||
"numRetries": 3,
|
||||
"numMaxDelayRetries": 0,
|
||||
"numNoDelayRetries": 0,
|
||||
"numMinDelayRetries": 0,
|
||||
"backoffFunction": "linear"
|
||||
},
|
||||
"disableSubscriptionOverrides": false
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
`
|
||||
|
|
|
@ -206,6 +206,11 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
|
|||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"placement_tenancy": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"spot_price": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -304,10 +309,15 @@ func buildSpotFleetLaunchSpecification(d map[string]interface{}, meta interface{
|
|||
SpotPrice: aws.String(d["spot_price"].(string)),
|
||||
}
|
||||
|
||||
placement := new(ec2.SpotPlacement)
|
||||
if v, ok := d["availability_zone"]; ok {
|
||||
opts.Placement = &ec2.SpotPlacement{
|
||||
AvailabilityZone: aws.String(v.(string)),
|
||||
}
|
||||
placement.AvailabilityZone = aws.String(v.(string))
|
||||
opts.Placement = placement
|
||||
}
|
||||
|
||||
if v, ok := d["placement_tenancy"]; ok {
|
||||
placement.Tenancy = aws.String(v.(string))
|
||||
opts.Placement = placement
|
||||
}
|
||||
|
||||
if v, ok := d["ebs_optimized"]; ok {
|
||||
|
|
|
@ -325,6 +325,30 @@ func TestAccAWSSpotFleetRequest_withEBSDisk(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSSpotFleetRequest_placementTenancy(t *testing.T) {
|
||||
var sfr ec2.SpotFleetRequestConfig
|
||||
rName := acctest.RandString(10)
|
||||
rInt := acctest.RandInt()
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAWSSpotFleetRequestDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAWSSpotFleetRequestTenancyConfig(rName, rInt),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
testAccCheckAWSSpotFleetRequestExists(
|
||||
"aws_spot_fleet_request.foo", &sfr),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_spot_fleet_request.foo", "spot_request_state", "active"),
|
||||
testAccCheckAWSSpotFleetRequest_PlacementAttributes(&sfr),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSSpotFleetRequest_CannotUseEmptyKeyName(t *testing.T) {
|
||||
_, errs := validateSpotFleetRequestKeyName("", "key_name")
|
||||
if len(errs) == 0 {
|
||||
|
@ -400,6 +424,27 @@ func testAccCheckAWSSpotFleetRequest_EBSAttributes(
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckAWSSpotFleetRequest_PlacementAttributes(
|
||||
sfr *ec2.SpotFleetRequestConfig) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if len(sfr.SpotFleetRequestConfig.LaunchSpecifications) == 0 {
|
||||
return errors.New("Missing launch specification")
|
||||
}
|
||||
|
||||
spec := *sfr.SpotFleetRequestConfig.LaunchSpecifications[0]
|
||||
|
||||
placement := spec.Placement
|
||||
if placement == nil {
|
||||
return fmt.Errorf("Expected placement to be set, got nil")
|
||||
}
|
||||
if *placement.Tenancy != "dedicated" {
|
||||
return fmt.Errorf("Expected placement tenancy to be %q, got %q", "dedicated", placement.Tenancy)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAWSSpotFleetRequestDestroy(s *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
|
||||
|
@ -1251,3 +1296,78 @@ resource "aws_spot_fleet_request" "foo" {
|
|||
}
|
||||
`, rInt, rInt, rName)
|
||||
}
|
||||
|
||||
func testAccAWSSpotFleetRequestTenancyConfig(rName string, rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_key_pair" "debugging" {
|
||||
key_name = "tmp-key-%s"
|
||||
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com"
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "test-policy" {
|
||||
name = "test-policy-%d"
|
||||
path = "/"
|
||||
description = "Spot Fleet Request ACCTest Policy"
|
||||
policy = <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeImages",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:RequestSpotInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:DescribeInstanceStatus",
|
||||
"iam:PassRole"
|
||||
],
|
||||
"Resource": ["*"]
|
||||
}]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_iam_policy_attachment" "test-attach" {
|
||||
name = "test-attachment-%d"
|
||||
roles = ["${aws_iam_role.test-role.name}"]
|
||||
policy_arn = "${aws_iam_policy.test-policy.arn}"
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "test-role" {
|
||||
name = "test-role-%s"
|
||||
assume_role_policy = <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": [
|
||||
"spotfleet.amazonaws.com",
|
||||
"ec2.amazonaws.com"
|
||||
]
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_spot_fleet_request" "foo" {
|
||||
iam_fleet_role = "${aws_iam_role.test-role.arn}"
|
||||
spot_price = "0.005"
|
||||
target_capacity = 2
|
||||
valid_until = "2019-11-04T20:44:20Z"
|
||||
terminate_instances_with_expiration = true
|
||||
launch_specification {
|
||||
instance_type = "m1.small"
|
||||
ami = "ami-d06a90b0"
|
||||
key_name = "${aws_key_pair.debugging.key_name}"
|
||||
placement_tenancy = "dedicated"
|
||||
}
|
||||
depends_on = ["aws_iam_policy_attachment.test-attach"]
|
||||
}
|
||||
`, rName, rInt, rInt, rName)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func resourceAwsSpotInstanceRequest() *schema.Resource {
|
|||
|
||||
// Everything on a spot instance is ForceNew except tags
|
||||
for k, v := range s {
|
||||
if k == "tags" {
|
||||
if k == "tags" || k == "volume_tags" {
|
||||
continue
|
||||
}
|
||||
v.ForceNew = true
|
||||
|
|
|
@ -814,11 +814,14 @@ func flattenStepAdjustments(adjustments []*autoscaling.StepAdjustment) []map[str
|
|||
return result
|
||||
}
|
||||
|
||||
func flattenResourceRecords(recs []*route53.ResourceRecord) []string {
|
||||
func flattenResourceRecords(recs []*route53.ResourceRecord, typeStr string) []string {
|
||||
strs := make([]string, 0, len(recs))
|
||||
for _, r := range recs {
|
||||
if r.Value != nil {
|
||||
s := strings.Replace(*r.Value, "\"", "", 2)
|
||||
s := *r.Value
|
||||
if typeStr == "TXT" || typeStr == "SPF" {
|
||||
s = strings.Replace(s, "\"", "", 2)
|
||||
}
|
||||
strs = append(strs, s)
|
||||
}
|
||||
}
|
||||
|
@ -829,13 +832,11 @@ func expandResourceRecords(recs []interface{}, typeStr string) []*route53.Resour
|
|||
records := make([]*route53.ResourceRecord, 0, len(recs))
|
||||
for _, r := range recs {
|
||||
s := r.(string)
|
||||
switch typeStr {
|
||||
case "TXT", "SPF":
|
||||
str := fmt.Sprintf("\"%s\"", s)
|
||||
records = append(records, &route53.ResourceRecord{Value: aws.String(str)})
|
||||
default:
|
||||
records = append(records, &route53.ResourceRecord{Value: aws.String(s)})
|
||||
if typeStr == "TXT" || typeStr == "SPF" {
|
||||
// `flattenResourceRecords` removes quotes. Add them back.
|
||||
s = fmt.Sprintf("\"%s\"", s)
|
||||
}
|
||||
records = append(records, &route53.ResourceRecord{Value: aws.String(s)})
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
|
|
@ -819,23 +819,56 @@ func TestFlattenStepAdjustments(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFlattenResourceRecords(t *testing.T) {
|
||||
expanded := []*route53.ResourceRecord{
|
||||
&route53.ResourceRecord{
|
||||
Value: aws.String("127.0.0.1"),
|
||||
},
|
||||
&route53.ResourceRecord{
|
||||
Value: aws.String("127.0.0.3"),
|
||||
},
|
||||
original := []string{
|
||||
`127.0.0.1`,
|
||||
`"abc def"`,
|
||||
}
|
||||
|
||||
result := flattenResourceRecords(expanded)
|
||||
dequoted := []string{
|
||||
`127.0.0.1`,
|
||||
`abc def`,
|
||||
}
|
||||
|
||||
var wrapped []*route53.ResourceRecord = nil
|
||||
for _, original := range original {
|
||||
wrapped = append(wrapped, &route53.ResourceRecord{Value: aws.String(original)})
|
||||
}
|
||||
|
||||
sub := func(recordType string, expected []string) {
|
||||
t.Run(recordType, func(t *testing.T) {
|
||||
checkFlattenResourceRecords(t, recordType, wrapped, expected)
|
||||
})
|
||||
}
|
||||
|
||||
// These record types should be dequoted.
|
||||
sub("TXT", dequoted)
|
||||
sub("SPF", dequoted)
|
||||
|
||||
// These record types should not be touched.
|
||||
sub("CNAME", original)
|
||||
sub("MX", original)
|
||||
}
|
||||
|
||||
func checkFlattenResourceRecords(
|
||||
t *testing.T,
|
||||
recordType string,
|
||||
expanded []*route53.ResourceRecord,
|
||||
expected []string) {
|
||||
|
||||
result := flattenResourceRecords(expanded, recordType)
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("expected result to have value, but got nil")
|
||||
}
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatal("expected result to have value, but got nil")
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("expected %v, got %v", expected, result)
|
||||
}
|
||||
|
||||
for i, e := range expected {
|
||||
if result[i] != e {
|
||||
t.Fatalf("expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/scheduler"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/servicebus"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/sql"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/trafficmanager"
|
||||
mainStorage "github.com/Azure/azure-sdk-for-go/storage"
|
||||
|
@ -99,6 +100,8 @@ type ArmClient struct {
|
|||
serviceBusSubscriptionsClient servicebus.SubscriptionsClient
|
||||
|
||||
keyVaultClient keyvault.VaultsClient
|
||||
|
||||
sqlElasticPoolsClient sql.ElasticPoolsClient
|
||||
}
|
||||
|
||||
func withRequestLogging() autorest.SendDecorator {
|
||||
|
@ -458,6 +461,12 @@ func (c *Config) getArmClient() (*ArmClient, error) {
|
|||
kvc.Sender = autorest.CreateSender(withRequestLogging())
|
||||
client.keyVaultClient = kvc
|
||||
|
||||
sqlepc := sql.NewElasticPoolsClientWithBaseURI(endpoint, c.SubscriptionID)
|
||||
setUserAgent(&sqlepc.Client)
|
||||
sqlepc.Authorizer = spt
|
||||
sqlepc.Sender = autorest.CreateSender(withRequestLogging())
|
||||
client.sqlElasticPoolsClient = sqlepc
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package azurerm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccAzureRMSqlElasticPool_importBasic(t *testing.T) {
|
||||
resourceName := "azurerm_sql_elasticpool.test"
|
||||
|
||||
ri := acctest.RandInt()
|
||||
config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: config,
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -99,6 +99,7 @@ func Provider() terraform.ResourceProvider {
|
|||
"azurerm_servicebus_namespace": resourceArmServiceBusNamespace(),
|
||||
"azurerm_servicebus_subscription": resourceArmServiceBusSubscription(),
|
||||
"azurerm_servicebus_topic": resourceArmServiceBusTopic(),
|
||||
"azurerm_sql_elasticpool": resourceArmSqlElasticPool(),
|
||||
"azurerm_storage_account": resourceArmStorageAccount(),
|
||||
"azurerm_storage_blob": resourceArmStorageBlob(),
|
||||
"azurerm_storage_container": resourceArmStorageContainer(),
|
||||
|
|
|
@ -92,6 +92,11 @@ func resourceArmLoadBalancer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
|
||||
"private_ip_address": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
},
|
||||
}
|
||||
|
@ -172,7 +177,17 @@ func resourecArmLoadBalancerRead(d *schema.ResourceData, meta interface{}) error
|
|||
d.Set("resource_group_name", id.ResourceGroup)
|
||||
|
||||
if loadBalancer.LoadBalancerPropertiesFormat != nil && loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations != nil {
|
||||
d.Set("frontend_ip_configuration", flattenLoadBalancerFrontendIpConfiguration(loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations))
|
||||
ipconfigs := loadBalancer.LoadBalancerPropertiesFormat.FrontendIPConfigurations
|
||||
d.Set("frontend_ip_configuration", flattenLoadBalancerFrontendIpConfiguration(ipconfigs))
|
||||
|
||||
for _, config := range *ipconfigs {
|
||||
if config.FrontendIPConfigurationPropertiesFormat.PrivateIPAddress != nil {
|
||||
d.Set("private_ip_address", config.FrontendIPConfigurationPropertiesFormat.PrivateIPAddress)
|
||||
|
||||
// set the private IP address at most once
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flattenAndSetTags(d, loadBalancer.Tags)
|
||||
|
|
|
@ -158,6 +158,10 @@ func resourceArmSqlDatabaseCreate(d *schema.ResourceData, meta interface{}) erro
|
|||
command.RequestedServiceObjectiveID = azure.String(v.(string))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("elastic_pool_name"); ok {
|
||||
command.ElasticPoolName = azure.String(v.(string))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("requested_service_objective_name"); ok {
|
||||
command.RequestedServiceObjectiveName = azure.String(v.(string))
|
||||
}
|
||||
|
@ -216,6 +220,7 @@ func resourceArmSqlDatabaseRead(d *schema.ResourceData, meta interface{}) error
|
|||
d.Set("name", resp.Name)
|
||||
d.Set("creation_date", resp.CreationDate)
|
||||
d.Set("default_secondary_location", resp.DefaultSecondaryLocation)
|
||||
d.Set("elastic_pool_name", resp.ElasticPoolName)
|
||||
|
||||
flattenAndSetTags(d, resp.Tags)
|
||||
|
||||
|
|
|
@ -65,6 +65,26 @@ func TestAccAzureRMSqlDatabase_basic(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAzureRMSqlDatabase_elasticPool(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
config := fmt.Sprintf(testAccAzureRMSqlDatabase_elasticPool, ri, ri, ri, ri)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testCheckAzureRMSqlDatabaseDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: config,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testCheckAzureRMSqlDatabaseExists("azurerm_sql_database.test"),
|
||||
resource.TestCheckResourceAttr("azurerm_sql_database.test", "elastic_pool_name", fmt.Sprintf("acctestep%d", ri)),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAzureRMSqlDatabase_withTags(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
preConfig := fmt.Sprintf(testAccAzureRMSqlDatabase_withTags, ri, ri, ri)
|
||||
|
@ -163,6 +183,44 @@ func testCheckAzureRMSqlDatabaseDestroy(s *terraform.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var testAccAzureRMSqlDatabase_elasticPool = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
name = "acctestRG_%d"
|
||||
location = "West US"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_server" "test" {
|
||||
name = "acctestsqlserver%d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
version = "12.0"
|
||||
administrator_login = "mradministrator"
|
||||
administrator_login_password = "thisIsDog11"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_elasticpool" "test" {
|
||||
name = "acctestep%d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
server_name = "${azurerm_sql_server.test.name}"
|
||||
edition = "Basic"
|
||||
dtu = 50
|
||||
pool_size = 5000
|
||||
}
|
||||
|
||||
resource "azurerm_sql_database" "test" {
|
||||
name = "acctestdb%d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
server_name = "${azurerm_sql_server.test.name}"
|
||||
location = "West US"
|
||||
edition = "${azurerm_sql_elasticpool.test.edition}"
|
||||
collation = "SQL_Latin1_General_CP1_CI_AS"
|
||||
max_size_bytes = "1073741824"
|
||||
elastic_pool_name = "${azurerm_sql_elasticpool.test.name}"
|
||||
requested_service_objective_name = "ElasticPool"
|
||||
}
|
||||
`
|
||||
|
||||
var testAccAzureRMSqlDatabase_basic = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
name = "acctestRG_%d"
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
package azurerm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/sql"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func resourceArmSqlElasticPool() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceArmSqlElasticPoolCreate,
|
||||
Read: resourceArmSqlElasticPoolRead,
|
||||
Update: resourceArmSqlElasticPoolCreate,
|
||||
Delete: resourceArmSqlElasticPoolDelete,
|
||||
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"location": locationSchema(),
|
||||
|
||||
"resource_group_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"server_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"edition": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateSqlElasticPoolEdition(),
|
||||
},
|
||||
|
||||
"dtu": {
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"db_dtu_min": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"db_dtu_max": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"pool_size": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"creation_date": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ArmClient)
|
||||
elasticPoolsClient := client.sqlElasticPoolsClient
|
||||
|
||||
log.Printf("[INFO] preparing arguments for Azure ARM SQL ElasticPool creation.")
|
||||
|
||||
name := d.Get("name").(string)
|
||||
serverName := d.Get("server_name").(string)
|
||||
location := d.Get("location").(string)
|
||||
resGroup := d.Get("resource_group_name").(string)
|
||||
tags := d.Get("tags").(map[string]interface{})
|
||||
|
||||
elasticPool := sql.ElasticPool{
|
||||
Name: &name,
|
||||
Location: &location,
|
||||
ElasticPoolProperties: getArmSqlElasticPoolProperties(d),
|
||||
Tags: expandTags(tags),
|
||||
}
|
||||
|
||||
_, err := elasticPoolsClient.CreateOrUpdate(resGroup, serverName, name, elasticPool, make(chan struct{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
read, err := elasticPoolsClient.Get(resGroup, serverName, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if read.ID == nil {
|
||||
return fmt.Errorf("Cannot read SQL ElasticPool %s (resource group %s) ID", name, resGroup)
|
||||
}
|
||||
|
||||
d.SetId(*read.ID)
|
||||
|
||||
return resourceArmSqlElasticPoolRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceArmSqlElasticPoolRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ArmClient)
|
||||
elasticPoolsClient := client.sqlElasticPoolsClient
|
||||
|
||||
resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := elasticPoolsClient.Get(resGroup, serverName, name)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Error making Read request on Sql Elastic Pool %s: %s", name, err)
|
||||
}
|
||||
|
||||
d.Set("name", resp.Name)
|
||||
d.Set("resource_group_name", resGroup)
|
||||
d.Set("location", azureRMNormalizeLocation(*resp.Location))
|
||||
d.Set("server_name", serverName)
|
||||
|
||||
elasticPool := resp.ElasticPoolProperties
|
||||
|
||||
if elasticPool != nil {
|
||||
d.Set("edition", string(elasticPool.Edition))
|
||||
d.Set("dtu", int(*elasticPool.Dtu))
|
||||
d.Set("db_dtu_min", int(*elasticPool.DatabaseDtuMin))
|
||||
d.Set("db_dtu_max", int(*elasticPool.DatabaseDtuMax))
|
||||
d.Set("pool_size", int(*elasticPool.StorageMB))
|
||||
|
||||
if elasticPool.CreationDate != nil {
|
||||
d.Set("creation_date", elasticPool.CreationDate.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
flattenAndSetTags(d, resp.Tags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceArmSqlElasticPoolDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ArmClient)
|
||||
elasticPoolsClient := client.sqlElasticPoolsClient
|
||||
|
||||
resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = elasticPoolsClient.Delete(resGroup, serverName, name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getArmSqlElasticPoolProperties(d *schema.ResourceData) *sql.ElasticPoolProperties {
|
||||
edition := sql.ElasticPoolEditions(d.Get("edition").(string))
|
||||
dtu := int32(d.Get("dtu").(int))
|
||||
|
||||
props := &sql.ElasticPoolProperties{
|
||||
Edition: edition,
|
||||
Dtu: &dtu,
|
||||
}
|
||||
|
||||
if databaseDtuMin, ok := d.GetOk("db_dtu_min"); ok {
|
||||
databaseDtuMin := int32(databaseDtuMin.(int))
|
||||
props.DatabaseDtuMin = &databaseDtuMin
|
||||
}
|
||||
|
||||
if databaseDtuMax, ok := d.GetOk("db_dtu_max"); ok {
|
||||
databaseDtuMax := int32(databaseDtuMax.(int))
|
||||
props.DatabaseDtuMax = &databaseDtuMax
|
||||
}
|
||||
|
||||
if poolSize, ok := d.GetOk("pool_size"); ok {
|
||||
poolSize := int32(poolSize.(int))
|
||||
props.StorageMB = &poolSize
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
func parseArmSqlElasticPoolId(sqlElasticPoolId string) (string, string, string, error) {
|
||||
id, err := parseAzureResourceID(sqlElasticPoolId)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("[ERROR] Unable to parse SQL ElasticPool ID '%s': %+v", sqlElasticPoolId, err)
|
||||
}
|
||||
|
||||
return id.ResourceGroup, id.Path["servers"], id.Path["elasticPools"], nil
|
||||
}
|
||||
|
||||
func validateSqlElasticPoolEdition() schema.SchemaValidateFunc {
|
||||
return validation.StringInSlice([]string{
|
||||
string(sql.ElasticPoolEditionsBasic),
|
||||
string(sql.ElasticPoolEditionsStandard),
|
||||
string(sql.ElasticPoolEditionsPremium),
|
||||
}, false)
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package azurerm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccAzureRMSqlElasticPool_basic(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: config,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAzureRMSqlElasticPool_resizeDtu(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
preConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri)
|
||||
postConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_resizedDtu, ri)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: preConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"azurerm_sql_elasticpool.test", "dtu", "50"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"azurerm_sql_elasticpool.test", "pool_size", "5000"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: postConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"azurerm_sql_elasticpool.test", "dtu", "100"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"azurerm_sql_elasticpool.test", "pool_size", "10000"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testCheckAzureRMSqlElasticPoolExists(name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
ressource, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
resourceGroup, serverName, name, err := parseArmSqlElasticPoolId(ressource.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient
|
||||
|
||||
resp, err := conn.Get(resourceGroup, serverName, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Bad: Get on sqlElasticPoolsClient: %s", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("Bad: SQL Elastic Pool %q on server: %q (resource group: %q) does not exist", name, serverName, resourceGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testCheckAzureRMSqlElasticPoolDestroy(s *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "azurerm_sql_elasticpool" {
|
||||
continue
|
||||
}
|
||||
|
||||
name := rs.Primary.Attributes["name"]
|
||||
serverName := rs.Primary.Attributes["server_name"]
|
||||
resourceGroup := rs.Primary.Attributes["resource_group_name"]
|
||||
|
||||
resp, err := conn.Get(resourceGroup, serverName, name)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
return fmt.Errorf("SQL Elastic Pool still exists:\n%#v", resp.ElasticPoolProperties)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var testAccAzureRMSqlElasticPool_basic = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
name = "acctest-%[1]d"
|
||||
location = "West US"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_server" "test" {
|
||||
name = "acctest%[1]d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
version = "12.0"
|
||||
administrator_login = "4dm1n157r470r"
|
||||
administrator_login_password = "4-v3ry-53cr37-p455w0rd"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_elasticpool" "test" {
|
||||
name = "acctest-pool-%[1]d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
server_name = "${azurerm_sql_server.test.name}"
|
||||
edition = "Basic"
|
||||
dtu = 50
|
||||
pool_size = 5000
|
||||
}
|
||||
`
|
||||
|
||||
var testAccAzureRMSqlElasticPool_resizedDtu = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
name = "acctest-%[1]d"
|
||||
location = "West US"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_server" "test" {
|
||||
name = "acctest%[1]d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
version = "12.0"
|
||||
administrator_login = "4dm1n157r470r"
|
||||
administrator_login_password = "4-v3ry-53cr37-p455w0rd"
|
||||
}
|
||||
|
||||
resource "azurerm_sql_elasticpool" "test" {
|
||||
name = "acctest-pool-%[1]d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
location = "West US"
|
||||
server_name = "${azurerm_sql_server.test.name}"
|
||||
edition = "Basic"
|
||||
dtu = 100
|
||||
pool_size = 10000
|
||||
}
|
||||
`
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -155,20 +156,40 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{})
|
|||
if resp.Properties.Outputs != nil && len(*resp.Properties.Outputs) > 0 {
|
||||
outputs = make(map[string]string)
|
||||
for key, output := range *resp.Properties.Outputs {
|
||||
log.Printf("[DEBUG] Processing deployment output %s", key)
|
||||
outputMap := output.(map[string]interface{})
|
||||
outputValue, ok := outputMap["value"]
|
||||
if !ok {
|
||||
// No value
|
||||
log.Printf("[DEBUG] No value - skipping")
|
||||
continue
|
||||
}
|
||||
outputType, ok := outputMap["type"]
|
||||
if !ok {
|
||||
log.Printf("[DEBUG] No type - skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
outputs[key] = outputValue.(string)
|
||||
var outputValueString string
|
||||
switch strings.ToLower(outputType.(string)) {
|
||||
case "bool":
|
||||
outputValueString = strconv.FormatBool(outputValue.(bool))
|
||||
|
||||
case "string":
|
||||
outputValueString = outputValue.(string)
|
||||
|
||||
case "int":
|
||||
outputValueString = fmt.Sprint(outputValue)
|
||||
|
||||
default:
|
||||
log.Printf("[WARN] Ignoring output %s: Outputs of type %s are not currently supported in azurerm_template_deployment.",
|
||||
key, outputType)
|
||||
continue
|
||||
}
|
||||
outputs[key] = outputValueString
|
||||
}
|
||||
}
|
||||
|
||||
d.Set("outputs", outputs)
|
||||
|
||||
return nil
|
||||
return d.Set("outputs", outputs)
|
||||
}
|
||||
|
||||
func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
|
|
|
@ -68,6 +68,29 @@ func TestAccAzureRMTemplateDeployment_withParams(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAzureRMTemplateDeployment_withOutputs(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
config := fmt.Sprintf(testAccAzureRMTemplateDeployment_withOutputs, ri, ri, ri)
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testCheckAzureRMTemplateDeploymentDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: config,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testCheckAzureRMTemplateDeploymentExists("azurerm_template_deployment.test"),
|
||||
resource.TestCheckOutput("tfIntOutput", "-123"),
|
||||
resource.TestCheckOutput("tfStringOutput", "Standard_GRS"),
|
||||
resource.TestCheckOutput("tfFalseOutput", "false"),
|
||||
resource.TestCheckOutput("tfTrueOutput", "true"),
|
||||
resource.TestCheckResourceAttr("azurerm_template_deployment.test", "outputs.stringOutput", "Standard_GRS"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAzureRMTemplateDeployment_withError(t *testing.T) {
|
||||
ri := acctest.RandInt()
|
||||
config := fmt.Sprintf(testAccAzureRMTemplateDeployment_withError, ri, ri)
|
||||
|
@ -352,6 +375,126 @@ DEPLOY
|
|||
|
||||
`
|
||||
|
||||
var testAccAzureRMTemplateDeployment_withOutputs = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
name = "acctestRG-%d"
|
||||
location = "West US"
|
||||
}
|
||||
|
||||
output "tfStringOutput" {
|
||||
value = "${azurerm_template_deployment.test.outputs.stringOutput}"
|
||||
}
|
||||
|
||||
output "tfIntOutput" {
|
||||
value = "${azurerm_template_deployment.test.outputs.intOutput}"
|
||||
}
|
||||
|
||||
output "tfFalseOutput" {
|
||||
value = "${azurerm_template_deployment.test.outputs.falseOutput}"
|
||||
}
|
||||
|
||||
output "tfTrueOutput" {
|
||||
value = "${azurerm_template_deployment.test.outputs.trueOutput}"
|
||||
}
|
||||
|
||||
resource "azurerm_template_deployment" "test" {
|
||||
name = "acctesttemplate-%d"
|
||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
||||
template_body = <<DEPLOY
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"storageAccountType": {
|
||||
"type": "string",
|
||||
"defaultValue": "Standard_LRS",
|
||||
"allowedValues": [
|
||||
"Standard_LRS",
|
||||
"Standard_GRS",
|
||||
"Standard_ZRS"
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Storage Account type"
|
||||
}
|
||||
},
|
||||
"dnsLabelPrefix": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "DNS Label for the Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error."
|
||||
}
|
||||
},
|
||||
"intParameter": {
|
||||
"type": "int",
|
||||
"defaultValue": -123
|
||||
},
|
||||
"falseParameter": {
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
"trueParameter": {
|
||||
"type": "bool",
|
||||
"defaultValue": true
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"location": "[resourceGroup().location]",
|
||||
"storageAccountName": "[concat(uniquestring(resourceGroup().id), 'storage')]",
|
||||
"publicIPAddressName": "[concat('myPublicIp', uniquestring(resourceGroup().id))]",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"apiVersion": "2015-06-15"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"name": "[variables('storageAccountName')]",
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"accountType": "[parameters('storageAccountType')]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.Network/publicIPAddresses",
|
||||
"apiVersion": "[variables('apiVersion')]",
|
||||
"name": "[variables('publicIPAddressName')]",
|
||||
"location": "[variables('location')]",
|
||||
"properties": {
|
||||
"publicIPAllocationMethod": "[variables('publicIPAddressType')]",
|
||||
"dnsSettings": {
|
||||
"domainNameLabel": "[parameters('dnsLabelPrefix')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"stringOutput": {
|
||||
"type": "string",
|
||||
"value": "[parameters('storageAccountType')]"
|
||||
},
|
||||
"intOutput": {
|
||||
"type": "int",
|
||||
"value": "[parameters('intParameter')]"
|
||||
},
|
||||
"falseOutput": {
|
||||
"type": "bool",
|
||||
"value": "[parameters('falseParameter')]"
|
||||
},
|
||||
"trueOutput": {
|
||||
"type": "bool",
|
||||
"value": "[parameters('trueParameter')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
DEPLOY
|
||||
parameters {
|
||||
dnsLabelPrefix = "terraform-test-%d"
|
||||
storageAccountType = "Standard_GRS"
|
||||
}
|
||||
deployment_mode = "Incremental"
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
// StorageAccount name is too long, forces error
|
||||
var testAccAzureRMTemplateDeployment_withError = `
|
||||
resource "azurerm_resource_group" "test" {
|
||||
|
|
|
@ -2,9 +2,12 @@ package digitalocean
|
|||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/terraform/helper/logging"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -21,11 +24,31 @@ func (c *Config) Client() (*godo.Client, error) {
|
|||
|
||||
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, tokenSrc))
|
||||
|
||||
if logging.IsDebugOrHigher() {
|
||||
client.OnRequestCompleted(logRequestAndResponse)
|
||||
}
|
||||
|
||||
log.Printf("[INFO] DigitalOcean Client configured for URL: %s", client.BaseURL.String())
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func logRequestAndResponse(req *http.Request, resp *http.Response) {
|
||||
reqData, err := httputil.DumpRequest(req, true)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] "+logReqMsg, string(reqData))
|
||||
} else {
|
||||
log.Printf("[ERROR] DigitalOcean API Request error: %#v", err)
|
||||
}
|
||||
|
||||
respData, err := httputil.DumpResponse(resp, true)
|
||||
if err == nil {
|
||||
log.Printf("[DEBUG] "+logRespMsg, string(respData))
|
||||
} else {
|
||||
log.Printf("[ERROR] DigitalOcean API Response error: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// waitForAction waits for the action to finish using the resource.StateChangeConf.
|
||||
func waitForAction(client *godo.Client, action *godo.Action) error {
|
||||
var (
|
||||
|
@ -61,3 +84,13 @@ func waitForAction(client *godo.Client, action *godo.Action) error {
|
|||
}).WaitForState()
|
||||
return err
|
||||
}
|
||||
|
||||
const logReqMsg = `DigitalOcean API Request Details:
|
||||
---[ REQUEST ]---------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logRespMsg = `DigitalOcean API Response Details:
|
||||
---[ RESPONSE ]--------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
|
|
@ -260,10 +260,13 @@ func resourceDigitalOceanDropletRead(d *schema.ResourceData, meta interface{}) e
|
|||
return fmt.Errorf("Error retrieving droplet: %s", err)
|
||||
}
|
||||
|
||||
if droplet.Image.Slug != "" {
|
||||
d.Set("image", droplet.Image.Slug)
|
||||
} else {
|
||||
_, err = strconv.Atoi(d.Get("image").(string))
|
||||
if err == nil || droplet.Image.Slug == "" {
|
||||
// The image field is provided as an ID (number), or
|
||||
// the image bash no slug. In both cases we store it as an ID.
|
||||
d.Set("image", droplet.Image.ID)
|
||||
} else {
|
||||
d.Set("image", droplet.Image.Slug)
|
||||
}
|
||||
|
||||
d.Set("name", droplet.Name)
|
||||
|
|
|
@ -41,16 +41,31 @@ func TestAccDigitalOceanDroplet_Basic(t *testing.T) {
|
|||
resource.TestCheckResourceAttr(
|
||||
"digitalocean_droplet.foobar", "user_data", "foobar"),
|
||||
),
|
||||
Destroy: false,
|
||||
},
|
||||
{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
|
||||
PlanOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDigitalOceanDroplet_WithID(t *testing.T) {
|
||||
var droplet godo.Droplet
|
||||
rInt := acctest.RandInt()
|
||||
// TODO: not hardcode this as it will change over time
|
||||
centosID := 22995941
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckDigitalOceanDropletConfig_withID(centosID, rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
func TestAccDigitalOceanDroplet_withSSH(t *testing.T) {
|
||||
var droplet godo.Droplet
|
||||
rInt := acctest.RandInt()
|
||||
|
@ -504,6 +519,17 @@ resource "digitalocean_droplet" "foobar" {
|
|||
}`, rInt)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanDropletConfig_withID(imageID, rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_droplet" "foobar" {
|
||||
name = "foo-%d"
|
||||
size = "512mb"
|
||||
image = "%d"
|
||||
region = "nyc3"
|
||||
user_data = "foobar"
|
||||
}`, rInt, imageID)
|
||||
}
|
||||
|
||||
func testAccCheckDigitalOceanDropletConfig_withSSH(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "digitalocean_ssh_key" "foobar" {
|
||||
|
|
|
@ -33,6 +33,9 @@ func resourceDMERecord() *schema.Resource {
|
|||
"value": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
StateFunc: func(value interface{}) string {
|
||||
return strings.ToLower(value.(string))
|
||||
},
|
||||
},
|
||||
"ttl": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
|
|
|
@ -318,17 +318,18 @@ func resourceServiceV1() *schema.Resource {
|
|||
Required: true,
|
||||
Description: "A name to refer to this Cache Setting",
|
||||
},
|
||||
"cache_condition": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of a condition to check if this Cache Setting applies",
|
||||
},
|
||||
"action": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Action to take",
|
||||
},
|
||||
// optional
|
||||
"cache_condition": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "Name of a condition to check if this Cache Setting applies",
|
||||
},
|
||||
"stale_ttl": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
|
@ -776,12 +777,13 @@ func resourceServiceV1() *schema.Resource {
|
|||
Required: true,
|
||||
Description: "Unique name to refer to this Request Setting",
|
||||
},
|
||||
// Optional fields
|
||||
"request_condition": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of a request condition to apply.",
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "Name of a request condition to apply. If there is no condition this setting will always be applied.",
|
||||
},
|
||||
// Optional fields
|
||||
"max_stale_age": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
|
|
|
@ -2,18 +2,21 @@ package google
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccDataSourceGoogleNetwork(t *testing.T) {
|
||||
networkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: TestAccDataSourceGoogleNetworkConfig,
|
||||
Config: testAccDataSourceGoogleNetworkConfig(networkName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccDataSourceGoogleNetworkCheck("data.google_compute_network.my_network", "google_compute_network.foobar"),
|
||||
),
|
||||
|
@ -57,12 +60,14 @@ func testAccDataSourceGoogleNetworkCheck(data_source_name string, resource_name
|
|||
}
|
||||
}
|
||||
|
||||
var TestAccDataSourceGoogleNetworkConfig = `
|
||||
func testAccDataSourceGoogleNetworkConfig(name string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_network" "foobar" {
|
||||
name = "network-test"
|
||||
name = "%s"
|
||||
description = "my-description"
|
||||
}
|
||||
|
||||
data "google_compute_network" "my_network" {
|
||||
name = "${google_compute_network.foobar.name}"
|
||||
}`
|
||||
}`, name)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
)
|
||||
|
||||
func TestAccDnsManagedZone_importBasic(t *testing.T) {
|
||||
resourceName := "google_dns_managed_zone.foobar"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDnsManagedZoneDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccDnsManagedZone_basic,
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -62,8 +62,10 @@ func Provider() terraform.ResourceProvider {
|
|||
"google_bigquery_dataset": resourceBigQueryDataset(),
|
||||
"google_compute_autoscaler": resourceComputeAutoscaler(),
|
||||
"google_compute_address": resourceComputeAddress(),
|
||||
"google_compute_backend_bucket": resourceComputeBackendBucket(),
|
||||
"google_compute_backend_service": resourceComputeBackendService(),
|
||||
"google_compute_disk": resourceComputeDisk(),
|
||||
"google_compute_snapshot": resourceComputeSnapshot(),
|
||||
"google_compute_firewall": resourceComputeFirewall(),
|
||||
"google_compute_forwarding_rule": resourceComputeForwardingRule(),
|
||||
"google_compute_global_address": resourceComputeGlobalAddress(),
|
||||
|
|
|
@ -333,7 +333,7 @@ func resourceComputeAutoscalerUpdate(d *schema.ResourceData, meta interface{}) e
|
|||
}
|
||||
|
||||
op, err := config.clientCompute.Autoscalers.Patch(
|
||||
project, zone, d.Id(), scaler).Do()
|
||||
project, zone, scaler).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating Autoscaler: %s", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func resourceComputeBackendBucket() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceComputeBackendBucketCreate,
|
||||
Read: resourceComputeBackendBucketRead,
|
||||
Update: resourceComputeBackendBucketUpdate,
|
||||
Delete: resourceComputeBackendBucketDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`
|
||||
if !regexp.MustCompile(re).MatchString(value) {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q (%q) doesn't match regexp %q", k, value, re))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"bucket_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"description": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"enable_cdn": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
|
||||
"project": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"self_link": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceComputeBackendBucketCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
bucket := compute.BackendBucket{
|
||||
Name: d.Get("name").(string),
|
||||
BucketName: d.Get("bucket_name").(string),
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("description"); ok {
|
||||
bucket.Description = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("enable_cdn"); ok {
|
||||
bucket.EnableCdn = v.(bool)
|
||||
}
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Creating new Backend Bucket: %#v", bucket)
|
||||
op, err := config.clientCompute.BackendBuckets.Insert(
|
||||
project, &bucket).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating backend bucket: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Waiting for new backend bucket, operation: %#v", op)
|
||||
|
||||
// Store the ID now
|
||||
d.SetId(bucket.Name)
|
||||
|
||||
// Wait for the operation to complete
|
||||
waitErr := computeOperationWaitGlobal(config, op, project, "Creating Backend Bucket")
|
||||
if waitErr != nil {
|
||||
// The resource didn't actually create
|
||||
d.SetId("")
|
||||
return waitErr
|
||||
}
|
||||
|
||||
return resourceComputeBackendBucketRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceComputeBackendBucketRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket, err := config.clientCompute.BackendBuckets.Get(
|
||||
project, d.Id()).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||
// The resource doesn't exist anymore
|
||||
log.Printf("[WARN] Removing Backend Bucket %q because it's gone", d.Get("name").(string))
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error reading bucket: %s", err)
|
||||
}
|
||||
|
||||
d.Set("bucket_name", bucket.BucketName)
|
||||
d.Set("description", bucket.Description)
|
||||
d.Set("enable_cdn", bucket.EnableCdn)
|
||||
d.Set("self_link", bucket.SelfLink)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeBackendBucketUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket := compute.BackendBucket{
|
||||
Name: d.Get("name").(string),
|
||||
BucketName: d.Get("bucket_name").(string),
|
||||
}
|
||||
|
||||
// Optional things
|
||||
if v, ok := d.GetOk("description"); ok {
|
||||
bucket.Description = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("enable_cdn"); ok {
|
||||
bucket.EnableCdn = v.(bool)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Updating existing Backend Bucket %q: %#v", d.Id(), bucket)
|
||||
op, err := config.clientCompute.BackendBuckets.Update(
|
||||
project, d.Id(), &bucket).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating backend bucket: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(bucket.Name)
|
||||
|
||||
err = computeOperationWaitGlobal(config, op, project, "Updating Backend Bucket")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceComputeBackendBucketRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceComputeBackendBucketDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Deleting backend bucket %s", d.Id())
|
||||
op, err := config.clientCompute.BackendBuckets.Delete(
|
||||
project, d.Id()).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting backend bucket: %s", err)
|
||||
}
|
||||
|
||||
err = computeOperationWaitGlobal(config, op, project, "Deleting Backend Bucket")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
func TestAccComputeBackendBucket_basic(t *testing.T) {
|
||||
backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
var svc compute.BackendBucket
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeBackendBucketDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeBackendBucket_basic(backendName, storageName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeBackendBucketExists(
|
||||
"google_compute_backend_bucket.foobar", &svc),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if svc.BucketName != storageName {
|
||||
t.Errorf("Expected BucketName to be %q, got %q", storageName, svc.BucketName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccComputeBackendBucket_basicModified(t *testing.T) {
|
||||
backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
secondStorageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
var svc compute.BackendBucket
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeBackendBucketDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeBackendBucket_basic(backendName, storageName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeBackendBucketExists(
|
||||
"google_compute_backend_bucket.foobar", &svc),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccComputeBackendBucket_basicModified(
|
||||
backendName, storageName, secondStorageName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeBackendBucketExists(
|
||||
"google_compute_backend_bucket.foobar", &svc),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if svc.BucketName != secondStorageName {
|
||||
t.Errorf("Expected BucketName to be %q, got %q", secondStorageName, svc.BucketName)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeBackendBucketDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "google_compute_backend_bucket" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := config.clientCompute.BackendBuckets.Get(
|
||||
config.Project, rs.Primary.ID).Do()
|
||||
if err == nil {
|
||||
return fmt.Errorf("Backend bucket %s still exists", rs.Primary.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckComputeBackendBucketExists(n string, svc *compute.BackendBucket) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.clientCompute.BackendBuckets.Get(
|
||||
config.Project, rs.Primary.ID).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Backend bucket %s not found", rs.Primary.ID)
|
||||
}
|
||||
|
||||
*svc = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccComputeBackendBucket_withCdnEnabled(t *testing.T) {
|
||||
backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
var svc compute.BackendBucket
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeBackendBucketDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeBackendBucket_withCdnEnabled(
|
||||
backendName, storageName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeBackendBucketExists(
|
||||
"google_compute_backend_bucket.foobar", &svc),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if svc.EnableCdn != true {
|
||||
t.Errorf("Expected EnableCdn == true, got %t", svc.EnableCdn)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccComputeBackendBucket_basic(backendName, storageName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_backend_bucket" "foobar" {
|
||||
name = "%s"
|
||||
bucket_name = "${google_storage_bucket.bucket_one.name}"
|
||||
}
|
||||
|
||||
resource "google_storage_bucket" "bucket_one" {
|
||||
name = "%s"
|
||||
location = "EU"
|
||||
}
|
||||
`, backendName, storageName)
|
||||
}
|
||||
|
||||
func testAccComputeBackendBucket_basicModified(backendName, bucketOne, bucketTwo string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_backend_bucket" "foobar" {
|
||||
name = "%s"
|
||||
bucket_name = "${google_storage_bucket.bucket_two.name}"
|
||||
}
|
||||
|
||||
resource "google_storage_bucket" "bucket_one" {
|
||||
name = "%s"
|
||||
location = "EU"
|
||||
}
|
||||
|
||||
resource "google_storage_bucket" "bucket_two" {
|
||||
name = "%s"
|
||||
location = "EU"
|
||||
}
|
||||
`, backendName, bucketOne, bucketTwo)
|
||||
}
|
||||
|
||||
func testAccComputeBackendBucket_withCdnEnabled(backendName, storageName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_backend_bucket" "foobar" {
|
||||
name = "%s"
|
||||
bucket_name = "${google_storage_bucket.bucket.name}"
|
||||
enable_cdn = true
|
||||
}
|
||||
|
||||
resource "google_storage_bucket" "bucket" {
|
||||
name = "%s"
|
||||
location = "EU"
|
||||
}
|
||||
`, backendName, storageName)
|
||||
}
|
|
@ -200,11 +200,15 @@ func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{
|
|||
|
||||
log.Printf("[DEBUG] Waiting for new backend service, operation: %#v", op)
|
||||
|
||||
// Store the ID now
|
||||
d.SetId(service.Name)
|
||||
|
||||
err = computeOperationWaitGlobal(config, op, project, "Creating Backend Service")
|
||||
if err != nil {
|
||||
return err
|
||||
// Wait for the operation to complete
|
||||
waitErr := computeOperationWaitGlobal(config, op, project, "Creating Backend Service")
|
||||
if waitErr != nil {
|
||||
// The resource didn't actually create
|
||||
d.SetId("")
|
||||
return waitErr
|
||||
}
|
||||
|
||||
return resourceComputeBackendServiceRead(d, meta)
|
||||
|
|
|
@ -125,7 +125,7 @@ func testAccCheckComputeBackendServiceDestroy(s *terraform.State) error {
|
|||
_, err := config.clientCompute.BackendServices.Get(
|
||||
config.Project, rs.Primary.ID).Do()
|
||||
if err == nil {
|
||||
return fmt.Errorf("Backend service still exists")
|
||||
return fmt.Errorf("Backend service %s still exists", rs.Primary.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi
|
|||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Backend service not found")
|
||||
return fmt.Errorf("Backend service %s not found", rs.Primary.ID)
|
||||
}
|
||||
|
||||
*svc = *found
|
||||
|
|
|
@ -429,6 +429,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
}
|
||||
|
||||
hasSource := false
|
||||
// Load up the disk for this disk if specified
|
||||
if v, ok := d.GetOk(prefix + ".disk"); ok {
|
||||
diskName := v.(string)
|
||||
|
@ -441,6 +442,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
|
||||
disk.Source = diskData.SelfLink
|
||||
hasSource = true
|
||||
} else {
|
||||
// Create a new disk
|
||||
disk.InitializeParams = &compute.AttachedDiskInitializeParams{}
|
||||
|
@ -453,7 +455,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
|
||||
// Load up the image for this disk if specified
|
||||
if v, ok := d.GetOk(prefix + ".image"); ok {
|
||||
if v, ok := d.GetOk(prefix + ".image"); ok && !hasSource {
|
||||
imageName := v.(string)
|
||||
|
||||
imageUrl, err := resolveImage(config, imageName)
|
||||
|
@ -464,9 +466,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
|
||||
disk.InitializeParams.SourceImage = imageUrl
|
||||
} else if ok && hasSource {
|
||||
return fmt.Errorf("Cannot specify disk image when referencing an existing disk")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(prefix + ".type"); ok {
|
||||
if v, ok := d.GetOk(prefix + ".type"); ok && !hasSource {
|
||||
diskTypeName := v.(string)
|
||||
diskType, err := readDiskType(config, zone, diskTypeName)
|
||||
if err != nil {
|
||||
|
@ -476,11 +480,15 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
|
|||
}
|
||||
|
||||
disk.InitializeParams.DiskType = diskType.SelfLink
|
||||
} else if ok && hasSource {
|
||||
return fmt.Errorf("Cannot specify disk type when referencing an existing disk")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(prefix + ".size"); ok {
|
||||
if v, ok := d.GetOk(prefix + ".size"); ok && !hasSource {
|
||||
diskSizeGb := v.(int)
|
||||
disk.InitializeParams.DiskSizeGb = int64(diskSizeGb)
|
||||
} else if ok && hasSource {
|
||||
return fmt.Errorf("Cannot specify disk size when referencing an existing disk")
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(prefix + ".device_name"); ok {
|
||||
|
|
|
@ -197,6 +197,12 @@ func resourceComputeInstanceTemplate() *schema.Resource {
|
|||
Computed: true,
|
||||
},
|
||||
|
||||
"network_ip": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"subnetwork": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -462,7 +468,9 @@ func buildNetworks(d *schema.ResourceData, meta interface{}) ([]*compute.Network
|
|||
var iface compute.NetworkInterface
|
||||
iface.Network = networkLink
|
||||
iface.Subnetwork = subnetworkLink
|
||||
|
||||
if v, ok := d.GetOk(prefix + ".network_ip"); ok {
|
||||
iface.NetworkIP = v.(string)
|
||||
}
|
||||
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
|
||||
iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount)
|
||||
for j := 0; j < accessConfigsCount; j++ {
|
||||
|
@ -648,6 +656,9 @@ func flattenNetworkInterfaces(networkInterfaces []*compute.NetworkInterface) ([]
|
|||
networkUrl := strings.Split(networkInterface.Network, "/")
|
||||
networkInterfaceMap["network"] = networkUrl[len(networkUrl)-1]
|
||||
}
|
||||
if networkInterface.NetworkIP != "" {
|
||||
networkInterfaceMap["network_ip"] = networkInterface.NetworkIP
|
||||
}
|
||||
if networkInterface.Subnetwork != "" {
|
||||
subnetworkUrl := strings.Split(networkInterface.Subnetwork, "/")
|
||||
networkInterfaceMap["subnetwork"] = subnetworkUrl[len(subnetworkUrl)-1]
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestAccComputeInstanceTemplate_basic(t *testing.T) {
|
|||
"google_compute_instance_template.foobar", &instanceTemplate),
|
||||
testAccCheckComputeInstanceTemplateTag(&instanceTemplate, "foo"),
|
||||
testAccCheckComputeInstanceTemplateMetadata(&instanceTemplate, "foo", "bar"),
|
||||
testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160803", true, true),
|
||||
testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "projects/debian-cloud/global/images/debian-8-jessie-v20160803", true, true),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -54,6 +54,29 @@ func TestAccComputeInstanceTemplate_IP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccComputeInstanceTemplate_networkIP(t *testing.T) {
|
||||
var instanceTemplate compute.InstanceTemplate
|
||||
networkIP := "10.128.0.2"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeInstanceTemplateDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeInstanceTemplate_networkIP(networkIP),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeInstanceTemplateExists(
|
||||
"google_compute_instance_template.foobar", &instanceTemplate),
|
||||
testAccCheckComputeInstanceTemplateNetwork(&instanceTemplate),
|
||||
testAccCheckComputeInstanceTemplateNetworkIP(
|
||||
"google_compute_instance_template.foobar", networkIP, &instanceTemplate),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccComputeInstanceTemplate_disks(t *testing.T) {
|
||||
var instanceTemplate compute.InstanceTemplate
|
||||
|
||||
|
@ -67,7 +90,7 @@ func TestAccComputeInstanceTemplate_disks(t *testing.T) {
|
|||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeInstanceTemplateExists(
|
||||
"google_compute_instance_template.foobar", &instanceTemplate),
|
||||
testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160803", true, true),
|
||||
testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "projects/debian-cloud/global/images/debian-8-jessie-v20160803", true, true),
|
||||
testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "terraform-test-foobar", false, false),
|
||||
),
|
||||
},
|
||||
|
@ -335,6 +358,17 @@ func testAccCheckComputeInstanceTemplateStartupScript(instanceTemplate *compute.
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeInstanceTemplateNetworkIP(n, networkIP string, instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
ip := instanceTemplate.Properties.NetworkInterfaces[0].NetworkIP
|
||||
err := resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", ip)(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", networkIP)(s)
|
||||
}
|
||||
}
|
||||
|
||||
var testAccComputeInstanceTemplate_basic = fmt.Sprintf(`
|
||||
resource "google_compute_instance_template" "foobar" {
|
||||
name = "instancet-test-%s"
|
||||
|
@ -392,6 +426,28 @@ resource "google_compute_instance_template" "foobar" {
|
|||
}
|
||||
}`, acctest.RandString(10), acctest.RandString(10))
|
||||
|
||||
func testAccComputeInstanceTemplate_networkIP(networkIP string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_instance_template" "foobar" {
|
||||
name = "instancet-test-%s"
|
||||
machine_type = "n1-standard-1"
|
||||
tags = ["foo", "bar"]
|
||||
|
||||
disk {
|
||||
source_image = "debian-8-jessie-v20160803"
|
||||
}
|
||||
|
||||
network_interface {
|
||||
network = "default"
|
||||
network_ip = "%s"
|
||||
}
|
||||
|
||||
metadata {
|
||||
foo = "bar"
|
||||
}
|
||||
}`, acctest.RandString(10), networkIP)
|
||||
}
|
||||
|
||||
var testAccComputeInstanceTemplate_disks = fmt.Sprintf(`
|
||||
resource "google_compute_disk" "foobar" {
|
||||
name = "instancet-test-%s"
|
||||
|
|
|
@ -2,8 +2,10 @@ package google
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"google.golang.org/api/compute/v1"
|
||||
|
@ -11,7 +13,16 @@ import (
|
|||
|
||||
// Add two key value pairs
|
||||
func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terrafom-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -19,13 +30,13 @@ func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -34,7 +45,16 @@ func TestAccComputeProjectMetadata_basic(t *testing.T) {
|
|||
|
||||
// Add three key value pairs, then replace one and modify a second
|
||||
func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terrafom-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -42,26 +62,26 @@ func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify0_metadata,
|
||||
Config: testAccComputeProject_modify0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "genghis_khan", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "smiling"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "genghis_khan", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "happy", "smiling"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 3),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_modify1_metadata,
|
||||
Config: testAccComputeProject_modify1_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "paris", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "happy", "laughing"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 3),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "paris", "french bread"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "happy", "laughing"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 3),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -70,7 +90,16 @@ func TestAccComputeProjectMetadata_modify_1(t *testing.T) {
|
|||
|
||||
// Add two key value pairs, and replace both
|
||||
func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
var project compute.Project
|
||||
projectID := "terraform-test-" + acctest.RandString(10)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
@ -78,24 +107,24 @@ func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
|||
CheckDestroy: testAccCheckComputeProjectMetadataDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic0_metadata,
|
||||
Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccComputeProject_basic1_metadata,
|
||||
Config: testAccComputeProject_basic1_metadata(projectID, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeProjectExists(
|
||||
"google_compute_project_metadata.fizzbuzz", &project),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "kiwi", "papaya"),
|
||||
testAccCheckComputeProjectMetadataContains(&project, "finches", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(&project, 2),
|
||||
"google_compute_project_metadata.fizzbuzz", projectID, &project),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "kiwi", "papaya"),
|
||||
testAccCheckComputeProjectMetadataContains(projectID, "finches", "darwinism"),
|
||||
testAccCheckComputeProjectMetadataSize(projectID, 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -105,15 +134,21 @@ func TestAccComputeProjectMetadata_modify_2(t *testing.T) {
|
|||
func testAccCheckComputeProjectMetadataDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
if err == nil && len(project.CommonInstanceMetadata.Items) > 0 {
|
||||
return fmt.Errorf("Error, metadata items still exist")
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "google_compute_project_metadata" {
|
||||
continue
|
||||
}
|
||||
|
||||
project, err := config.clientCompute.Projects.Get(rs.Primary.ID).Do()
|
||||
if err == nil && len(project.CommonInstanceMetadata.Items) > 0 {
|
||||
return fmt.Errorf("Error, metadata items still exist in %s", rs.Primary.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectExists(n string, project *compute.Project) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectExists(n, projectID string, project *compute.Project) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
|
@ -126,8 +161,7 @@ func testAccCheckComputeProjectExists(n string, project *compute.Project) resour
|
|||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.clientCompute.Projects.Get(
|
||||
config.Project).Do()
|
||||
found, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -142,10 +176,10 @@ func testAccCheckComputeProjectExists(n string, project *compute.Project) resour
|
|||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataContains(project *compute.Project, key string, value string) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectMetadataContains(projectID, key, value string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
@ -161,14 +195,14 @@ func testAccCheckComputeProjectMetadataContains(project *compute.Project, key st
|
|||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error, key %s not present", key)
|
||||
return fmt.Errorf("Error, key %s not present in %s", key, project.SelfLink)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckComputeProjectMetadataSize(project *compute.Project, size int) resource.TestCheckFunc {
|
||||
func testAccCheckComputeProjectMetadataSize(projectID string, size int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
project, err := config.clientCompute.Projects.Get(config.Project).Do()
|
||||
project, err := config.clientCompute.Projects.Get(projectID).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err)
|
||||
}
|
||||
|
@ -182,36 +216,100 @@ func testAccCheckComputeProjectMetadataSize(project *compute.Project, size int)
|
|||
}
|
||||
}
|
||||
|
||||
const testAccComputeProject_basic0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
banana = "orange"
|
||||
sofa = "darwinism"
|
||||
}
|
||||
}`
|
||||
func testAccComputeProject_basic0_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
const testAccComputeProject_basic1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
kiwi = "papaya"
|
||||
finches = "darwinism"
|
||||
}
|
||||
}`
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
const testAccComputeProject_modify0_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
genghis_khan = "french bread"
|
||||
happy = "smiling"
|
||||
}
|
||||
}`
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
banana = "orange"
|
||||
sofa = "darwinism"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_basic1_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
const testAccComputeProject_modify1_metadata = `
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
metadata {
|
||||
paper = "pen"
|
||||
paris = "french bread"
|
||||
happy = "laughing"
|
||||
}
|
||||
}`
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
kiwi = "papaya"
|
||||
finches = "darwinism"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_modify0_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
paper = "pen"
|
||||
genghis_khan = "french bread"
|
||||
happy = "smiling"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
||||
func testAccComputeProject_modify1_metadata(projectID, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "project" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_services" "services" {
|
||||
project = "${google_project.project.project_id}"
|
||||
services = ["compute-component.googleapis.com"]
|
||||
}
|
||||
|
||||
resource "google_compute_project_metadata" "fizzbuzz" {
|
||||
project = "${google_project.project.project_id}"
|
||||
metadata {
|
||||
paper = "pen"
|
||||
paris = "french bread"
|
||||
happy = "laughing"
|
||||
}
|
||||
depends_on = ["google_project_services.services"]
|
||||
}`, projectID, name, org, billing)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func resourceComputeSnapshot() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceComputeSnapshotCreate,
|
||||
Read: resourceComputeSnapshotRead,
|
||||
Delete: resourceComputeSnapshotDelete,
|
||||
Exists: resourceComputeSnapshotExists,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"zone": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"snapshot_encryption_key_raw": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
|
||||
"snapshot_encryption_key_sha256": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"source_disk_encryption_key_raw": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
|
||||
"source_disk_encryption_key_sha256": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"source_disk": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"source_disk_link": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"project": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"self_link": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceComputeSnapshotCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build the snapshot parameter
|
||||
snapshot := &compute.Snapshot{
|
||||
Name: d.Get("name").(string),
|
||||
}
|
||||
|
||||
source_disk := d.Get("source_disk").(string)
|
||||
|
||||
if v, ok := d.GetOk("snapshot_encryption_key_raw"); ok {
|
||||
snapshot.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{}
|
||||
snapshot.SnapshotEncryptionKey.RawKey = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("source_disk_encryption_key_raw"); ok {
|
||||
snapshot.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{}
|
||||
snapshot.SourceDiskEncryptionKey.RawKey = v.(string)
|
||||
}
|
||||
|
||||
op, err := config.clientCompute.Disks.CreateSnapshot(
|
||||
project, d.Get("zone").(string), source_disk, snapshot).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating snapshot: %s", err)
|
||||
}
|
||||
|
||||
// It probably maybe worked, so store the ID now
|
||||
d.SetId(snapshot.Name)
|
||||
|
||||
err = computeOperationWaitZone(config, op, project, d.Get("zone").(string), "Creating Snapshot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resourceComputeSnapshotRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceComputeSnapshotRead(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
snapshot, err := config.clientCompute.Snapshots.Get(
|
||||
project, d.Id()).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||
log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
|
||||
// The resource doesn't exist anymore
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error reading snapshot: %s", err)
|
||||
}
|
||||
|
||||
d.Set("self_link", snapshot.SelfLink)
|
||||
d.Set("source_disk_link", snapshot.SourceDisk)
|
||||
d.Set("name", snapshot.Name)
|
||||
|
||||
if snapshot.SnapshotEncryptionKey != nil && snapshot.SnapshotEncryptionKey.Sha256 != "" {
|
||||
d.Set("snapshot_encryption_key_sha256", snapshot.SnapshotEncryptionKey.Sha256)
|
||||
}
|
||||
|
||||
if snapshot.SourceDiskEncryptionKey != nil && snapshot.SourceDiskEncryptionKey.Sha256 != "" {
|
||||
d.Set("source_disk_encryption_key_sha256", snapshot.SourceDiskEncryptionKey.Sha256)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeSnapshotDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the snapshot
|
||||
op, err := config.clientCompute.Snapshots.Delete(
|
||||
project, d.Id()).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||
log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
|
||||
// The resource doesn't exist anymore
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Error deleting snapshot: %s", err)
|
||||
}
|
||||
|
||||
err = computeOperationWaitGlobal(config, op, project, "Deleting Snapshot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceComputeSnapshotExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
||||
config := meta.(*Config)
|
||||
|
||||
project, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = config.clientCompute.Snapshots.Get(
|
||||
project, d.Id()).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||
log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string))
|
||||
// The resource doesn't exist anymore
|
||||
d.SetId("")
|
||||
|
||||
return false, err
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
func TestAccComputeSnapshot_basic(t *testing.T) {
|
||||
snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
var snapshot compute.Snapshot
|
||||
diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeSnapshotDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeSnapshot_basic(snapshotName, diskName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeSnapshotExists(
|
||||
"google_compute_snapshot.foobar", &snapshot),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccComputeSnapshot_encryption(t *testing.T) {
|
||||
snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
var snapshot compute.Snapshot
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckComputeSnapshotDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccComputeSnapshot_encryption(snapshotName, diskName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckComputeSnapshotExists(
|
||||
"google_compute_snapshot.foobar", &snapshot),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckComputeSnapshotDestroy(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "google_compute_snapshot" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := config.clientCompute.Snapshots.Get(
|
||||
config.Project, rs.Primary.ID).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||
return nil
|
||||
} else if ok {
|
||||
return fmt.Errorf("Error while requesting Google Cloud Plateform: http code error : %d, http message error: %s", gerr.Code, gerr.Message)
|
||||
}
|
||||
return fmt.Errorf("Error while requesting Google Cloud Plateform")
|
||||
}
|
||||
return fmt.Errorf("Snapshot still exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckComputeSnapshotExists(n string, snapshot *compute.Snapshot) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
||||
found, err := config.clientCompute.Snapshots.Get(
|
||||
config.Project, rs.Primary.ID).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found.Name != rs.Primary.ID {
|
||||
return fmt.Errorf("Snapshot %s not found", n)
|
||||
}
|
||||
|
||||
attr := rs.Primary.Attributes["snapshot_encryption_key_sha256"]
|
||||
if found.SnapshotEncryptionKey != nil && found.SnapshotEncryptionKey.Sha256 != attr {
|
||||
return fmt.Errorf("Snapshot %s has mismatched encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SnapshotEncryptionKey.Sha256)
|
||||
} else if found.SnapshotEncryptionKey == nil && attr != "" {
|
||||
return fmt.Errorf("Snapshot %s has mismatched encryption key.\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SnapshotEncryptionKey)
|
||||
}
|
||||
|
||||
attr = rs.Primary.Attributes["source_disk_encryption_key_sha256"]
|
||||
if found.SourceDiskEncryptionKey != nil && found.SourceDiskEncryptionKey.Sha256 != attr {
|
||||
return fmt.Errorf("Snapshot %s has mismatched source disk encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SourceDiskEncryptionKey.Sha256)
|
||||
} else if found.SourceDiskEncryptionKey == nil && attr != "" {
|
||||
return fmt.Errorf("Snapshot %s has mismatched source disk encryption key.\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SourceDiskEncryptionKey)
|
||||
}
|
||||
|
||||
attr = rs.Primary.Attributes["source_disk_link"]
|
||||
if found.SourceDisk != attr {
|
||||
return fmt.Errorf("Snapshot %s has mismatched source disk link.\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SourceDisk)
|
||||
}
|
||||
|
||||
foundDisk, errDisk := config.clientCompute.Disks.Get(
|
||||
config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["source_disk"]).Do()
|
||||
if errDisk != nil {
|
||||
return errDisk
|
||||
}
|
||||
if foundDisk.SelfLink != attr {
|
||||
return fmt.Errorf("Snapshot %s has mismatched source disk\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, foundDisk.SelfLink)
|
||||
}
|
||||
|
||||
attr = rs.Primary.Attributes["self_link"]
|
||||
if found.SelfLink != attr {
|
||||
return fmt.Errorf("Snapshot %s has mismatched self link.\nTF State: %+v.\nGCP State: %+v",
|
||||
n, attr, found.SelfLink)
|
||||
}
|
||||
|
||||
*snapshot = *found
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccComputeSnapshot_basic(snapshotName string, diskName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_disk" "foobar" {
|
||||
name = "%s"
|
||||
image = "debian-8-jessie-v20160921"
|
||||
size = 10
|
||||
type = "pd-ssd"
|
||||
zone = "us-central1-a"
|
||||
}
|
||||
|
||||
resource "google_compute_snapshot" "foobar" {
|
||||
name = "%s"
|
||||
source_disk = "${google_compute_disk.foobar.name}"
|
||||
zone = "us-central1-a"
|
||||
}`, diskName, snapshotName)
|
||||
}
|
||||
|
||||
func testAccComputeSnapshot_encryption(snapshotName string, diskName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_compute_disk" "foobar" {
|
||||
name = "%s"
|
||||
image = "debian-8-jessie-v20160921"
|
||||
size = 10
|
||||
type = "pd-ssd"
|
||||
zone = "us-central1-a"
|
||||
disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
|
||||
}
|
||||
resource "google_compute_snapshot" "foobar" {
|
||||
name = "%s"
|
||||
source_disk = "${google_compute_disk.foobar.name}"
|
||||
zone = "us-central1-a"
|
||||
source_disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
|
||||
snapshot_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
|
||||
}`, diskName, snapshotName)
|
||||
}
|
|
@ -403,7 +403,7 @@ var testAccContainerCluster_withVersion = fmt.Sprintf(`
|
|||
resource "google_container_cluster" "with_version" {
|
||||
name = "cluster-test-%s"
|
||||
zone = "us-central1-a"
|
||||
node_version = "1.6.0"
|
||||
node_version = "1.6.1"
|
||||
initial_node_count = 1
|
||||
|
||||
master_auth {
|
||||
|
|
|
@ -14,7 +14,9 @@ func resourceDnsManagedZone() *schema.Resource {
|
|||
Create: resourceDnsManagedZoneCreate,
|
||||
Read: resourceDnsManagedZoneRead,
|
||||
Delete: resourceDnsManagedZoneDelete,
|
||||
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
Schema: map[string]*schema.Schema{
|
||||
"dns_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
@ -109,6 +111,9 @@ func resourceDnsManagedZoneRead(d *schema.ResourceData, meta interface{}) error
|
|||
}
|
||||
|
||||
d.Set("name_servers", zone.NameServers)
|
||||
d.Set("name", zone.Name)
|
||||
d.Set("dns_name", zone.DnsName)
|
||||
d.Set("description", zone.Description)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -31,6 +31,14 @@ func resourceGoogleProjectServices() *schema.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
// These services can only be enabled as a side-effect of enabling other services,
|
||||
// so don't bother storing them in the config or using them for diffing.
|
||||
var ignore = map[string]struct{}{
|
||||
"containeranalysis.googleapis.com": struct{}{},
|
||||
"dataproc-control.googleapis.com": struct{}{},
|
||||
"source.googleapis.com": struct{}{},
|
||||
}
|
||||
|
||||
func resourceGoogleProjectServicesCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
pid := d.Get("project").(string)
|
||||
|
@ -155,12 +163,19 @@ func getConfigServices(d *schema.ResourceData) (services []string) {
|
|||
func getApiServices(pid string, config *Config) ([]string, error) {
|
||||
apiServices := make([]string, 0)
|
||||
// Get services from the API
|
||||
svcResp, err := config.clientServiceMan.Services.List().ConsumerId("project:" + pid).Do()
|
||||
if err != nil {
|
||||
return apiServices, err
|
||||
}
|
||||
for _, v := range svcResp.Services {
|
||||
apiServices = append(apiServices, v.ServiceName)
|
||||
token := ""
|
||||
for paginate := true; paginate; {
|
||||
svcResp, err := config.clientServiceMan.Services.List().ConsumerId("project:" + pid).PageToken(token).Do()
|
||||
if err != nil {
|
||||
return apiServices, err
|
||||
}
|
||||
for _, v := range svcResp.Services {
|
||||
if _, ok := ignore[v.ServiceName]; !ok {
|
||||
apiServices = append(apiServices, v.ServiceName)
|
||||
}
|
||||
}
|
||||
token = svcResp.NextPageToken
|
||||
paginate = token != ""
|
||||
}
|
||||
return apiServices, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
@ -123,6 +124,103 @@ func TestAccGoogleProjectServices_authoritative2(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// Test that services that can't be enabled on their own (such as dataproc-control.googleapis.com)
|
||||
// don't end up causing diffs when they are enabled as a side-effect of a different service's
|
||||
// enablement.
|
||||
func TestAccGoogleProjectServices_ignoreUnenablableServices(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
pid := "terraform-" + acctest.RandString(10)
|
||||
services := []string{
|
||||
"dataproc.googleapis.com",
|
||||
// The following services are enabled as a side-effect of dataproc's enablement
|
||||
"storage-component.googleapis.com",
|
||||
"deploymentmanager.googleapis.com",
|
||||
"replicapool.googleapis.com",
|
||||
"replicapoolupdater.googleapis.com",
|
||||
"resourceviews.googleapis.com",
|
||||
"compute-component.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
"containerregistry.googleapis.com",
|
||||
"storage-api.googleapis.com",
|
||||
"pubsub.googleapis.com",
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccGoogleProjectAssociateServicesBasic_withBilling(services, pid, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testProjectServicesMatch(services, pid),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccGoogleProjectServices_manyServices(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
"GOOGLE_BILLING_ACCOUNT",
|
||||
}...,
|
||||
)
|
||||
|
||||
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
|
||||
pid := "terraform-" + acctest.RandString(10)
|
||||
services := []string{
|
||||
"bigquery-json.googleapis.com",
|
||||
"cloudbuild.googleapis.com",
|
||||
"cloudfunctions.googleapis.com",
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"cloudtrace.googleapis.com",
|
||||
"compute-component.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
"containerregistry.googleapis.com",
|
||||
"dataflow.googleapis.com",
|
||||
"dataproc.googleapis.com",
|
||||
"deploymentmanager.googleapis.com",
|
||||
"dns.googleapis.com",
|
||||
"endpoints.googleapis.com",
|
||||
"iam.googleapis.com",
|
||||
"logging.googleapis.com",
|
||||
"ml.googleapis.com",
|
||||
"monitoring.googleapis.com",
|
||||
"pubsub.googleapis.com",
|
||||
"replicapool.googleapis.com",
|
||||
"replicapoolupdater.googleapis.com",
|
||||
"resourceviews.googleapis.com",
|
||||
"runtimeconfig.googleapis.com",
|
||||
"servicecontrol.googleapis.com",
|
||||
"servicemanagement.googleapis.com",
|
||||
"sourcerepo.googleapis.com",
|
||||
"spanner.googleapis.com",
|
||||
"storage-api.googleapis.com",
|
||||
"storage-component.googleapis.com",
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccGoogleProjectAssociateServicesBasic_withBilling(services, pid, pname, org, billingId),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testProjectServicesMatch(services, pid),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccGoogleProjectAssociateServicesBasic(services []string, pid, name, org string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
|
@ -137,6 +235,21 @@ resource "google_project_services" "acceptance" {
|
|||
`, pid, name, org, testStringsToString(services))
|
||||
}
|
||||
|
||||
func testAccGoogleProjectAssociateServicesBasic_withBilling(services []string, pid, name, org, billing string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
billing_account = "%s"
|
||||
}
|
||||
resource "google_project_services" "acceptance" {
|
||||
project = "${google_project.acceptance.project_id}"
|
||||
services = [%s]
|
||||
}
|
||||
`, pid, name, org, billing, testStringsToString(services))
|
||||
}
|
||||
|
||||
func testProjectServicesMatch(services []string, pid string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
config := testAccProvider.Meta().(*Config)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -25,12 +27,15 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"heroku_addon": resourceHerokuAddon(),
|
||||
"heroku_app": resourceHerokuApp(),
|
||||
"heroku_cert": resourceHerokuCert(),
|
||||
"heroku_domain": resourceHerokuDomain(),
|
||||
"heroku_drain": resourceHerokuDrain(),
|
||||
"heroku_space": resourceHerokuSpace(),
|
||||
"heroku_addon": resourceHerokuAddon(),
|
||||
"heroku_app": resourceHerokuApp(),
|
||||
"heroku_app_feature": resourceHerokuAppFeature(),
|
||||
"heroku_cert": resourceHerokuCert(),
|
||||
"heroku_domain": resourceHerokuDomain(),
|
||||
"heroku_drain": resourceHerokuDrain(),
|
||||
"heroku_pipeline": resourceHerokuPipeline(),
|
||||
"heroku_pipeline_coupling": resourceHerokuPipelineCoupling(),
|
||||
"heroku_space": resourceHerokuSpace(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
@ -46,3 +51,12 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
log.Println("[INFO] Initializing Heroku client")
|
||||
return config.Client()
|
||||
}
|
||||
|
||||
func buildCompositeID(a, b string) string {
|
||||
return fmt.Sprintf("%s:%s", a, b)
|
||||
}
|
||||
|
||||
func parseCompositeID(id string) (string, string) {
|
||||
parts := strings.SplitN(id, ":", 2)
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
|
|
@ -232,15 +232,8 @@ func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
d.SetId(a.Name)
|
||||
log.Printf("[INFO] App ID: %s", d.Id())
|
||||
|
||||
if v, ok := d.GetOk("config_vars"); ok {
|
||||
err = updateConfigVars(d.Id(), client, nil, v.([]interface{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("buildpacks"); ok {
|
||||
err = updateBuildpacks(d.Id(), client, v.([]interface{}))
|
||||
if err := performAppPostCreateTasks(d, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceHerokuAppRead(d, meta)
|
||||
|
@ -305,11 +298,8 @@ func resourceHerokuOrgAppCreate(d *schema.ResourceData, meta interface{}) error
|
|||
d.SetId(a.Name)
|
||||
log.Printf("[INFO] App ID: %s", d.Id())
|
||||
|
||||
if v, ok := d.GetOk("config_vars"); ok {
|
||||
err = updateConfigVars(d.Id(), client, nil, v.([]interface{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := performAppPostCreateTasks(d, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceHerokuAppRead(d, meta)
|
||||
|
@ -534,3 +524,20 @@ func updateBuildpacks(id string, client *heroku.Service, v []interface{}) error
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performAppPostCreateTasks performs post-create tasks common to both org and non-org apps.
|
||||
func performAppPostCreateTasks(d *schema.ResourceData, client *heroku.Service) error {
|
||||
if v, ok := d.GetOk("config_vars"); ok {
|
||||
if err := updateConfigVars(d.Id(), client, nil, v.([]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("buildpacks"); ok {
|
||||
if err := updateBuildpacks(d.Id(), client, v.([]interface{})); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceHerokuAppFeature() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuAppFeatureCreate,
|
||||
Update: resourceHerokuAppFeatureUpdate,
|
||||
Read: resourceHerokuAppFeatureRead,
|
||||
Delete: resourceHerokuAppFeatureDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"app": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"enabled": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app, id := parseCompositeID(d.Id())
|
||||
|
||||
feature, err := client.AppFeatureInfo(context.TODO(), app, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("app", app)
|
||||
d.Set("name", feature.Name)
|
||||
d.Set("enabled", feature.Enabled)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app := d.Get("app").(string)
|
||||
featureName := d.Get("name").(string)
|
||||
enabled := d.Get("enabled").(bool)
|
||||
|
||||
opts := heroku.AppFeatureUpdateOpts{Enabled: enabled}
|
||||
|
||||
log.Printf("[DEBUG] Feature set configuration: %#v, %#v", featureName, opts)
|
||||
|
||||
feature, err := client.AppFeatureUpdate(context.TODO(), app, featureName, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(buildCompositeID(app, feature.ID))
|
||||
|
||||
return resourceHerokuAppFeatureRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
if d.HasChange("enabled") {
|
||||
return resourceHerokuAppFeatureCreate(d, meta)
|
||||
}
|
||||
|
||||
return resourceHerokuAppFeatureRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuAppFeatureDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
app, id := parseCompositeID(d.Id())
|
||||
featureName := d.Get("name").(string)
|
||||
|
||||
log.Printf("[INFO] Deleting app feature %s (%s) for app %s", featureName, id, app)
|
||||
opts := heroku.AppFeatureUpdateOpts{Enabled: false}
|
||||
_, err := client.AppFeatureUpdate(context.TODO(), app, id, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuAppFeature(t *testing.T) {
|
||||
var feature heroku.AppFeatureInfoResult
|
||||
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuFeatureDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckHerokuFeature_basic(appName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
|
||||
testAccCheckHerokuFeatureEnabled(&feature, true),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app_feature.runtime_metrics", "enabled", "true",
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckHerokuFeature_disabled(appName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuFeatureExists("heroku_app_feature.runtime_metrics", &feature),
|
||||
testAccCheckHerokuFeatureEnabled(&feature, false),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app_feature.runtime_metrics", "enabled", "false",
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "heroku_app_feature" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.AppFeatureInfo(context.TODO(), rs.Primary.Attributes["app"], rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Feature still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureExists(n string, feature *heroku.AppFeatureInfoResult) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No feature ID is set")
|
||||
}
|
||||
|
||||
app, id := parseCompositeID(rs.Primary.ID)
|
||||
if app != rs.Primary.Attributes["app"] {
|
||||
return fmt.Errorf("Bad app: %s", app)
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
foundFeature, err := client.AppFeatureInfo(context.TODO(), app, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundFeature.ID != id {
|
||||
return fmt.Errorf("Feature not found")
|
||||
}
|
||||
|
||||
*feature = *foundFeature
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeatureEnabled(feature *heroku.AppFeatureInfoResult, enabled bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if feature.Enabled != enabled {
|
||||
return fmt.Errorf("Bad enabled: %v", feature.Enabled)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeature_basic(appName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_app" "example" {
|
||||
name = "%s"
|
||||
region = "us"
|
||||
}
|
||||
|
||||
resource "heroku_app_feature" "runtime_metrics" {
|
||||
app = "${heroku_app.example.name}"
|
||||
name = "log-runtime-metrics"
|
||||
}
|
||||
`, appName)
|
||||
}
|
||||
|
||||
func testAccCheckHerokuFeature_disabled(appName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_app" "example" {
|
||||
name = "%s"
|
||||
region = "us"
|
||||
}
|
||||
|
||||
resource "heroku_app_feature" "runtime_metrics" {
|
||||
app = "${heroku_app.example.name}"
|
||||
name = "log-runtime-metrics"
|
||||
enabled = false
|
||||
}
|
||||
`, appName)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceHerokuPipeline() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuPipelineCreate,
|
||||
Update: resourceHerokuPipelineUpdate,
|
||||
Read: resourceHerokuPipelineRead,
|
||||
Delete: resourceHerokuPipelineDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
opts := heroku.PipelineCreateOpts{
|
||||
Name: d.Get("name").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Pipeline create configuration: %#v", opts)
|
||||
|
||||
p, err := client.PipelineCreate(context.TODO(), opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating pipeline: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(p.ID)
|
||||
d.Set("name", p.Name)
|
||||
|
||||
log.Printf("[INFO] Pipeline ID: %s", d.Id())
|
||||
|
||||
return resourceHerokuPipelineUpdate(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
if d.HasChange("name") {
|
||||
name := d.Get("name").(string)
|
||||
opts := heroku.PipelineUpdateOpts{
|
||||
Name: &name,
|
||||
}
|
||||
|
||||
_, err := client.PipelineUpdate(context.TODO(), d.Id(), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return resourceHerokuPipelineRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
log.Printf("[INFO] Deleting pipeline: %s", d.Id())
|
||||
|
||||
_, err := client.PipelineDelete(context.TODO(), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting pipeline: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
p, err := client.PipelineInfo(context.TODO(), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving pipeline: %s", err)
|
||||
}
|
||||
|
||||
d.Set("name", p.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceHerokuPipelineCoupling() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuPipelineCouplingCreate,
|
||||
Read: resourceHerokuPipelineCouplingRead,
|
||||
Delete: resourceHerokuPipelineCouplingDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"app": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"pipeline": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateUUID,
|
||||
},
|
||||
"stage": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validatePipelineStageName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineCouplingCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
opts := heroku.PipelineCouplingCreateOpts{
|
||||
App: d.Get("app").(string),
|
||||
Pipeline: d.Get("pipeline").(string),
|
||||
Stage: d.Get("stage").(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] PipelineCoupling create configuration: %#v", opts)
|
||||
|
||||
p, err := client.PipelineCouplingCreate(context.TODO(), opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating pipeline: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(p.ID)
|
||||
|
||||
log.Printf("[INFO] PipelineCoupling ID: %s", d.Id())
|
||||
|
||||
return resourceHerokuPipelineCouplingRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineCouplingDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
log.Printf("[INFO] Deleting pipeline: %s", d.Id())
|
||||
|
||||
_, err := client.PipelineCouplingDelete(context.TODO(), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting pipeline: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHerokuPipelineCouplingRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
p, err := client.PipelineCouplingInfo(context.TODO(), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error retrieving pipeline: %s", err)
|
||||
}
|
||||
|
||||
d.Set("app", p.App)
|
||||
d.Set("pipeline", p.Pipeline)
|
||||
d.Set("stage", p.Stage)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuPipelineCoupling_Basic(t *testing.T) {
|
||||
var coupling heroku.PipelineCouplingInfoResult
|
||||
|
||||
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
|
||||
pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
|
||||
stageName := "development"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuPipelineCouplingDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckHerokuPipelineCouplingConfig_basic(appName, pipelineName, stageName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuPipelineCouplingExists("heroku_pipeline_coupling.default", &coupling),
|
||||
testAccCheckHerokuPipelineCouplingAttributes(
|
||||
&coupling,
|
||||
"heroku_pipeline.default",
|
||||
stageName,
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineCouplingConfig_basic(appName, pipelineName, stageName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_app" "default" {
|
||||
name = "%s"
|
||||
region = "us"
|
||||
}
|
||||
|
||||
resource "heroku_pipeline" "default" {
|
||||
name = "%s"
|
||||
}
|
||||
|
||||
resource "heroku_pipeline_coupling" "default" {
|
||||
app = "${heroku_app.default.id}"
|
||||
pipeline = "${heroku_pipeline.default.id}"
|
||||
stage = "%s"
|
||||
}
|
||||
`, appName, pipelineName, stageName)
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineCouplingExists(n string, pipeline *heroku.PipelineCouplingInfoResult) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No coupling ID set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
foundPipelineCoupling, err := client.PipelineCouplingInfo(context.TODO(), rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundPipelineCoupling.ID != rs.Primary.ID {
|
||||
return fmt.Errorf("PipelineCoupling not found: %s != %s", foundPipelineCoupling.ID, rs.Primary.ID)
|
||||
}
|
||||
|
||||
*pipeline = *foundPipelineCoupling
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineCouplingAttributes(coupling *heroku.PipelineCouplingInfoResult, pipelineResource, stageName string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
pipeline, ok := s.RootModule().Resources[pipelineResource]
|
||||
if !ok {
|
||||
return fmt.Errorf("Pipeline not found: %s", pipelineResource)
|
||||
}
|
||||
|
||||
if coupling.Pipeline.ID != pipeline.Primary.ID {
|
||||
return fmt.Errorf("Bad pipeline ID: %v != %v", coupling.Pipeline.ID, pipeline.Primary.ID)
|
||||
}
|
||||
if coupling.Stage != stageName {
|
||||
return fmt.Errorf("Bad stage: %s", coupling.Stage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineCouplingDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "heroku_pipeline_coupling" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.PipelineCouplingInfo(context.TODO(), rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("PipelineCoupling still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuPipeline_Basic(t *testing.T) {
|
||||
var pipeline heroku.PipelineInfoResult
|
||||
pipelineName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
|
||||
pipelineName2 := fmt.Sprintf("%s-2", pipelineName)
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuPipelineDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccCheckHerokuPipelineConfig_basic(pipelineName),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuPipelineExists("heroku_pipeline.foobar", &pipeline),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_pipeline.foobar", "name", pipelineName),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccCheckHerokuPipelineConfig_basic(pipelineName2),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_pipeline.foobar", "name", pipelineName2),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineConfig_basic(pipelineName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "heroku_pipeline" "foobar" {
|
||||
name = "%s"
|
||||
}
|
||||
`, pipelineName)
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineExists(n string, pipeline *heroku.PipelineInfoResult) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No pipeline name set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
foundPipeline, err := client.PipelineInfo(context.TODO(), rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundPipeline.ID != rs.Primary.ID {
|
||||
return fmt.Errorf("Pipeline not found")
|
||||
}
|
||||
|
||||
*pipeline = *foundPipeline
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuPipelineDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*heroku.Service)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "heroku_pipeline" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.PipelineInfo(context.TODO(), rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Pipeline still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2,9 +2,12 @@ package heroku
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
heroku "github.com/cyberdelia/heroku-go/v3"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
|
@ -56,23 +59,32 @@ func resourceHerokuSpaceCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
d.SetId(space.ID)
|
||||
log.Printf("[INFO] Space ID: %s", d.Id())
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
setSpaceAttributes(d, (*heroku.Space)(space))
|
||||
return nil
|
||||
// Wait for the Space to be allocated
|
||||
log.Printf("[DEBUG] Waiting for Space (%s) to be allocated", d.Id())
|
||||
stateConf := &resource.StateChangeConf{
|
||||
Pending: []string{"allocating"},
|
||||
Target: []string{"allocated"},
|
||||
Refresh: SpaceStateRefreshFunc(client, d.Id()),
|
||||
Timeout: 20 * time.Minute,
|
||||
}
|
||||
|
||||
if _, err := stateConf.WaitForState(); err != nil {
|
||||
return fmt.Errorf("Error waiting for Space (%s) to become available: %s", d.Id(), err)
|
||||
}
|
||||
|
||||
return resourceHerokuSpaceRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuSpaceRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Service)
|
||||
|
||||
space, err := client.SpaceInfo(context.TODO(), d.Id())
|
||||
spaceRaw, _, err := SpaceStateRefreshFunc(client, d.Id())()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
space := spaceRaw.(*heroku.Space)
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
setSpaceAttributes(d, (*heroku.Space)(space))
|
||||
setSpaceAttributes(d, space)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -115,3 +127,18 @@ func resourceHerokuSpaceDelete(d *schema.ResourceData, meta interface{}) error {
|
|||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpaceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||
// a Space.
|
||||
func SpaceStateRefreshFunc(client *heroku.Service, id string) resource.StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
space, err := client.SpaceInfo(context.TODO(), id)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// The type conversion here can be dropped when the vendored version of
|
||||
// heroku-go is updated.
|
||||
return (*heroku.Space)(space), space.State, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/satori/uuid"
|
||||
)
|
||||
|
||||
func validatePipelineStageName(v interface{}, k string) (ws []string, errors []error) {
|
||||
validPipelineStageNames := []string{
|
||||
"review",
|
||||
"development",
|
||||
"staging",
|
||||
"production",
|
||||
}
|
||||
|
||||
for _, s := range validPipelineStageNames {
|
||||
if v == s {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := fmt.Errorf(
|
||||
"%s is an invalid pipeline stage, must be one of [%s]",
|
||||
v,
|
||||
strings.Join(validPipelineStageNames, ", "),
|
||||
)
|
||||
errors = append(errors, err)
|
||||
return
|
||||
}
|
||||
|
||||
func validateUUID(v interface{}, k string) (ws []string, errors []error) {
|
||||
if _, err := uuid.FromString(v.(string)); err != nil {
|
||||
errors = append(errors, fmt.Errorf("%q is an invalid UUID: %s", k, err))
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package heroku
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPipelineStage(t *testing.T) {
|
||||
valid := []string{
|
||||
"review",
|
||||
"development",
|
||||
"staging",
|
||||
"production",
|
||||
}
|
||||
for _, v := range valid {
|
||||
_, errors := validatePipelineStageName(v, "stage")
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("%q should be a valid stage: %q", v, errors)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []string{
|
||||
"foobarbaz",
|
||||
"another-stage",
|
||||
"",
|
||||
}
|
||||
for _, v := range invalid {
|
||||
_, errors := validatePipelineStageName(v, "stage")
|
||||
if len(errors) == 0 {
|
||||
t.Fatalf("%q should be an invalid stage", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUUID(t *testing.T) {
|
||||
valid := []string{
|
||||
"4812ccbc-2a2e-4c6c-bae4-a3d04ed51c0e",
|
||||
}
|
||||
for _, v := range valid {
|
||||
_, errors := validateUUID(v, "id")
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("%q should be a valid UUID: %q", v, errors)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []string{
|
||||
"foobarbaz",
|
||||
"my-app-name",
|
||||
}
|
||||
for _, v := range invalid {
|
||||
_, errors := validateUUID(v, "id")
|
||||
if len(errors) == 0 {
|
||||
t.Fatalf("%q should be an invalid UUID", v)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,24 @@ func Provider() terraform.ResourceProvider {
|
|||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_REGION", ""),
|
||||
Description: "Region of the target Nomad agent.",
|
||||
},
|
||||
"ca_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CACERT", ""),
|
||||
Description: "A path to a PEM-encoded certificate authority used to verify the remote agent's certificate.",
|
||||
},
|
||||
"cert_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CLIENT_CERT", ""),
|
||||
Description: "A path to a PEM-encoded certificate provided to the remote agent; requires use of key_file.",
|
||||
},
|
||||
"key_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("NOMAD_CLIENT_KEY", ""),
|
||||
Description: "A path to a PEM-encoded private key, required if cert_file is specified.",
|
||||
},
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
|
@ -38,6 +56,9 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
config := api.DefaultConfig()
|
||||
config.Address = d.Get("address").(string)
|
||||
config.Region = d.Get("region").(string)
|
||||
config.TLSConfig.CACert = d.Get("ca_file").(string)
|
||||
config.TLSConfig.ClientCert = d.Get("cert_file").(string)
|
||||
config.TLSConfig.ClientKey = d.Get("key_file").(string)
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
|
|
|
@ -207,15 +207,17 @@ func testResourceJob_checkExists(s *terraform.State) error {
|
|||
func testResourceJob_checkDestroy(jobID string) r.TestCheckFunc {
|
||||
return func(*terraform.State) error {
|
||||
client := testProvider.Meta().(*api.Client)
|
||||
_, _, err := client.Jobs().Info(jobID, nil)
|
||||
if err != nil && strings.Contains(err.Error(), "404") {
|
||||
job, _, err := client.Jobs().Info(jobID, nil)
|
||||
// This should likely never happen, due to how nomad caches jobs
|
||||
if err != nil && strings.Contains(err.Error(), "404") || job == nil {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
err = errors.New("not destroyed")
|
||||
|
||||
if job.Status != "dead" {
|
||||
return fmt.Errorf("Job %q has not been stopped. Status: %s", jobID, job.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,9 +286,12 @@ func testResourceJob_updateCheck(s *terraform.State) error {
|
|||
|
||||
{
|
||||
// Verify foo doesn't exist
|
||||
_, _, err := client.Jobs().Info("foo", nil)
|
||||
if err == nil {
|
||||
return errors.New("reading foo success")
|
||||
job, _, err := client.Jobs().Info("foo", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading %q job: %s", "foo", err)
|
||||
}
|
||||
if job.Status != "dead" {
|
||||
return fmt.Errorf("%q job is not dead. Status: %q", "foo", job.Status)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -122,6 +122,12 @@ func resourcePostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{})
|
|||
b := bytes.NewBufferString("CREATE DATABASE ")
|
||||
fmt.Fprint(b, pq.QuoteIdentifier(dbName))
|
||||
|
||||
//needed in order to set the owner of the db if the connection user is not a superuser
|
||||
err = grantRoleMembership(conn, d.Get(dbOwnerAttr).(string), c.username)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("Error granting role membership on database %s: {{err}}", dbName), err)
|
||||
}
|
||||
|
||||
// Handle each option individually and stream results into the query
|
||||
// buffer.
|
||||
|
||||
|
@ -464,3 +470,18 @@ func doSetDBIsTemplate(conn *sql.DB, dbName string, isTemplate bool) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func grantRoleMembership(conn *sql.DB, dbOwner string, connUsername string) error {
|
||||
if dbOwner != "" && dbOwner != connUsername {
|
||||
query := fmt.Sprintf("GRANT %s TO %s", pq.QuoteIdentifier(dbOwner), pq.QuoteIdentifier(connUsername))
|
||||
_, err := conn.Query(query)
|
||||
if err != nil {
|
||||
// is already member or role
|
||||
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||
return nil
|
||||
}
|
||||
return errwrap.Wrapf("Error granting membership: {{err}}", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestAccDataSourceImage_basic(t *testing.T) {
|
|||
Config: testAccDataSourceProfitBricksImage_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("data.profitbricks_image.img", "location", "us/las"),
|
||||
resource.TestCheckResourceAttr("data.profitbricks_image.img", "name", "Ubuntu-16.04-LTS-server-2017-02-01"),
|
||||
resource.TestCheckResourceAttr("data.profitbricks_image.img", "name", "Ubuntu-16.04-LTS-server-2017-05-01"),
|
||||
resource.TestCheckResourceAttr("data.profitbricks_image.img", "type", "HDD"),
|
||||
),
|
||||
},
|
||||
|
|
|
@ -254,34 +254,38 @@ func resourceProfitBricksServerCreate(d *schema.ResourceData, meta interface{})
|
|||
var sshkey_path []interface{}
|
||||
var image, licenceType, availabilityZone string
|
||||
|
||||
if !IsValidUUID(rawMap["image_name"].(string)) {
|
||||
if rawMap["image_name"] != nil {
|
||||
image = getImageId(d.Get("datacenter_id").(string), rawMap["image_name"].(string), rawMap["disk_type"].(string))
|
||||
if image == "" {
|
||||
dc := profitbricks.GetDatacenter(d.Get("datacenter_id").(string))
|
||||
return fmt.Errorf("Image '%s' doesn't exist. in location %s", rawMap["image_name"], dc.Properties.Location)
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
image = rawMap["image_name"].(string)
|
||||
}
|
||||
|
||||
if rawMap["licence_type"] != nil {
|
||||
licenceType = rawMap["licence_type"].(string)
|
||||
}
|
||||
|
||||
if rawMap["image_password"] != nil {
|
||||
imagePassword = rawMap["image_password"].(string)
|
||||
}
|
||||
if rawMap["ssh_key_path"] != nil {
|
||||
sshkey_path = rawMap["ssh_key_path"].([]interface{})
|
||||
}
|
||||
if rawMap["image_name"] != nil {
|
||||
|
||||
image_name := rawMap["image_name"].(string)
|
||||
if !IsValidUUID(image_name) {
|
||||
if imagePassword == "" && len(sshkey_path) == 0 {
|
||||
return fmt.Errorf("Either 'image_password' or 'ssh_key_path' must be provided.")
|
||||
return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
|
||||
}
|
||||
image = getImageId(d.Get("datacenter_id").(string), image_name, rawMap["disk_type"].(string))
|
||||
} else {
|
||||
img := profitbricks.GetImage(image_name)
|
||||
if img.StatusCode > 299 {
|
||||
return fmt.Errorf("Error fetching image: %s", img.Response)
|
||||
}
|
||||
if img.Properties.Public == true {
|
||||
if imagePassword == "" && len(sshkey_path) == 0 {
|
||||
return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
|
||||
}
|
||||
image = image_name
|
||||
} else {
|
||||
image = image_name
|
||||
}
|
||||
}
|
||||
|
||||
if rawMap["licence_type"] != nil {
|
||||
licenceType = rawMap["licence_type"].(string)
|
||||
}
|
||||
|
||||
var publicKeys []string
|
||||
if len(sshkey_path) != 0 {
|
||||
for _, path := range sshkey_path {
|
||||
|
|
|
@ -77,12 +77,6 @@ func resourceProfitBricksVolumeCreate(d *schema.ResourceData, meta interface{})
|
|||
ssh_keypath = d.Get("ssh_key_path").([]interface{})
|
||||
image_name := d.Get("image_name").(string)
|
||||
|
||||
if image_name != "" {
|
||||
if imagePassword == "" && len(ssh_keypath) == 0 {
|
||||
return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
|
||||
}
|
||||
}
|
||||
|
||||
licenceType := d.Get("licence_type").(string)
|
||||
|
||||
if image_name == "" && licenceType == "" {
|
||||
|
@ -102,10 +96,26 @@ func resourceProfitBricksVolumeCreate(d *schema.ResourceData, meta interface{})
|
|||
}
|
||||
|
||||
var image string
|
||||
if !IsValidUUID(image_name) {
|
||||
image = getImageId(d.Get("datacenter_id").(string), image_name, d.Get("disk_type").(string))
|
||||
} else {
|
||||
image = image_name
|
||||
if image_name != "" {
|
||||
if !IsValidUUID(image_name) {
|
||||
if imagePassword == "" && len(ssh_keypath) == 0 {
|
||||
return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
|
||||
}
|
||||
image = getImageId(d.Get("datacenter_id").(string), image_name, d.Get("disk_type").(string))
|
||||
} else {
|
||||
img := profitbricks.GetImage(image_name)
|
||||
if img.StatusCode > 299 {
|
||||
return fmt.Errorf("Error fetching image: %s", img.Response)
|
||||
}
|
||||
if img.Properties.Public == true {
|
||||
if imagePassword == "" && len(ssh_keypath) == 0 {
|
||||
return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.")
|
||||
}
|
||||
image = image_name
|
||||
} else {
|
||||
image = image_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
volume := profitbricks.Volume{
|
||||
|
|
|
@ -99,3 +99,59 @@ resource "test_resource" "foo" {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TestDataSource_dataSourceCountGrandChild tests that a grandchild data source
|
||||
// that is based off of count works, ie: dependency chain foo -> bar -> baz.
|
||||
// This was failing because CountBoundaryTransformer is being run during apply
|
||||
// instead of plan, which meant that it wasn't firing after data sources were
|
||||
// potentially changing state and causing diff/interpolation issues.
|
||||
//
|
||||
// This happens after the initial apply, after state is saved.
|
||||
func TestDataSource_dataSourceCountGrandChild(t *testing.T) {
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: func(s *terraform.State) error {
|
||||
return nil
|
||||
},
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: dataSourceCountGrandChildConfig,
|
||||
},
|
||||
{
|
||||
Config: dataSourceCountGrandChildConfig,
|
||||
Check: func(s *terraform.State) error {
|
||||
for _, v := range []string{"foo", "bar", "baz"} {
|
||||
count := 0
|
||||
for k := range s.RootModule().Resources {
|
||||
if strings.HasPrefix(k, fmt.Sprintf("data.test_data_source.%s.", v)) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if count != 2 {
|
||||
return fmt.Errorf("bad count for data.test_data_source.%s: %d", v, count)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const dataSourceCountGrandChildConfig = `
|
||||
data "test_data_source" "foo" {
|
||||
count = 2
|
||||
input = "one"
|
||||
}
|
||||
|
||||
data "test_data_source" "bar" {
|
||||
count = "${length(data.test_data_source.foo.*.id)}"
|
||||
input = "${data.test_data_source.foo.*.output[count.index]}"
|
||||
}
|
||||
|
||||
data "test_data_source" "baz" {
|
||||
count = "${length(data.test_data_source.bar.*.id)}"
|
||||
input = "${data.test_data_source.bar.*.output[count.index]}"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -42,6 +42,12 @@ func Provider() terraform.ResourceProvider {
|
|||
Required: true,
|
||||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"TRITON_KEY_ID", "SDC_KEY_ID"}, ""),
|
||||
},
|
||||
|
||||
"insecure_skip_tls_verify": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("TRITON_SKIP_TLS_VERIFY", ""),
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
|
@ -56,10 +62,11 @@ func Provider() terraform.ResourceProvider {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
Account string
|
||||
KeyMaterial string
|
||||
KeyID string
|
||||
URL string
|
||||
Account string
|
||||
KeyMaterial string
|
||||
KeyID string
|
||||
URL string
|
||||
InsecureSkipTLSVerify bool
|
||||
}
|
||||
|
||||
func (c Config) validate() error {
|
||||
|
@ -98,6 +105,10 @@ func (c Config) getTritonClient() (*triton.Client, error) {
|
|||
return nil, errwrap.Wrapf("Error Creating Triton Client: {{err}}", err)
|
||||
}
|
||||
|
||||
if c.InsecureSkipTLSVerify {
|
||||
client.InsecureSkipTLSVerify()
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
|
@ -106,6 +117,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
Account: d.Get("account").(string),
|
||||
URL: d.Get("url").(string),
|
||||
KeyID: d.Get("key_id").(string),
|
||||
|
||||
InsecureSkipTLSVerify: d.Get("insecure_skip_tls_verify").(bool),
|
||||
}
|
||||
|
||||
if keyMaterial, ok := d.GetOk("key_material"); ok {
|
||||
|
|
|
@ -23,6 +23,7 @@ var (
|
|||
"user_script": "user-script",
|
||||
"user_data": "user-data",
|
||||
"administrator_pw": "administrator-pw",
|
||||
"cloud_config": "cloud-init:user-data",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -182,6 +183,12 @@ func resourceMachine() *schema.Resource {
|
|||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"cloud_config": {
|
||||
Description: "copied to machine on boot",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"user_data": {
|
||||
Description: "Data copied to machine on boot",
|
||||
Type: schema.TypeString,
|
||||
|
|
|
@ -87,6 +87,7 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"vault_auth_backend": authBackendResource(),
|
||||
"vault_generic_secret": genericSecretResource(),
|
||||
"vault_policy": policyResource(),
|
||||
},
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
func authBackendResource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: authBackendWrite,
|
||||
Delete: authBackendDelete,
|
||||
Read: authBackendRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
Description: "Name of the auth backend",
|
||||
},
|
||||
|
||||
"path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Description: "path to mount the backend. This defaults to the type.",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errs []error) {
|
||||
value := v.(string)
|
||||
if strings.HasSuffix(value, "/") {
|
||||
errs = append(errs, errors.New("cannot write to a path ending in '/'"))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
"description": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Optional: true,
|
||||
Description: "The description of the auth backend",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func authBackendWrite(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*api.Client)
|
||||
|
||||
name := d.Get("type").(string)
|
||||
desc := d.Get("description").(string)
|
||||
path := d.Get("path").(string)
|
||||
|
||||
log.Printf("[DEBUG] Writing auth %s to Vault", name)
|
||||
|
||||
var err error
|
||||
|
||||
if path == "" {
|
||||
path = name
|
||||
err = d.Set("path", name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set state: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = client.Sys().EnableAuth(path, name, desc)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing to Vault: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func authBackendDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*api.Client)
|
||||
|
||||
name := d.Id()
|
||||
|
||||
log.Printf("[DEBUG] Deleting auth %s from Vault", name)
|
||||
|
||||
err := client.Sys().DisableAuth(name)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error disabling auth from Vault: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func authBackendRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*api.Client)
|
||||
|
||||
name := d.Id()
|
||||
|
||||
auths, err := client.Sys().ListAuth()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading from Vault: %s", err)
|
||||
}
|
||||
|
||||
for path, auth := range auths {
|
||||
configuredPath := d.Get("path").(string)
|
||||
|
||||
vaultPath := configuredPath + "/"
|
||||
if auth.Type == name && path == vaultPath {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we fell out here then we didn't find our Auth in the list.
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
r "github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
func TestResourceAuth(t *testing.T) {
|
||||
r.Test(t, r.TestCase{
|
||||
Providers: testProviders,
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Steps: []r.TestStep{
|
||||
r.TestStep{
|
||||
Config: testResourceAuth_initialConfig,
|
||||
Check: testResourceAuth_initialCheck,
|
||||
},
|
||||
r.TestStep{
|
||||
Config: testResourceAuth_updateConfig,
|
||||
Check: testResourceAuth_updateCheck,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var testResourceAuth_initialConfig = `
|
||||
|
||||
resource "vault_auth_backend" "test" {
|
||||
type = "github"
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
func testResourceAuth_initialCheck(s *terraform.State) error {
|
||||
resourceState := s.Modules[0].Resources["vault_auth_backend.test"]
|
||||
if resourceState == nil {
|
||||
return fmt.Errorf("resource not found in state")
|
||||
}
|
||||
|
||||
instanceState := resourceState.Primary
|
||||
if instanceState == nil {
|
||||
return fmt.Errorf("resource has no primary instance")
|
||||
}
|
||||
|
||||
name := instanceState.ID
|
||||
|
||||
if name != instanceState.Attributes["type"] {
|
||||
return fmt.Errorf("id doesn't match name")
|
||||
}
|
||||
|
||||
if name != "github" {
|
||||
return fmt.Errorf("unexpected auth name %s", name)
|
||||
}
|
||||
|
||||
client := testProvider.Meta().(*api.Client)
|
||||
auths, err := client.Sys().ListAuth()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading back auth: %s", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, auth := range auths {
|
||||
if auth.Type == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("could not find auth backend %s in %+v", name, auths)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var testResourceAuth_updateConfig = `
|
||||
|
||||
resource "vault_auth_backend" "test" {
|
||||
type = "ldap"
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
func testResourceAuth_updateCheck(s *terraform.State) error {
|
||||
resourceState := s.Modules[0].Resources["vault_auth_backend.test"]
|
||||
if resourceState == nil {
|
||||
return fmt.Errorf("resource not found in state")
|
||||
}
|
||||
|
||||
instanceState := resourceState.Primary
|
||||
if instanceState == nil {
|
||||
return fmt.Errorf("resource has no primary instance")
|
||||
}
|
||||
|
||||
name := instanceState.ID
|
||||
|
||||
if name != instanceState.Attributes["type"] {
|
||||
return fmt.Errorf("id doesn't match name")
|
||||
}
|
||||
|
||||
if name != "ldap" {
|
||||
return fmt.Errorf("unexpected auth name")
|
||||
}
|
||||
|
||||
client := testProvider.Meta().(*api.Client)
|
||||
auths, err := client.Sys().ListAuth()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading back auth: %s", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, auth := range auths {
|
||||
if auth.Type == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("could not find auth backend %s in %+v", name, auths)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -22,7 +22,7 @@ import (
|
|||
func Provisioner() terraform.ResourceProvisioner {
|
||||
return &schema.Provisioner{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"inline": &schema.Schema{
|
||||
"inline": {
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
PromoteSingle: true,
|
||||
|
@ -30,13 +30,13 @@ func Provisioner() terraform.ResourceProvisioner {
|
|||
ConflictsWith: []string{"script", "scripts"},
|
||||
},
|
||||
|
||||
"script": &schema.Schema{
|
||||
"script": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ConflictsWith: []string{"inline", "scripts"},
|
||||
},
|
||||
|
||||
"scripts": &schema.Schema{
|
||||
"scripts": {
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
Optional: true,
|
||||
|
@ -81,7 +81,11 @@ func applyFn(ctx context.Context) error {
|
|||
func generateScripts(d *schema.ResourceData) ([]string, error) {
|
||||
var lines []string
|
||||
for _, l := range d.Get("inline").([]interface{}) {
|
||||
lines = append(lines, l.(string))
|
||||
line, ok := l.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Error parsing %v as a string", l)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lines = append(lines, "")
|
||||
|
||||
|
@ -109,12 +113,20 @@ func collectScripts(d *schema.ResourceData) ([]io.ReadCloser, error) {
|
|||
// Collect scripts
|
||||
var scripts []string
|
||||
if script, ok := d.GetOk("script"); ok {
|
||||
scripts = append(scripts, script.(string))
|
||||
scr, ok := script.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Error parsing script %v as string", script)
|
||||
}
|
||||
scripts = append(scripts, scr)
|
||||
}
|
||||
|
||||
if scriptList, ok := d.GetOk("scripts"); ok {
|
||||
for _, script := range scriptList.([]interface{}) {
|
||||
scripts = append(scripts, script.(string))
|
||||
scr, ok := script.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Error parsing script %v as string", script)
|
||||
}
|
||||
scripts = append(scripts, scr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -71,6 +73,23 @@ func TestResourceProvider_generateScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_generateScriptEmptyInline(t *testing.T) {
|
||||
p := Provisioner().(*schema.Provisioner)
|
||||
conf := map[string]interface{}{
|
||||
"inline": []interface{}{""},
|
||||
}
|
||||
|
||||
_, err := generateScripts(schema.TestResourceDataRaw(
|
||||
t, p.Schema, conf))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got none")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Error parsing") {
|
||||
t.Fatalf("expected parsing error, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_CollectScripts_inline(t *testing.T) {
|
||||
p := Provisioner().(*schema.Provisioner)
|
||||
conf := map[string]interface{}{
|
||||
|
@ -162,6 +181,24 @@ func TestResourceProvider_CollectScripts_scripts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_CollectScripts_scriptsEmpty(t *testing.T) {
|
||||
p := Provisioner().(*schema.Provisioner)
|
||||
conf := map[string]interface{}{
|
||||
"scripts": []interface{}{""},
|
||||
}
|
||||
|
||||
_, err := collectScripts(schema.TestResourceDataRaw(
|
||||
t, p.Schema, conf))
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Error parsing") {
|
||||
t.Fatalf("Expected parsing error, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryFunc(t *testing.T) {
|
||||
// succeed on the third try
|
||||
errs := []error{io.EOF, &net.OpError{Err: errors.New("ERROR")}, nil}
|
||||
|
|
|
@ -57,7 +57,7 @@ const (
|
|||
envDoesNotExist = `
|
||||
Environment %q doesn't exist!
|
||||
|
||||
You can create this environment with the "-new" option.`
|
||||
You can create this environment with the "new" option.`
|
||||
|
||||
envChanged = `[reset][green]Switched to environment %q!`
|
||||
|
||||
|
|
|
@ -42,6 +42,10 @@ func (h *CountHook) PreApply(
|
|||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
if d.Empty() {
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
if h.pending == nil {
|
||||
h.pending = make(map[string]countHookAction)
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@ func (h *UiHook) PreApply(
|
|||
d *terraform.InstanceDiff) (terraform.HookAction, error) {
|
||||
h.once.Do(h.init)
|
||||
|
||||
// if there's no diff, there's nothing to output
|
||||
if d.Empty() {
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
id := n.HumanId()
|
||||
|
||||
op := uiResourceModify
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
@ -57,6 +58,7 @@ func Funcs() map[string]ast.Function {
|
|||
"base64decode": interpolationFuncBase64Decode(),
|
||||
"base64encode": interpolationFuncBase64Encode(),
|
||||
"base64sha256": interpolationFuncBase64Sha256(),
|
||||
"base64sha512": interpolationFuncBase64Sha512(),
|
||||
"ceil": interpolationFuncCeil(),
|
||||
"chomp": interpolationFuncChomp(),
|
||||
"cidrhost": interpolationFuncCidrHost(),
|
||||
|
@ -90,6 +92,7 @@ func Funcs() map[string]ast.Function {
|
|||
"replace": interpolationFuncReplace(),
|
||||
"sha1": interpolationFuncSha1(),
|
||||
"sha256": interpolationFuncSha256(),
|
||||
"sha512": interpolationFuncSha512(),
|
||||
"signum": interpolationFuncSignum(),
|
||||
"slice": interpolationFuncSlice(),
|
||||
"sort": interpolationFuncSort(),
|
||||
|
@ -1240,6 +1243,20 @@ func interpolationFuncSha256() ast.Function {
|
|||
}
|
||||
}
|
||||
|
||||
func interpolationFuncSha512() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
s := args[0].(string)
|
||||
h := sha512.New()
|
||||
h.Write([]byte(s))
|
||||
hash := hex.EncodeToString(h.Sum(nil))
|
||||
return hash, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func interpolationFuncTrimSpace() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
|
@ -1266,6 +1283,21 @@ func interpolationFuncBase64Sha256() ast.Function {
|
|||
}
|
||||
}
|
||||
|
||||
func interpolationFuncBase64Sha512() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
s := args[0].(string)
|
||||
h := sha512.New()
|
||||
h.Write([]byte(s))
|
||||
shaSum := h.Sum(nil)
|
||||
encoded := base64.StdEncoding.EncodeToString(shaSum[:])
|
||||
return encoded, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func interpolationFuncUUID() ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{},
|
||||
|
|
|
@ -2070,6 +2070,18 @@ func TestInterpolateFuncSha256(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncSha512(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${sha512("test")}`,
|
||||
"ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff",
|
||||
false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncTitle(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
|
@ -2129,6 +2141,23 @@ func TestInterpolateFuncBase64Sha256(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncBase64Sha512(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${base64sha512("test")}`,
|
||||
"7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==",
|
||||
false,
|
||||
},
|
||||
{ // This will differ because we're base64-encoding hex represantiation, not raw bytes
|
||||
`${base64encode(sha512("test"))}`,
|
||||
"ZWUyNmIwZGQ0YWY3ZTc0OWFhMWE4ZWUzYzEwYWU5OTIzZjYxODk4MDc3MmU0NzNmODgxOWE1ZDQ5NDBlMGRiMjdhYzE4NWY4YTBlMWQ1Zjg0Zjg4YmM4ODdmZDY3YjE0MzczMmMzMDRjYzVmYTlhZDhlNmY1N2Y1MDAyOGE4ZmY=",
|
||||
false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInterpolateFuncMd5(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
|
|
|
@ -327,6 +327,10 @@ func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
|
|||
// represents exactly one module definition in the HCL configuration.
|
||||
// We leave it up to another pass to merge them together.
|
||||
func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
|
||||
if err := assertAllBlocksHaveNames("module", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, nil
|
||||
|
@ -391,12 +395,12 @@ func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
|
|||
// LoadOutputsHcl recurses into the given HCL object and turns
|
||||
// it into a mapping of outputs.
|
||||
func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"'output' must be followed by exactly one string: a name")
|
||||
if err := assertAllBlocksHaveNames("output", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
|
||||
// Go through each object and turn it into an actual result.
|
||||
result := make([]*Output, 0, len(list.Items))
|
||||
for _, item := range list.Items {
|
||||
|
@ -450,12 +454,12 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
|||
// LoadVariablesHcl recurses into the given HCL object and turns
|
||||
// it into a list of variables.
|
||||
func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"'variable' must be followed by exactly one strings: a name")
|
||||
if err := assertAllBlocksHaveNames("variable", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
|
||||
// hclVariable is the structure each variable is decoded into
|
||||
type hclVariable struct {
|
||||
DeclaredType string `hcl:"type"`
|
||||
|
@ -531,6 +535,10 @@ func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
|
|||
// LoadProvidersHcl recurses into the given HCL object and turns
|
||||
// it into a mapping of provider configs.
|
||||
func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
|
||||
if err := assertAllBlocksHaveNames("provider", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, nil
|
||||
|
@ -592,6 +600,10 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
|
|||
// represents exactly one data definition in the HCL configuration.
|
||||
// We leave it up to another pass to merge them together.
|
||||
func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
|
||||
if err := assertAllBlocksHaveNames("data", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, nil
|
||||
|
@ -901,6 +913,10 @@ func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
|
|||
}
|
||||
|
||||
func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) {
|
||||
if err := assertAllBlocksHaveNames("provisioner", list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = list.Children()
|
||||
if len(list.Items) == 0 {
|
||||
return nil, nil
|
||||
|
@ -1023,6 +1039,29 @@ func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
|
|||
}
|
||||
*/
|
||||
|
||||
// assertAllBlocksHaveNames returns an error if any of the items in
|
||||
// the given object list are blocks without keys (like "module {}")
|
||||
// or simple assignments (like "module = 1"). It returns nil if
|
||||
// neither of these things are true.
|
||||
//
|
||||
// The given name is used in any generated error messages, and should
|
||||
// be the name of the block we're dealing with. The given list should
|
||||
// be the result of calling .Filter on an object list with that same
|
||||
// name.
|
||||
func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error {
|
||||
if elem := list.Elem(); len(elem.Items) != 0 {
|
||||
switch et := elem.Items[0].Val.(type) {
|
||||
case *ast.ObjectType:
|
||||
pos := et.Lbrace
|
||||
return fmt.Errorf("%s: %q must be followed by a name", pos, name)
|
||||
default:
|
||||
pos := elem.Items[0].Val.Pos()
|
||||
return fmt.Errorf("%s: %q must be a configuration block", pos, name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkHCLKeys(node ast.Node, valid []string) error {
|
||||
var list *ast.ObjectList
|
||||
switch n := node.(type) {
|
||||
|
|
|
@ -314,6 +314,18 @@ func TestLoadFileBasic_modules(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadFile_unnamedModule(t *testing.T) {
|
||||
_, err := LoadFile(filepath.Join(fixtureDir, "module-unnamed.tf"))
|
||||
if err == nil {
|
||||
t.Fatalf("bad: expected error")
|
||||
}
|
||||
|
||||
errorStr := err.Error()
|
||||
if !strings.Contains(errorStr, `"module" must be followed`) {
|
||||
t.Fatalf("bad: expected error has wrong text: %s", errorStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFile_outputDependsOn(t *testing.T) {
|
||||
c, err := LoadFile(filepath.Join(fixtureDir, "output-depends-on.tf"))
|
||||
if err != nil {
|
||||
|
@ -696,7 +708,7 @@ func TestLoadFile_variableNoName(t *testing.T) {
|
|||
}
|
||||
|
||||
errorStr := err.Error()
|
||||
if !strings.Contains(errorStr, "'variable' must be followed") {
|
||||
if !strings.Contains(errorStr, `"variable" must be followed`) {
|
||||
t.Fatalf("bad: expected error has wrong text: %s", errorStr)
|
||||
}
|
||||
}
|
||||
|
@ -740,7 +752,7 @@ func TestLoadFile_unnamedOutput(t *testing.T) {
|
|||
}
|
||||
|
||||
errorStr := err.Error()
|
||||
if !strings.Contains(errorStr, "'output' must be followed") {
|
||||
if !strings.Contains(errorStr, `"output" must be followed`) {
|
||||
t.Fatalf("bad: expected error has wrong text: %s", errorStr)
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue