From bcd5904eeace2ab624231f34522089c979f4d068 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Fri, 11 Dec 2015 18:35:22 -0500 Subject: [PATCH 001/337] Add support for Opsworks Instances New resource checklist - [x] Acceptance testing - [x] Documentation - [x] Well-formed code --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_opsworks_instance.go | 998 ++++++++++++++++++ .../resource_aws_opsworks_instance_test.go | 211 ++++ .../aws/r/opsworks_instance.html.markdown | 133 +++ website/source/layouts/aws.erb | 4 + 5 files changed, 1347 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_opsworks_instance.go create mode 100644 builtin/providers/aws/resource_aws_opsworks_instance_test.go create mode 100644 website/source/docs/providers/aws/r/opsworks_instance.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e469c6756943..b401e2a12ca0 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -208,6 +208,7 @@ func Provider() terraform.ResourceProvider { "aws_opsworks_mysql_layer": resourceAwsOpsworksMysqlLayer(), "aws_opsworks_ganglia_layer": resourceAwsOpsworksGangliaLayer(), "aws_opsworks_custom_layer": resourceAwsOpsworksCustomLayer(), + "aws_opsworks_instance": resourceAwsOpsworksInstance(), "aws_placement_group": resourceAwsPlacementGroup(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_rds_cluster": resourceAwsRDSCluster(), diff --git a/builtin/providers/aws/resource_aws_opsworks_instance.go b/builtin/providers/aws/resource_aws_opsworks_instance.go new file mode 100644 index 000000000000..dc4051f014bd --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_instance.go @@ -0,0 +1,998 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" +) + +func resourceAwsOpsworksInstance() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsOpsworksInstanceCreate, + Read: resourceAwsOpsworksInstanceRead, + Update: resourceAwsOpsworksInstanceUpdate, + Delete: resourceAwsOpsworksInstanceDelete, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "agent_version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "INHERIT", + }, + + "ami_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "architecture": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "x86_64", + ValidateFunc: validateArchitecture, + }, + + "auto_scaling_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateAutoScalingType, + }, + + "availability_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "created_at": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "delete_ebs": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "delete_eip": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "ebs_optimized": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + + "ec2_instance_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "ecs_cluster_arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "elastic_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "hostname": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "infrastructure_class": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "install_updates_on_boot": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "instance_profile_arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "instance_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "last_service_error_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "layer_ids": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "os": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "platform": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "private_dns": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "private_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "public_dns": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "public_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "registered_by": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "reported_agent_version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "reported_os_family": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "reported_os_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "reported_os_version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "root_device_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validateRootDeviceType, + }, + + "root_device_volume_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "security_group_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "ssh_host_dsa_key_fingerprint": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "ssh_host_rsa_key_fingerprint": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "ssh_key_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "stack_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "state": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateState, + }, + + "status": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "virtualization_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateVirtualizationType, + }, + + "ebs_block_device": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_on_termination": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + + "device_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "iops": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "snapshot_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) + return hashcode.String(buf.String()) + }, + }, + "ephemeral_block_device": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "virtual_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) + return hashcode.String(buf.String()) + }, + }, + + "root_block_device": &schema.Schema{ + // TODO: This is a set because we don't support singleton + // sub-resources today. We'll enforce that the set only ever has + // length zero or one below. When TF gains support for + // sub-resources this can be converted. + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Resource{ + // "You can only modify the volume size, volume type, and Delete on + // Termination flag on the block device mapping entry for the root + // device volume." - bit.ly/ec2bdmap + Schema: map[string]*schema.Schema{ + "delete_on_termination": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + + "iops": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + }, + Set: func(v interface{}) int { + // there can be only one root device; no need to hash anything + return 0 + }, + }, + }, + } +} + +func validateArchitecture(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "x86_64" && value != "i386" { + errors = append(errors, fmt.Errorf( + "%q must be one of \"x86_64\" or \"i386\"", k)) + } + return +} + +func validateAutoScalingType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "load" && value != "timer" { + errors = append(errors, fmt.Errorf( + "%q must be one of \"load\" or \"timer\"", k)) + } + return +} + +func validateRootDeviceType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "ebs" && value != "instance-store" { + errors = append(errors, fmt.Errorf( + "%q must be one of \"ebs\" or \"instance-store\"", k)) + } + return +} + +func validateState(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "running" && value != "stopped" { + errors = append(errors, fmt.Errorf( + "%q must be one of \"running\" or \"stopped\"", k)) + } + return +} + +func validateVirtualizationType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "paravirtual" && value != "hvm" { + errors = append(errors, fmt.Errorf( + "%q must be one of \"paravirtual\" or \"hvm\"", k)) + } + return +} + +func resourceAwsOpsworksInstanceValidate(d *schema.ResourceData) error { + if d.HasChange("ami_id") { + if v, ok := d.GetOk("os"); ok { + if v.(string) != "Custom" { + return fmt.Errorf("OS must be \"Custom\" when using using a custom ami_id") + } + } + + if _, ok := d.GetOk("root_block_device"); ok { + return fmt.Errorf("Cannot specify root_block_device when using a custom ami_id.") + } + + if _, ok := d.GetOk("ebs_block_device"); ok { + return fmt.Errorf("Cannot specify ebs_block_device when using a custom ami_id.") + } + + if _, ok := d.GetOk("ephemeral_block_device"); ok { + return fmt.Errorf("Cannot specify ephemeral_block_device when using a custom ami_id.") + } + } + return nil +} + +func resourceAwsOpsworksInstanceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DescribeInstancesInput{ + InstanceIds: []*string{ + aws.String(d.Id()), + }, + } + + log.Printf("[DEBUG] Reading OpsWorks instance: %s", d.Id()) + + resp, err := client.DescribeInstances(req) + if err != nil { + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + d.SetId("") + return nil + } + } + return err + } + + instance := resp.Instances[0] + instanceId := *instance.InstanceId + d.SetId(instanceId) + d.Set("agent_version", instance.AgentVersion) + d.Set("ami_id", instance.AmiId) + d.Set("architecture", instance.Architecture) + d.Set("auto_scaling_type", instance.AutoScalingType) + d.Set("availability_zone", instance.AvailabilityZone) + d.Set("created_at", instance.CreatedAt) + d.Set("ebs_optimized", instance.EbsOptimized) + d.Set("ec2_instance_id", instance.Ec2InstanceId) + d.Set("ecs_cluster_arn", instance.EcsClusterArn) + d.Set("elastic_ip", instance.ElasticIp) + d.Set("hostname", instance.Hostname) + d.Set("infrastructure_class", instance.InfrastructureClass) + d.Set("install_updates_on_boot", instance.InstallUpdatesOnBoot) + d.Set("id", instanceId) + d.Set("instance_profile_arn", instance.InstanceProfileArn) + d.Set("instance_type", instance.InstanceType) + d.Set("last_service_error_id", instance.LastServiceErrorId) + d.Set("layer_ids", instance.LayerIds) + d.Set("os", instance.Os) + d.Set("platform", instance.Platform) + d.Set("private_dns", instance.PrivateDns) + d.Set("private_ip", instance.PrivateIp) + d.Set("public_dns", instance.PublicDns) + d.Set("public_ip", instance.PublicIp) + d.Set("registered_by", instance.RegisteredBy) + d.Set("reported_agent_version", instance.ReportedAgentVersion) + d.Set("reported_os_family", instance.ReportedOs.Family) + d.Set("reported_os_name", instance.ReportedOs.Name) + d.Set("reported_os_version", instance.ReportedOs.Version) + d.Set("root_device_type", instance.RootDeviceType) + d.Set("root_device_volume_id", instance.RootDeviceVolumeId) + d.Set("ssh_host_dsa_key_fingerprint", instance.SshHostDsaKeyFingerprint) + d.Set("ssh_host_rsa_key_fingerprint", instance.SshHostRsaKeyFingerprint) + d.Set("ssh_key_name", instance.SshKeyName) + d.Set("stack_id", instance.StackId) + d.Set("status", instance.Status) + d.Set("subnet_id", instance.SubnetId) + d.Set("virtualization_type", instance.VirtualizationType) + + // Read BlockDeviceMapping + ibds, err := readOpsworksBlockDevices(d, instance, meta) + if err != nil { + return err + } + + if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { + return err + } + if err := d.Set("ephemeral_block_device", ibds["ephemeral"]); err != nil { + return err + } + if ibds["root"] != nil { + if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { + return err + } + } else { + d.Set("root_block_device", []interface{}{}) + } + + // Read Security Groups + sgs := make([]string, 0, len(instance.SecurityGroupIds)) + for _, sg := range instance.SecurityGroupIds { + sgs = append(sgs, *sg) + } + if err := d.Set("security_group_ids", sgs); err != nil { + return err + } + + return nil +} + +func resourceAwsOpsworksInstanceCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + err := resourceAwsOpsworksInstanceValidate(d) + if err != nil { + return err + } + + req := &opsworks.CreateInstanceInput{ + AgentVersion: aws.String(d.Get("agent_version").(string)), + Architecture: aws.String(d.Get("architecture").(string)), + EbsOptimized: aws.Bool(d.Get("ebs_optimized").(bool)), + InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), + InstanceType: aws.String(d.Get("instance_type").(string)), + LayerIds: expandStringList(d.Get("layer_ids").([]interface{})), + StackId: aws.String(d.Get("stack_id").(string)), + } + + if v, ok := d.GetOk("ami_id"); ok { + req.AmiId = aws.String(v.(string)) + req.Os = aws.String("Custom") + } + + if v, ok := d.GetOk("auto_scaling_type"); ok { + req.AutoScalingType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("availability_zone"); ok { + req.AvailabilityZone = aws.String(v.(string)) + } + + if v, ok := d.GetOk("hostname"); ok { + req.Hostname = aws.String(v.(string)) + } + + if v, ok := d.GetOk("os"); ok { + req.Os = aws.String(v.(string)) + } + + if v, ok := d.GetOk("root_device_type"); ok { + req.RootDeviceType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("ssh_key_name"); ok { + req.SshKeyName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("subnet_id"); ok { + req.SubnetId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("virtualization_type"); ok { + req.VirtualizationType = aws.String(v.(string)) + } + + var blockDevices []*opsworks.BlockDeviceMapping + + if v, ok := d.GetOk("ebs_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + ebs := &opsworks.EbsBlockDevice{ + DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), + } + + if v, ok := bd["snapshot_id"].(string); ok && v != "" { + ebs.SnapshotId = aws.String(v) + } + + if v, ok := bd["volume_size"].(int); ok && v != 0 { + ebs.VolumeSize = aws.Int64(int64(v)) + } + + if v, ok := bd["volume_type"].(string); ok && v != "" { + ebs.VolumeType = aws.String(v) + } + + if v, ok := bd["iops"].(int); ok && v > 0 { + ebs.Iops = aws.Int64(int64(v)) + } + + blockDevices = append(blockDevices, &opsworks.BlockDeviceMapping{ + DeviceName: aws.String(bd["device_name"].(string)), + Ebs: ebs, + }) + } + } + + if v, ok := d.GetOk("ephemeral_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + blockDevices = append(blockDevices, &opsworks.BlockDeviceMapping{ + DeviceName: aws.String(bd["device_name"].(string)), + VirtualName: aws.String(bd["virtual_name"].(string)), + }) + } + } + + if v, ok := d.GetOk("root_block_device"); ok { + vL := v.(*schema.Set).List() + if len(vL) > 1 { + return fmt.Errorf("Cannot specify more than one root_block_device.") + } + for _, v := range vL { + bd := v.(map[string]interface{}) + ebs := &opsworks.EbsBlockDevice{ + DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), + } + + if v, ok := bd["volume_size"].(int); ok && v != 0 { + ebs.VolumeSize = aws.Int64(int64(v)) + } + + if v, ok := bd["volume_type"].(string); ok && v != "" { + ebs.VolumeType = aws.String(v) + } + + if v, ok := bd["iops"].(int); ok && v > 0 { + ebs.Iops = aws.Int64(int64(v)) + } + + blockDevices = append(blockDevices, &opsworks.BlockDeviceMapping{ + DeviceName: aws.String("ROOT_DEVICE"), + Ebs: ebs, + }) + } + } + + if len(blockDevices) > 0 { + req.BlockDeviceMappings = blockDevices + } + + log.Printf("[DEBUG] Creating OpsWorks instance") + + var resp *opsworks.CreateInstanceOutput + + resp, err = client.CreateInstance(req) + if err != nil { + return err + } + + instanceId := *resp.InstanceId + d.SetId(instanceId) + d.Set("id", instanceId) + + if v, ok := d.GetOk("state"); ok && v.(string) == "running" { + err := startOpsworksInstance(d, meta, false) + if err != nil { + return err + } + } + + return resourceAwsOpsworksInstanceRead(d, meta) +} + +func resourceAwsOpsworksInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + err := resourceAwsOpsworksInstanceValidate(d) + if err != nil { + return err + } + + req := &opsworks.UpdateInstanceInput{ + AgentVersion: aws.String(d.Get("agent_version").(string)), + Architecture: aws.String(d.Get("architecture").(string)), + InstanceId: aws.String(d.Get("id").(string)), + InstallUpdatesOnBoot: aws.Bool(d.Get("install_updates_on_boot").(bool)), + } + + if v, ok := d.GetOk("ami_id"); ok { + req.AmiId = aws.String(v.(string)) + req.Os = aws.String("Custom") + } + + if v, ok := d.GetOk("auto_scaling_type"); ok { + req.AutoScalingType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("hostname"); ok { + req.Hostname = aws.String(v.(string)) + } + + if v, ok := d.GetOk("instance_type"); ok { + req.InstanceType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("layer_ids"); ok { + req.LayerIds = expandStringList(v.([]interface{})) + + } + + if v, ok := d.GetOk("os"); ok { + req.Os = aws.String(v.(string)) + } + + if v, ok := d.GetOk("ssh_key_name"); ok { + req.SshKeyName = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Updating OpsWorks instance: %s", d.Id()) + + _, err = client.UpdateInstance(req) + if err != nil { + return err + } + + var status string + + if v, ok := d.GetOk("status"); ok { + status = v.(string) + } else { + status = "stopped" + } + + if v, ok := d.GetOk("state"); ok { + state := v.(string) + if state == "running" { + if status == "stopped" || status == "stopping" || status == "shutting_down" { + err := startOpsworksInstance(d, meta, false) + if err != nil { + return err + } + } + } else { + if status != "stopped" && status != "stopping" && status != "shutting_down" { + err := stopOpsworksInstance(d, meta, false) + if err != nil { + return err + } + } + } + } + + return resourceAwsOpsworksInstanceRead(d, meta) +} + +func resourceAwsOpsworksInstanceDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + if v, ok := d.GetOk("status"); ok && v.(string) != "stopped" { + err := stopOpsworksInstance(d, meta, true) + if err != nil { + return err + } + } + + req := &opsworks.DeleteInstanceInput{ + InstanceId: aws.String(d.Id()), + DeleteElasticIp: aws.Bool(d.Get("delete_eip").(bool)), + DeleteVolumes: aws.Bool(d.Get("delete_ebs").(bool)), + } + + log.Printf("[DEBUG] Deleting OpsWorks instance: %s", d.Id()) + + _, err := client.DeleteInstance(req) + if err != nil { + return err + } + + d.SetId("") + return nil +} + +func startOpsworksInstance(d *schema.ResourceData, meta interface{}, wait bool) error { + client := meta.(*AWSClient).opsworksconn + + instanceId := d.Get("id").(string) + + req := &opsworks.StartInstanceInput{ + InstanceId: aws.String(instanceId), + } + + log.Printf("[DEBUG] Starting OpsWorks instance: %s", instanceId) + + _, err := client.StartInstance(req) + + if err != nil { + return err + } + + if wait { + log.Printf("[DEBUG] Waiting for instance (%s) to become running", instanceId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"requested", "pending", "booting", "running_setup"}, + Target: []string{"online"}, + Refresh: OpsworksInstanceStateRefreshFunc(client, instanceId), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to become stopped: %s", + instanceId, err) + } + } + + return nil +} + +func stopOpsworksInstance(d *schema.ResourceData, meta interface{}, wait bool) error { + client := meta.(*AWSClient).opsworksconn + + instanceId := d.Get("id").(string) + + req := &opsworks.StopInstanceInput{ + InstanceId: aws.String(instanceId), + } + + log.Printf("[DEBUG] Stopping OpsWorks instance: %s", instanceId) + + _, err := client.StopInstance(req) + + if err != nil { + return err + } + + if wait { + log.Printf("[DEBUG] Waiting for instance (%s) to become stopped", instanceId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"stopping", "terminating", "shutting_down", "terminated"}, + Target: []string{"stopped"}, + Refresh: OpsworksInstanceStateRefreshFunc(client, instanceId), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to become stopped: %s", + instanceId, err) + } + } + + return nil +} + +func readOpsworksBlockDevices(d *schema.ResourceData, instance *opsworks.Instance, meta interface{}) ( + map[string]interface{}, error) { + + blockDevices := make(map[string]interface{}) + blockDevices["ebs"] = make([]map[string]interface{}, 0) + blockDevices["ephemeral"] = make([]map[string]interface{}, 0) + blockDevices["root"] = nil + + if len(instance.BlockDeviceMappings) == 0 { + return nil, nil + } + + for _, bdm := range instance.BlockDeviceMappings { + bd := make(map[string]interface{}) + if bdm.Ebs != nil && bdm.Ebs.DeleteOnTermination != nil { + bd["delete_on_termination"] = *bdm.Ebs.DeleteOnTermination + } + if bdm.Ebs != nil && bdm.Ebs.VolumeSize != nil { + bd["volume_size"] = *bdm.Ebs.VolumeSize + } + if bdm.Ebs != nil && bdm.Ebs.VolumeType != nil { + bd["volume_type"] = *bdm.Ebs.VolumeType + } + if bdm.Ebs != nil && bdm.Ebs.Iops != nil { + bd["iops"] = *bdm.Ebs.Iops + } + if bdm.DeviceName != nil && *bdm.DeviceName == "ROOT_DEVICE" { + blockDevices["root"] = bd + } else { + if bdm.DeviceName != nil { + bd["device_name"] = *bdm.DeviceName + } + if bdm.VirtualName != nil { + bd["virtual_name"] = *bdm.VirtualName + blockDevices["ephemeral"] = append(blockDevices["ephemeral"].([]map[string]interface{}), bd) + } else { + if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil { + bd["snapshot_id"] = *bdm.Ebs.SnapshotId + } + blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) + } + } + } + return blockDevices, nil +} + +func OpsworksInstanceStateRefreshFunc(conn *opsworks.OpsWorks, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeInstances(&opsworks.DescribeInstancesInput{ + InstanceIds: []*string{aws.String(instanceID)}, + }) + if err != nil { + if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "ResourceNotFoundException" { + // Set this to nil as if we didn't find anything. + resp = nil + } else { + log.Printf("Error on OpsworksInstanceStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil || len(resp.Instances) == 0 { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + i := resp.Instances[0] + return i, *i.Status, nil + } +} diff --git a/builtin/providers/aws/resource_aws_opsworks_instance_test.go b/builtin/providers/aws/resource_aws_opsworks_instance_test.go new file mode 100644 index 000000000000..82d02b63e57b --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_instance_test.go @@ -0,0 +1,211 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// These tests assume the existence of predefined Opsworks IAM roles named `aws-opsworks-ec2-role` +// and `aws-opsworks-service-role`. + +func TestAccAWSOpsworksInstance(t *testing.T) { + stackName := fmt.Sprintf("tf-%d", acctest.RandInt()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsOpsworksInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsOpsworksInstanceConfigCreate(stackName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "hostname", "tf-acc1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "instance_type", "t2.micro", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "state", "stopped", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "layer_ids.#", "1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "install_updates_on_boot", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "architecture", "x86_64", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "os", "Amazon Linux 2014.09", // inherited from opsworks_stack_test + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "root_device_type", "ebs", // inherited from opsworks_stack_test + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "availability_zone", "us-west-2a", // inherited from opsworks_stack_test + ), + ), + }, + resource.TestStep{ + Config: testAccAwsOpsworksInstanceConfigUpdate(stackName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "hostname", "tf-acc1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "instance_type", "t2.small", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "layer_ids.#", "2", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_instance.tf-acc", "os", "Amazon Linux 2015.09", + ), + ), + }, + }, + }) +} + +func testAccCheckAwsOpsworksInstanceDestroy(s *terraform.State) error { + opsworksconn := testAccProvider.Meta().(*AWSClient).opsworksconn + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_opsworks_instance" { + continue + } + req := &opsworks.DescribeInstancesInput{ + InstanceIds: []*string{ + aws.String(rs.Primary.ID), + }, + } + + _, err := opsworksconn.DescribeInstances(req) + if err != nil { + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + // not found, good to go + return nil + } + } + return err + } + } + + return fmt.Errorf("Fall through error on OpsWorks instance test") +} + +func testAccAwsOpsworksInstanceConfigCreate(name string) string { + return fmt.Sprintf(` +resource "aws_security_group" "tf-ops-acc-web" { + name = "%s-web" + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_security_group" "tf-ops-acc-php" { + name = "%s-php" + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_opsworks_static_web_layer" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + + custom_security_group_ids = [ + "${aws_security_group.tf-ops-acc-web.id}", + ] +} + +resource "aws_opsworks_php_app_layer" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + + custom_security_group_ids = [ + "${aws_security_group.tf-ops-acc-php.id}", + ] +} + +resource "aws_opsworks_instance" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + layer_ids = [ + "${aws_opsworks_static_web_layer.tf-acc.id}", + ] + instance_type = "t2.micro" + state = "stopped" + hostname = "tf-acc1" +} + +%s + +`, name, name, testAccAwsOpsworksStackConfigVpcCreate(name)) +} + +func testAccAwsOpsworksInstanceConfigUpdate(name string) string { + return fmt.Sprintf(` +resource "aws_security_group" "tf-ops-acc-web" { + name = "%s-web" + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_security_group" "tf-ops-acc-php" { + name = "%s-php" + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_opsworks_static_web_layer" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + + custom_security_group_ids = [ + "${aws_security_group.tf-ops-acc-web.id}", + ] +} + +resource "aws_opsworks_php_app_layer" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + + custom_security_group_ids = [ + "${aws_security_group.tf-ops-acc-php.id}", + ] +} + +resource "aws_opsworks_instance" "tf-acc" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + layer_ids = [ + "${aws_opsworks_static_web_layer.tf-acc.id}", + "${aws_opsworks_php_app_layer.tf-acc.id}", + ] + instance_type = "t2.small" + state = "stopped" + hostname = "tf-acc1" + os = "Amazon Linux 2015.09" +} + +%s + +`, name, name, testAccAwsOpsworksStackConfigVpcCreate(name)) +} diff --git a/website/source/docs/providers/aws/r/opsworks_instance.html.markdown b/website/source/docs/providers/aws/r/opsworks_instance.html.markdown new file mode 100644 index 000000000000..20b03c2dc665 --- /dev/null +++ b/website/source/docs/providers/aws/r/opsworks_instance.html.markdown @@ -0,0 +1,133 @@ +--- +layout: "aws" +page_title: "AWS: aws_opsworks_instance" +sidebar_current: "docs-aws-resource-opsworks-instance" +description: |- + Provides an OpsWorks instance resource. +--- + +# aws\_opsworks\_instance + +Provides an OpsWorks instance resource. + +## Example Usage + +``` +resource "aws_opsworks_instance" "my-instance" { + stack_id = "${aws_opsworks_stack.my-stack.id}" + layer_ids = [ + "${aws_opsworks_custom_layer.my-layer.id}", + ] + instance_type = "t2.micro" + os = "Amazon Linux 2015.09" + state = "stopped" +``` + +## Argument Reference + +The following arguments are supported: + +* `instance_type` - (Required) The type of instance to start +* `stack_id` - (Required) The id of the stack the instance will belong to. +* `layer_ids` - (Required) The ids of the layers the instance will belong to. +* `state` - (Optional) The desired state of the instance. Can be either `"running"` or `"stopped"`. +* `install_updates_on_boot` - (Optional) Controls where to install OS and package updates when the instance boots. Defaults to `true`. +* `auto_scaling_type` - (Optional) Creates load-based or time-based instances. If set, can be either: `"load"` or `"timer"`. +* `availability_zone` - (Optional) Name of the availability zone where instances will be created + by default. +* `ebs_optimized` - (Optional) If true, the launched EC2 instance will be EBS-optimized. +* `hostname` - (Optional) The instance's host name. +* `architecture` - (Optional) Machine architecture for created instances. Can be either `"x86_64"` (the default) or `"i386"` +* `ami_id` - (Optional) The AMI to use for the instance. If an AMI is specified, `os` must be `"Custom"`. +* `os` - (Optional) Name of operating system that will be installed. +* `root_device_type` - (Optional) Name of the type of root device instances will have by default. Can be either `"ebs"` or `"instance-store"` +* `ssh_key_name` - (Optional) Name of the SSH keypair that instances will have by default. +* `agent_version` - (Optional) The AWS OpsWorks agent to install. Defaults to `"INHERIT"`. +* `subnet_id` - (Optional) Subnet ID to attach to +* `virtualization_type` - (Optional) Keyword to choose what virtualization mode created instances + will use. Can be either `"paravirtual"` or `"hvm"`. +* `root_block_device` - (Optional) Customize details about the root block + device of the instance. See [Block Devices](#block-devices) below for details. +* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the + instance. See [Block Devices](#block-devices) below for details. +* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as + "Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details. + + + +## Block devices + +Each of the `*_block_device` attributes controls a portion of the AWS +Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device +Mapping docs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html) +to understand the implications of using these attributes. + +The `root_block_device` mapping supports the following: + +* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, + or `"io1"`. (Default: `"standard"`). +* `volume_size` - (Optional) The size of the volume in gigabytes. +* `iops` - (Optional) The amount of provisioned + [IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). + This must be set with a `volume_type` of `"io1"`. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed + on instance termination (Default: `true`). + +Modifying any of the `root_block_device` settings requires resource +replacement. + +Each `ebs_block_device` supports the following: + +* `device_name` - The name of the device to mount. +* `snapshot_id` - (Optional) The Snapshot ID to mount. +* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, + or `"io1"`. (Default: `"standard"`). +* `volume_size` - (Optional) The size of the volume in gigabytes. +* `iops` - (Optional) The amount of provisioned + [IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). + This must be set with a `volume_type` of `"io1"`. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed + on instance termination (Default: `true`). +* `encrypted` - (Optional) Enables [EBS + encryption](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html) + on the volume (Default: `false`). Cannot be used with `snapshot_id`. + +Modifying any `ebs_block_device` currently requires resource replacement. + +Each `ephemeral_block_device` supports the following: + +* `device_name` - The name of the block device to mount on the instance. +* `virtual_name` - The [Instance Store Device + Name](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames) + (e.g. `"ephemeral0"`) + +Each AWS Instance type has a different set of Instance Store block devices +available for attachment. AWS [publishes a +list](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes) +of which ephemeral devices are available on each type. The devices are always +identified by the `virtual_name` in the format `"ephemeral{0..N}"`. + +~> **NOTE:** Currently, changes to `*_block_device` configuration of _existing_ +resources cannot be automatically detected by Terraform. After making updates +to block device configuration, resource recreation can be manually triggered by +using the [`taint` command](/docs/commands/taint.html). + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The id of the OpsWorks instance. +* `agent_version` - The AWS OpsWorks agent version. +* `availability_zone` - The availability zone of the instance. +* `ssh_key_name` - The key name of the instance +* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this + is only available if you've enabled DNS hostnames for your VPC +* `public_ip` - The public IP address assigned to the instance, if applicable. +* `private_dns` - The private DNS name assigned to the instance. Can only be + used inside the Amazon EC2, and only available if you've enabled DNS hostnames + for your VPC +* `private_ip` - The private IP address assigned to the instance +* `subnet_id` - The VPC subnet ID. +* `security_group_ids` - The associated security groups. + diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 6887917c9dfd..689477c3b61f 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -458,6 +458,10 @@ aws_opsworks_haproxy_layer + > + aws_opsworks_instance + + > aws_opsworks_java_app_layer From 88de250615972584cdc480371b1a613d22a52c0e Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 24 Mar 2016 08:08:01 -0400 Subject: [PATCH 002/337] style updates to documentation and nil checks --- .../aws/resource_aws_opsworks_instance.go | 15 +++++++++++++++ .../aws/r/opsworks_instance.html.markdown | 9 ++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_opsworks_instance.go b/builtin/providers/aws/resource_aws_opsworks_instance.go index dc4051f014bd..bf1dbfdf3066 100644 --- a/builtin/providers/aws/resource_aws_opsworks_instance.go +++ b/builtin/providers/aws/resource_aws_opsworks_instance.go @@ -510,8 +510,19 @@ func resourceAwsOpsworksInstanceRead(d *schema.ResourceData, meta interface{}) e return err } + // If nothing was found, then return no state + if len(resp.Instances) == 0 { + d.SetId("") + return nil + } instance := resp.Instances[0] + + if instance.InstanceId == nil { + d.SetId("") + return nil + } instanceId := *instance.InstanceId + d.SetId(instanceId) d.Set("agent_version", instance.AgentVersion) d.Set("ami_id", instance.AmiId) @@ -726,6 +737,10 @@ func resourceAwsOpsworksInstanceCreate(d *schema.ResourceData, meta interface{}) return err } + if resp.InstanceId == nil { + return fmt.Errorf("Error launching instance: no instance returned in response") + } + instanceId := *resp.InstanceId d.SetId(instanceId) d.Set("id", instanceId) diff --git a/website/source/docs/providers/aws/r/opsworks_instance.html.markdown b/website/source/docs/providers/aws/r/opsworks_instance.html.markdown index 20b03c2dc665..c855d559f28f 100644 --- a/website/source/docs/providers/aws/r/opsworks_instance.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_instance.html.markdown @@ -13,14 +13,17 @@ Provides an OpsWorks instance resource. ## Example Usage ``` -resource "aws_opsworks_instance" "my-instance" { +aws_opsworks_instance" "my-instance" { stack_id = "${aws_opsworks_stack.my-stack.id}" + layer_ids = [ "${aws_opsworks_custom_layer.my-layer.id}", ] + instance_type = "t2.micro" - os = "Amazon Linux 2015.09" - state = "stopped" + os = "Amazon Linux 2015.09" + state = "stopped" +} ``` ## Argument Reference From 41a8220e0fa821b748fb1fdfaa513d73f4f2f13c Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Fri, 11 Dec 2015 15:14:11 -0500 Subject: [PATCH 003/337] add custom_json for opsworks layers --- builtin/providers/aws/opsworks_layers.go | 22 +++++++++++++++++++ ...resource_aws_opsworks_custom_layer_test.go | 4 ++++ .../aws/r/opsworks_custom_layer.html.markdown | 1 + .../r/opsworks_ganglia_layer.html.markdown | 1 + .../r/opsworks_haproxy_layer.html.markdown | 1 + .../r/opsworks_java_app_layer.html.markdown | 1 + .../r/opsworks_memcached_layer.html.markdown | 1 + .../aws/r/opsworks_mysql_layer.html.markdown | 1 + .../r/opsworks_nodejs_app_layer.html.markdown | 1 + .../r/opsworks_php_app_layer.html.markdown | 1 + .../r/opsworks_rails_app_layer.html.markdown | 1 + .../aws/r/opsworks_stack.html.markdown | 1 + 12 files changed, 36 insertions(+) diff --git a/builtin/providers/aws/opsworks_layers.go b/builtin/providers/aws/opsworks_layers.go index 6eb6d1bddeab..1b0800348bfb 100644 --- a/builtin/providers/aws/opsworks_layers.go +++ b/builtin/providers/aws/opsworks_layers.go @@ -109,6 +109,12 @@ func (lt *opsworksLayerType) SchemaResource() *schema.Resource { Set: schema.HashString, }, + "custom_json": &schema.Schema{ + Type: schema.TypeString, + StateFunc: normalizeJson, + Optional: true, + }, + "auto_healing": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -288,6 +294,14 @@ func (lt *opsworksLayerType) Read(d *schema.ResourceData, client *opsworks.OpsWo d.Set("short_name", layer.Shortname) } + if v := layer.CustomJson; v == nil { + if err := d.Set("custom_json", ""); err != nil { + return err + } + } else if err := d.Set("custom_json", normalizeJson(*v)); err != nil { + return err + } + lt.SetAttributeMap(d, layer.Attributes) lt.SetLifecycleEventConfiguration(d, layer.LifecycleEventConfiguration) lt.SetCustomRecipes(d, layer.CustomRecipes) @@ -342,6 +356,10 @@ func (lt *opsworksLayerType) Create(d *schema.ResourceData, client *opsworks.Ops req.Shortname = aws.String(lt.TypeName) } + if customJson, ok := d.GetOk("custom_json"); ok { + req.CustomJson = aws.String(customJson.(string)) + } + log.Printf("[DEBUG] Creating OpsWorks layer: %s", d.Id()) resp, err := client.CreateLayer(req) @@ -393,6 +411,10 @@ func (lt *opsworksLayerType) Update(d *schema.ResourceData, client *opsworks.Ops req.Shortname = aws.String(lt.TypeName) } + if customJson, ok := d.GetOk("custom_json"); ok { + req.CustomJson = aws.String(customJson.(string)) + } + log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id()) if d.HasChange("elastic_load_balancer") { diff --git a/builtin/providers/aws/resource_aws_opsworks_custom_layer_test.go b/builtin/providers/aws/resource_aws_opsworks_custom_layer_test.go index 7dcff04ffa3f..4df71948445c 100644 --- a/builtin/providers/aws/resource_aws_opsworks_custom_layer_test.go +++ b/builtin/providers/aws/resource_aws_opsworks_custom_layer_test.go @@ -129,6 +129,9 @@ func TestAccAWSOpsworksCustomLayer(t *testing.T) { resource.TestCheckResourceAttr( "aws_opsworks_custom_layer.tf-acc", "ebs_volume.1266957920.iops", "3000", ), + resource.TestCheckResourceAttr( + "aws_opsworks_custom_layer.tf-acc", "custom_json", `{"layer_key":"layer_value2"}`, + ), ), }, }, @@ -268,6 +271,7 @@ resource "aws_opsworks_custom_layer" "tf-acc" { raid_level = 1 iops = 3000 } + custom_json = "{\"layer_key\": \"layer_value2\"}" } %s diff --git a/website/source/docs/providers/aws/r/opsworks_custom_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_custom_layer.html.markdown index 7f04202d4c79..b43ce8a2dd0a 100644 --- a/website/source/docs/providers/aws/r/opsworks_custom_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_custom_layer.html.markdown @@ -39,6 +39,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_ganglia_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_ganglia_layer.html.markdown index 29c8fc68e261..3425eb196e3d 100644 --- a/website/source/docs/providers/aws/r/opsworks_ganglia_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_ganglia_layer.html.markdown @@ -40,6 +40,7 @@ The following arguments are supported: * `username` - (Optiona) The username to use for Ganglia. Defaults to "opsworks". * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_haproxy_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_haproxy_layer.html.markdown index 68b54a646f0c..baeff6172861 100644 --- a/website/source/docs/providers/aws/r/opsworks_haproxy_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_haproxy_layer.html.markdown @@ -43,6 +43,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_java_app_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_java_app_layer.html.markdown index 0463fbba76aa..7d4b3eb232c9 100644 --- a/website/source/docs/providers/aws/r/opsworks_java_app_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_java_app_layer.html.markdown @@ -41,6 +41,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_memcached_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_memcached_layer.html.markdown index 31d4728063e3..fcbcf16f8b3a 100644 --- a/website/source/docs/providers/aws/r/opsworks_memcached_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_memcached_layer.html.markdown @@ -37,6 +37,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_mysql_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_mysql_layer.html.markdown index 0cc11b73f5d8..85033868646e 100644 --- a/website/source/docs/providers/aws/r/opsworks_mysql_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_mysql_layer.html.markdown @@ -38,6 +38,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_nodejs_app_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_nodejs_app_layer.html.markdown index ea0fdeb9b401..e9ab9d597e3c 100644 --- a/website/source/docs/providers/aws/r/opsworks_nodejs_app_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_nodejs_app_layer.html.markdown @@ -37,6 +37,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_php_app_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_php_app_layer.html.markdown index 7d5d8ab8f751..8335c4154d87 100644 --- a/website/source/docs/providers/aws/r/opsworks_php_app_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_php_app_layer.html.markdown @@ -36,6 +36,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_rails_app_layer.html.markdown b/website/source/docs/providers/aws/r/opsworks_rails_app_layer.html.markdown index 27ea7a979c7c..3d2c10fb5df5 100644 --- a/website/source/docs/providers/aws/r/opsworks_rails_app_layer.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_rails_app_layer.html.markdown @@ -42,6 +42,7 @@ The following arguments are supported: * `system_packages` - (Optional) Names of a set of system packages to install on the layer's instances. * `use_ebs_optimized_instances` - (Optional) Whether to use EBS-optimized instances. * `ebs_volume` - (Optional) `ebs_volume` blocks, as described below, will each create an EBS volume and connect it to the layer's instances. +* `custom_json` - (Optional) Custom JSON attributes to apply to the layer. The following extra optional arguments, all lists of Chef recipe names, allow custom Chef recipes to be applied to layer instances at the five different diff --git a/website/source/docs/providers/aws/r/opsworks_stack.html.markdown b/website/source/docs/providers/aws/r/opsworks_stack.html.markdown index d664ca1a9520..b55fa9dffe60 100644 --- a/website/source/docs/providers/aws/r/opsworks_stack.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_stack.html.markdown @@ -51,6 +51,7 @@ The following arguments are supported: * `use_opsworks_security_groups` - (Optional) Boolean value controlling whether the standard OpsWorks security groups apply to created instances. * `vpc_id` - (Optional) The id of the VPC that this stack belongs to. +* `custom_json` - (Optional) Custom JSON attributes to apply to the entire stack. The `custom_cookbooks_source` block supports the following arguments: From cf607e8a5871aef57d2bdd3caef2b48f8e71b2d4 Mon Sep 17 00:00:00 2001 From: Jeff LaPlante Date: Tue, 5 Apr 2016 09:12:45 -0700 Subject: [PATCH 004/337] Added Group attribute to cloudstack instance resource --- .../cloudstack/resource_cloudstack_instance.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance.go b/builtin/providers/cloudstack/resource_cloudstack_instance.go index 05898dc23b4c..315b99ad3e75 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance.go @@ -93,6 +93,12 @@ func resourceCloudStackInstance() *schema.Resource { Optional: true, Default: false, }, + + "group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } @@ -193,6 +199,11 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) p.SetUserdata(ud) } + // If there is a group supplied, add it to the parameter struct + if group, ok := d.GetOk("group"); ok { + p.SetGroup(group.(string)) + } + // Create the new instance r, err := cs.VirtualMachine.DeployVirtualMachine(p) if err != nil { From ba4ec0097a47820db3bd686fdb708eca3b717d74 Mon Sep 17 00:00:00 2001 From: Jeff LaPlante Date: Tue, 5 Apr 2016 09:21:23 -0700 Subject: [PATCH 005/337] fixed formatting --- .../cloudstack/resource_cloudstack_instance.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance.go b/builtin/providers/cloudstack/resource_cloudstack_instance.go index 315b99ad3e75..75b1ce06ede0 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance.go @@ -94,11 +94,11 @@ func resourceCloudStackInstance() *schema.Resource { Default: false, }, - "group": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - }, + "group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } @@ -199,10 +199,10 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) p.SetUserdata(ud) } - // If there is a group supplied, add it to the parameter struct - if group, ok := d.GetOk("group"); ok { - p.SetGroup(group.(string)) - } + // If there is a group supplied, add it to the parameter struct + if group, ok := d.GetOk("group"); ok { + p.SetGroup(group.(string)) + } // Create the new instance r, err := cs.VirtualMachine.DeployVirtualMachine(p) From b857bd1ce9ccc7e58b64ce4c2076ac742de2f810 Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Thu, 24 Mar 2016 16:46:45 -0400 Subject: [PATCH 006/337] add TestCheckExists/TestCheckAttributes for opsworks instance --- .../resource_aws_opsworks_instance_test.go | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_opsworks_instance_test.go b/builtin/providers/aws/resource_aws_opsworks_instance_test.go index 82d02b63e57b..e79f8bb45dfc 100644 --- a/builtin/providers/aws/resource_aws_opsworks_instance_test.go +++ b/builtin/providers/aws/resource_aws_opsworks_instance_test.go @@ -12,11 +12,9 @@ import ( "github.com/hashicorp/terraform/terraform" ) -// These tests assume the existence of predefined Opsworks IAM roles named `aws-opsworks-ec2-role` -// and `aws-opsworks-service-role`. - func TestAccAWSOpsworksInstance(t *testing.T) { stackName := fmt.Sprintf("tf-%d", acctest.RandInt()) + var opsinst opsworks.Instance resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -25,6 +23,9 @@ func TestAccAWSOpsworksInstance(t *testing.T) { resource.TestStep{ Config: testAccAwsOpsworksInstanceConfigCreate(stackName), Check: resource.ComposeTestCheckFunc( + testAccCheckAWSOpsworksInstanceExists( + "aws_opsworks_instance.tf-acc", &opsinst), + testAccCheckAWSOpsworksInstanceAttributes(&opsinst), resource.TestCheckResourceAttr( "aws_opsworks_instance.tf-acc", "hostname", "tf-acc1", ), @@ -57,6 +58,9 @@ func TestAccAWSOpsworksInstance(t *testing.T) { resource.TestStep{ Config: testAccAwsOpsworksInstanceConfigUpdate(stackName), Check: resource.ComposeTestCheckFunc( + testAccCheckAWSOpsworksInstanceExists( + "aws_opsworks_instance.tf-acc", &opsinst), + testAccCheckAWSOpsworksInstanceAttributes(&opsinst), resource.TestCheckResourceAttr( "aws_opsworks_instance.tf-acc", "hostname", "tf-acc1", ), @@ -75,6 +79,65 @@ func TestAccAWSOpsworksInstance(t *testing.T) { }) } +func testAccCheckAWSOpsworksInstanceExists( + n string, opsinst *opsworks.Instance) 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 Opsworks Instance is set") + } + + conn := testAccProvider.Meta().(*AWSClient).opsworksconn + + params := &opsworks.DescribeInstancesInput{ + InstanceIds: []*string{&rs.Primary.ID}, + } + resp, err := conn.DescribeInstances(params) + + if err != nil { + return err + } + + if v := len(resp.Instances); v != 1 { + return fmt.Errorf("Expected 1 request returned, got %d", v) + } + + *opsinst = *resp.Instances[0] + + return nil + } +} + +func testAccCheckAWSOpsworksInstanceAttributes( + opsinst *opsworks.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Depending on the timing, the state could be requested or stopped + if *opsinst.Status != "stopped" && *opsinst.Status != "requested" { + return fmt.Errorf("Unexpected request status: %s", *opsinst.Status) + } + if *opsinst.AvailabilityZone != "us-west-2a" { + return fmt.Errorf("Unexpected availability zone: %s", *opsinst.AvailabilityZone) + } + if *opsinst.Architecture != "x86_64" { + return fmt.Errorf("Unexpected architecture: %s", *opsinst.Architecture) + } + if *opsinst.InfrastructureClass != "ec2" { + return fmt.Errorf("Unexpected infrastructure class: %s", *opsinst.InfrastructureClass) + } + if *opsinst.RootDeviceType != "ebs" { + return fmt.Errorf("Unexpected root device type: %s", *opsinst.RootDeviceType) + } + if *opsinst.VirtualizationType != "hvm" { + return fmt.Errorf("Unexpected virtualization type: %s", *opsinst.VirtualizationType) + } + return nil + } +} + func testAccCheckAwsOpsworksInstanceDestroy(s *terraform.State) error { opsworksconn := testAccProvider.Meta().(*AWSClient).opsworksconn for _, rs := range s.RootModule().Resources { From be0ebbc22e82932f1b7f697e9580779edd8db2ab Mon Sep 17 00:00:00 2001 From: Jeff Tang Date: Wed, 6 Apr 2016 15:57:14 -0400 Subject: [PATCH 007/337] Handle race condition with IAM role permissions --- builtin/providers/aws/resource_aws_opsworks_stack.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_opsworks_stack.go b/builtin/providers/aws/resource_aws_opsworks_stack.go index c021f16a999f..61ceb7a1a4c8 100644 --- a/builtin/providers/aws/resource_aws_opsworks_stack.go +++ b/builtin/providers/aws/resource_aws_opsworks_stack.go @@ -341,7 +341,8 @@ func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) er // Service Role Arn: [...] is not yet propagated, please try again in a couple of minutes propErr := "not yet propagated" trustErr := "not the necessary trust relationship" - if opserr.Code() == "ValidationException" && (strings.Contains(opserr.Message(), trustErr) || strings.Contains(opserr.Message(), propErr)) { + validateErr := "validate IAM role permission" + if opserr.Code() == "ValidationException" && (strings.Contains(opserr.Message(), trustErr) || strings.Contains(opserr.Message(), propErr) || strings.Contains(opserr.Message(), validateErr)) { log.Printf("[INFO] Waiting for service IAM role to propagate") return resource.RetryableError(cerr) } From 6bf9f21c398da289151c0e1120b78fd175f8dc85 Mon Sep 17 00:00:00 2001 From: Jan Nabbefeld Date: Tue, 22 Dec 2015 17:45:27 +0100 Subject: [PATCH 008/337] Opsworks Application support --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_opsworks_application.go | 603 ++++++++++++++++++ .../resource_aws_opsworks_application_test.go | 221 +++++++ .../aws/r/opsworks_application.html.markdown | 94 +++ website/source/layouts/aws.erb | 4 + 5 files changed, 923 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_opsworks_application.go create mode 100644 builtin/providers/aws/resource_aws_opsworks_application_test.go create mode 100644 website/source/docs/providers/aws/r/opsworks_application.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 01d55a8992d9..bf1641067797 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -199,6 +199,7 @@ func Provider() terraform.ResourceProvider { "aws_network_acl": resourceAwsNetworkAcl(), "aws_network_acl_rule": resourceAwsNetworkAclRule(), "aws_network_interface": resourceAwsNetworkInterface(), + "aws_opsworks_application": resourceAwsOpsworksApplication(), "aws_opsworks_stack": resourceAwsOpsworksStack(), "aws_opsworks_java_app_layer": resourceAwsOpsworksJavaAppLayer(), "aws_opsworks_haproxy_layer": resourceAwsOpsworksHaproxyLayer(), diff --git a/builtin/providers/aws/resource_aws_opsworks_application.go b/builtin/providers/aws/resource_aws_opsworks_application.go new file mode 100644 index 000000000000..cf63c3b2344e --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_application.go @@ -0,0 +1,603 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsOpsworksApplication() *schema.Resource { + return &schema.Resource{ + + Create: resourceAwsOpsworksApplicationCreate, + Read: resourceAwsOpsworksApplicationRead, + Update: resourceAwsOpsworksApplicationUpdate, + Delete: resourceAwsOpsworksApplicationDelete, + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "short_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + // aws-flow-ruby | java | rails | php | nodejs | static | other + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "stack_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + // TODO: the following 4 vals are really part of the Attributes array. We should validate that only ones relevant to the chosen type are set, perhaps. (what is the default type? how do they map?) + "document_root": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + //Default: "public", + }, + "rails_env": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + //Default: "production", + }, + "auto_bundle_on_deploy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + //Default: true, + }, + "aws_flow_ruby_settings": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "app_source": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "url": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "username": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "revision": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "ssh_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + // AutoSelectOpsworksMysqlInstance, OpsworksMysqlInstance, or RdsDbInstance. + // anything beside auto select will lead into failure in case the instance doesn't exist + // XXX: validation? + "data_source_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "data_source_database_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "data_source_arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "domains": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "environment": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "value": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "secure": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + "enable_ssl": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "ssl_configuration": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + //Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "certificate": &schema.Schema{ + Type: schema.TypeString, + Required: true, + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + return strings.TrimSpace(v.(string)) + default: + return "" + } + }, + }, + "private_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + return strings.TrimSpace(v.(string)) + default: + return "" + } + }, + }, + "chain": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + return strings.TrimSpace(v.(string)) + default: + return "" + } + }, + }, + }, + }, + }, + }, + } +} + +func resourceAwsOpsworksApplicationValidate(d *schema.ResourceData) error { + appSourceCount := d.Get("app_source.#").(int) + if appSourceCount > 1 { + return fmt.Errorf("Only one app_source is permitted.") + } + + sslCount := d.Get("ssl_configuration.#").(int) + if sslCount > 1 { + return fmt.Errorf("Only one ssl_configuration is permitted.") + } + + if d.Get("type").(string) == opsworks.AppTypeRails { + if _, ok := d.GetOk("rails_env"); !ok { + return fmt.Errorf("Set rails_env must be set if type is set to rails.") + } + } + switch d.Get("type").(string) { + case opsworks.AppTypeStatic: + case opsworks.AppTypeRails: + case opsworks.AppTypePhp: + case opsworks.AppTypeOther: + case opsworks.AppTypeNodejs: + case opsworks.AppTypeJava: + case opsworks.AppTypeAwsFlowRuby: + log.Printf("[DEBUG] type supported") + default: + return fmt.Errorf("opsworks_application.type must be one of %s, %s, %s, %s, %s, %s, %s", + opsworks.AppTypeStatic, + opsworks.AppTypeRails, + opsworks.AppTypePhp, + opsworks.AppTypeOther, + opsworks.AppTypeNodejs, + opsworks.AppTypeJava, + opsworks.AppTypeAwsFlowRuby) + } + + return nil +} + +func resourceAwsOpsworksApplicationRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DescribeAppsInput{ + AppIds: []*string{ + aws.String(d.Id()), + }, + } + + log.Printf("[DEBUG] Reading OpsWorks app: %s", d.Id()) + + resp, err := client.DescribeApps(req) + if err != nil { + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() == "ResourceNotFoundException" { + log.Printf("[INFO] App not found: %s", d.Id()) + d.SetId("") + return nil + } + } + return err + } + + app := resp.Apps[0] + + d.Set("name", app.Name) + d.Set("stack_id", app.StackId) + d.Set("type", app.Type) + d.Set("description", app.Description) + d.Set("domains", flattenStringList(app.Domains)) + d.Set("enable_ssl", app.EnableSsl) + resourceAwsOpsworksSetApplicationSsl(d, app.SslConfiguration) + resourceAwsOpsworksSetApplicationSource(d, app.AppSource) + resourceAwsOpsworksSetApplicationDataSources(d, app.DataSources) + resourceAwsOpsworksSetApplicationEnvironmentVariable(d, app.Environment) + resourceAwsOpsworksSetApplicationAttributes(d, app.Attributes) + return nil +} + +func resourceAwsOpsworksApplicationCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + err := resourceAwsOpsworksApplicationValidate(d) + if err != nil { + return err + } + + req := &opsworks.CreateAppInput{ + Name: aws.String(d.Get("name").(string)), + Shortname: aws.String(d.Get("short_name").(string)), + StackId: aws.String(d.Get("stack_id").(string)), + Type: aws.String(d.Get("type").(string)), + Description: aws.String(d.Get("description").(string)), + Domains: expandStringList(d.Get("domains").([]interface{})), + EnableSsl: aws.Bool(d.Get("enable_ssl").(bool)), + SslConfiguration: resourceAwsOpsworksApplicationSsl(d), + AppSource: resourceAwsOpsworksApplicationSource(d), + DataSources: resourceAwsOpsworksApplicationDataSources(d), + Environment: resourceAwsOpsworksApplicationEnvironmentVariable(d), + Attributes: resourceAwsOpsworksApplicationAttributes(d), + } + + var resp *opsworks.CreateAppOutput + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + var cerr error + resp, cerr = client.CreateApp(req) + if cerr != nil { + log.Printf("[INFO] client error") + if opserr, ok := cerr.(awserr.Error); ok { + // XXX: handle errors + log.Printf("[ERROR] OpsWorks error: %s message: %s", opserr.Code(), opserr.Message()) + return resource.RetryableError(cerr) + } + return resource.NonRetryableError(cerr) + } + return nil + }) + + if err != nil { + return err + } + + appID := *resp.AppId + d.SetId(appID) + d.Set("id", appID) + + return resourceAwsOpsworksApplicationRead(d, meta) +} + +func resourceAwsOpsworksApplicationUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.UpdateAppInput{ + AppId: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + Type: aws.String(d.Get("type").(string)), + Description: aws.String(d.Get("description").(string)), + Domains: expandStringList(d.Get("domains").([]interface{})), + EnableSsl: aws.Bool(d.Get("enable_ssl").(bool)), + SslConfiguration: resourceAwsOpsworksApplicationSsl(d), + AppSource: resourceAwsOpsworksApplicationSource(d), + DataSources: resourceAwsOpsworksApplicationDataSources(d), + Environment: resourceAwsOpsworksApplicationEnvironmentVariable(d), + Attributes: resourceAwsOpsworksApplicationAttributes(d), + } + + log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id()) + + var resp *opsworks.UpdateAppOutput + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + var cerr error + resp, cerr = client.UpdateApp(req) + if cerr != nil { + log.Printf("[INFO] client error") + if opserr, ok := cerr.(awserr.Error); ok { + // XXX: handle errors + log.Printf("[ERROR] OpsWorks error: %s message: %s", opserr.Code(), opserr.Message()) + return resource.NonRetryableError(cerr) + } + return resource.RetryableError(cerr) + } + return nil + }) + + if err != nil { + return err + } + return resourceAwsOpsworksApplicationRead(d, meta) +} + +func resourceAwsOpsworksApplicationDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AWSClient).opsworksconn + + req := &opsworks.DeleteAppInput{ + AppId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting OpsWorks application: %s", d.Id()) + + _, err := client.DeleteApp(req) + return err +} + +func resourceAwsOpsworksSetApplicationEnvironmentVariable(d *schema.ResourceData, v []*opsworks.EnvironmentVariable) { + log.Printf("[DEBUG] envs: %s %d", v, len(v)) + if len(v) == 0 { + d.Set("environment", nil) + return + } + newValue := make([]*map[string]interface{}, len(v)) + + for i := 0; i < len(v); i++ { + config := v[i] + data := make(map[string]interface{}) + newValue[i] = &data + + if config.Key != nil { + data["key"] = *config.Key + } + if config.Value != nil { + data["value"] = *config.Value + } + if config.Secure != nil { + + if bool(*config.Secure) { + data["secure"] = &opsworksTrueString + } else { + data["secure"] = &opsworksFalseString + } + } + log.Printf("[DEBUG] v: %s", data) + } + + d.Set("environment", newValue) +} + +func resourceAwsOpsworksApplicationEnvironmentVariable(d *schema.ResourceData) []*opsworks.EnvironmentVariable { + environmentVariables := d.Get("environment").(*schema.Set).List() + result := make([]*opsworks.EnvironmentVariable, len(environmentVariables)) + + for i := 0; i < len(environmentVariables); i++ { + env := environmentVariables[i].(map[string]interface{}) + + result[i] = &opsworks.EnvironmentVariable{ + Key: aws.String(env["key"].(string)), + Value: aws.String(env["value"].(string)), + Secure: aws.Bool(env["secure"].(bool)), + } + } + return result +} + +func resourceAwsOpsworksApplicationSource(d *schema.ResourceData) *opsworks.Source { + count := d.Get("app_source.#").(int) + if count == 0 { + return nil + } + + return &opsworks.Source{ + Type: aws.String(d.Get("app_source.0.type").(string)), + Url: aws.String(d.Get("app_source.0.url").(string)), + Username: aws.String(d.Get("app_source.0.username").(string)), + Password: aws.String(d.Get("app_source.0.password").(string)), + Revision: aws.String(d.Get("app_source.0.revision").(string)), + SshKey: aws.String(d.Get("app_source.0.ssh_key").(string)), + } +} + +func resourceAwsOpsworksSetApplicationSource(d *schema.ResourceData, v *opsworks.Source) { + nv := make([]interface{}, 0, 1) + if v != nil { + m := make(map[string]interface{}) + if v.Type != nil { + m["type"] = *v.Type + } + if v.Url != nil { + m["url"] = *v.Url + } + if v.Username != nil { + m["username"] = *v.Username + } + if v.Password != nil { + m["password"] = *v.Password + } + if v.Revision != nil { + m["revision"] = *v.Revision + } + if v.SshKey != nil { + m["ssh_key"] = *v.SshKey + } + nv = append(nv, m) + } + + err := d.Set("app_source", nv) + if err != nil { + // should never happen + panic(err) + } +} + +func resourceAwsOpsworksApplicationDataSources(d *schema.ResourceData) []*opsworks.DataSource { + arn := d.Get("data_source_arn").(string) + databaseName := d.Get("data_source_database_name").(string) + databaseType := d.Get("data_source_type").(string) + + result := make([]*opsworks.DataSource, 1) + + if len(arn) > 0 || len(databaseName) > 0 || len(databaseType) > 0 { + result[0] = &opsworks.DataSource{ + Arn: aws.String(arn), + DatabaseName: aws.String(databaseName), + Type: aws.String(databaseType), + } + } + return result +} + +func resourceAwsOpsworksSetApplicationDataSources(d *schema.ResourceData, v []*opsworks.DataSource) { + d.Set("data_source_arn", nil) + d.Set("data_source_database_name", nil) + d.Set("data_source_type", nil) + + if len(v) == 0 { + return + } + + d.Set("data_source_arn", v[0].Arn) + d.Set("data_source_database_name", v[0].DatabaseName) + d.Set("data_source_type", v[0].Type) +} + +func resourceAwsOpsworksApplicationSsl(d *schema.ResourceData) *opsworks.SslConfiguration { + count := d.Get("ssl_configuration.#").(int) + if count == 0 { + return nil + } + + return &opsworks.SslConfiguration{ + PrivateKey: aws.String(d.Get("ssl_configuration.0.private_key").(string)), + Certificate: aws.String(d.Get("ssl_configuration.0.certificate").(string)), + Chain: aws.String(d.Get("ssl_configuration.0.chain").(string)), + } +} + +func resourceAwsOpsworksSetApplicationSsl(d *schema.ResourceData, v *opsworks.SslConfiguration) { + nv := make([]interface{}, 0, 1) + set := false + if v != nil { + m := make(map[string]interface{}) + if v.PrivateKey != nil { + m["private_key"] = *v.PrivateKey + set = true + } + if v.Certificate != nil { + m["certificate"] = *v.Certificate + set = true + } + if v.Chain != nil { + m["chain"] = *v.Chain + set = true + } + if set { + nv = append(nv, m) + } + } + + err := d.Set("ssl_configuration", nv) + if err != nil { + // should never happen + panic(err) + } +} + +func resourceAwsOpsworksApplicationAttributes(d *schema.ResourceData) map[string]*string { + if d.Get("type") != opsworks.AppTypeRails { + return nil + } + attributes := make(map[string]*string) + + if val := d.Get("document_root").(string); len(val) > 0 { + attributes[opsworks.AppAttributesKeysDocumentRoot] = aws.String(val) + } + if val := d.Get("aws_flow_ruby_settings").(string); len(val) > 0 { + attributes[opsworks.AppAttributesKeysAwsFlowRubySettings] = aws.String(val) + } + if val := d.Get("rails_env").(string); len(val) > 0 { + attributes[opsworks.AppAttributesKeysRailsEnv] = aws.String(val) + } + if val := d.Get("auto_bundle_on_deploy").(string); len(val) > 0 { + if val == "1" { + val = "true" + } else if val == "0" { + val = "false" + } + attributes[opsworks.AppAttributesKeysAutoBundleOnDeploy] = aws.String(val) + } + + return attributes +} + +func resourceAwsOpsworksSetApplicationAttributes(d *schema.ResourceData, v map[string]*string) { + d.Set("document_root", nil) + d.Set("rails_env", nil) + d.Set("aws_flow_ruby_settings", nil) + d.Set("auto_bundle_on_deploy", nil) + + if d.Get("type") != opsworks.AppTypeRails { + return + } + if val, ok := v[opsworks.AppAttributesKeysDocumentRoot]; ok { + d.Set("document_root", val) + } + if val, ok := v[opsworks.AppAttributesKeysAwsFlowRubySettings]; ok { + d.Set("aws_flow_ruby_settings", val) + } + if val, ok := v[opsworks.AppAttributesKeysRailsEnv]; ok { + d.Set("rails_env", val) + } + if val, ok := v[opsworks.AppAttributesKeysAutoBundleOnDeploy]; ok { + d.Set("auto_bundle_on_deploy", val) + } +} diff --git a/builtin/providers/aws/resource_aws_opsworks_application_test.go b/builtin/providers/aws/resource_aws_opsworks_application_test.go new file mode 100644 index 000000000000..7f202be3721f --- /dev/null +++ b/builtin/providers/aws/resource_aws_opsworks_application_test.go @@ -0,0 +1,221 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSOpsworksApplication(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsOpsworksApplicationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsOpsworksApplicationCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "name", "tf-ops-acc-application", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "type", "other", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "enable_ssl", "false", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "ssl_configuration", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "domains", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.key", "key1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.value", "value1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.secret", "", + ), + ), + }, + resource.TestStep{ + Config: testAccAwsOpsworksApplicationUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "name", "tf-ops-acc-application", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "type", "rails", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "enable_ssl", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "ssl_configuration.0.certificate", "-----BEGIN CERTIFICATE-----\nMIIBkDCB+gIJALoScFD0sJq3MA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAkRF\nMB4XDTE1MTIxOTIwMzU1MVoXDTE2MDExODIwMzU1MVowDTELMAkGA1UEBhMCREUw\ngZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKKQKbTTH/Julz16xY7ArYlzJYCP\nedTCx1bopuryCx/+d1gC94MtRdlPSpQl8mfc9iBdtXbJppp73Qh/DzLzO9Ns25xZ\n+kUQMhbIyLsaCBzuEGLgAaVdGpNvRBw++UoYtd0U7QczFAreTGLH8n8+FIzuI5Mc\n+MJ1TKbbt5gFfRSzAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEALARo96wCDmaHKCaX\nS0IGLGnZCfiIUfCmBxOXBSJxDBwter95QHR0dMGxYIujee5n4vvavpVsqZnfMC3I\nOZWPlwiUJbNIpK+04Bg2vd5m/NMMrvi75RfmyeMtSfq/NrIX2Q3+nyWI7DLq7yZI\nV/YEvOqdAiy5NEWBztHx8HvB9G4=\n-----END CERTIFICATE-----", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "ssl_configuration.0.private_key", "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQCikCm00x/ybpc9esWOwK2JcyWAj3nUwsdW6Kbq8gsf/ndYAveD\nLUXZT0qUJfJn3PYgXbV2yaaae90Ifw8y8zvTbNucWfpFEDIWyMi7Gggc7hBi4AGl\nXRqTb0QcPvlKGLXdFO0HMxQK3kxix/J/PhSM7iOTHPjCdUym27eYBX0UswIDAQAB\nAoGBAIYcrvuqDboguI8U4TUjCkfSAgds1pLLWk79wu8jXkA329d1IyNKT0y3WIye\nPbyoEzmidZmZROQ/+ZsPz8c12Y0DrX73WSVzKNyJeP7XMk9HSzA1D9RX0U0S+5Kh\nFAMc2NEVVFIfQtVtoVmHdKDpnRYtOCHLW9rRpvqOOjd4mYk5AkEAzeiFr1mtlnsa\n67shMxzDaOTAFMchRz6G7aSovvCztxcB63ulFI/w9OTUMdTQ7ff7pet+lVihLc2W\nefIL0HvsjQJBAMocNTKaR/TnsV5GSk2kPAdR+zFP5sQy8sfMy0lEXTylc7zN4ajX\nMeHVoxp+GZgpfDcZ3ya808H1umyXh+xA1j8CQE9x9ZKQYT98RAjL7KVR5btk9w+N\nPTPF1j1+mHUDXfO4ds8qp6jlWKzEVXLcj7ghRADiebaZuaZ4eiSW1SQdjEkCQQC4\nwDhQ3X9RfEpCp3ZcqvjEqEg6t5N3XitYQPjDLN8eBRBbUsgpEy3iBuxl10eGNMX7\niIbYXlwkPYAArDPv3wT5AkAwp4vym+YKmDqh6gseKfRDuJqRiW9yD5A8VGr/w88k\n5rkuduVGP7tK3uIp00Its3aEyKF8mLGWYszVGeeLxAMH\n-----END RSA PRIVATE KEY-----", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "domains.0", "example.com", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "domains.1", "sub.example.com", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.password", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.revision", "master", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.ssh_key", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.type", "git", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.url", "https://github.com/aws/example.git", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "app_source.0.username", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.2107898637.key", "key2", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.2107898637.value", "value2", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.2107898637.secure", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.key", "key1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.value", "value1", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "environment.3077298702.secret", "", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "document_root", "root", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "auto_bundle_on_deploy", "true", + ), + resource.TestCheckResourceAttr( + "aws_opsworks_application.tf-acc-app", "rails_env", "staging", + ), + ), + }, + }, + }) +} + +func testAccCheckAwsOpsworksApplicationDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*AWSClient).opsworksconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_opsworks_application" { + continue + } + + req := &opsworks.DescribeAppsInput{ + AppIds: []*string{ + aws.String(rs.Primary.ID), + }, + } + + resp, err := client.DescribeApps(req) + if err == nil { + if len(resp.Apps) > 0 { + return fmt.Errorf("OpsWorks App still exist.") + } + } + + if awserr, ok := err.(awserr.Error); ok { + if awserr.Code() != "ResourceNotFoundException" { + return err + } + } + } + + return nil +} + +var testAccAwsOpsworksApplicationCreate = testAccAwsOpsworksStackConfigNoVpcCreate("") + ` +resource "aws_opsworks_application" "tf-acc-app" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + name = "tf-ops-acc-application" + type = "other" + enable_ssl = false + app_source ={ + type = "other" + } + environment = { key = "key1" value = "value1" secure = false} +} +` + +var testAccAwsOpsworksApplicationUpdate = testAccAwsOpsworksStackConfigNoVpcCreate("") + ` +resource "aws_opsworks_application" "tf-acc-app" { + stack_id = "${aws_opsworks_stack.tf-acc.id}" + name = "tf-ops-acc-application" + type = "rails" + domains = ["example.com", "sub.example.com"] + enable_ssl = true + ssl_configuration = { + private_key = <aws_opsworks_static_web_layer + > + aws_opsworks_application + + From a8a3bd71df91eb019d74782334ef84d14042604b Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 8 Apr 2016 04:07:42 +0000 Subject: [PATCH 009/337] provider/openstack: Enable Token Authentication This commit enables the ability to authenticate to OpenStack by way of a Keystone Token. Tokens can provide a way to use Terraform and OpenStack with an expiring, temporary credential. The token will need to be generated out of band from Terraform. --- builtin/providers/openstack/config.go | 2 ++ builtin/providers/openstack/provider.go | 12 +++++++++--- .../docs/providers/openstack/index.html.markdown | 13 ++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index 47ba00f855eb..5001c8ecad98 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -15,6 +15,7 @@ type Config struct { Username string UserID string Password string + Token string APIKey string IdentityEndpoint string TenantID string @@ -41,6 +42,7 @@ func (c *Config) loadAndValidate() error { Username: c.Username, UserID: c.UserID, Password: c.Password, + TokenID: c.Token, APIKey: c.APIKey, IdentityEndpoint: c.IdentityEndpoint, TenantID: c.TenantID, diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 2e5a1f8e74d3..d917ed3fd1f8 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -17,7 +17,7 @@ func Provider() terraform.ResourceProvider { "user_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_USERNAME", nil), + DefaultFunc: schema.EnvDefaultFunc("OS_USERNAME", ""), }, "user_id": &schema.Schema{ Type: schema.TypeString, @@ -37,13 +37,18 @@ func Provider() terraform.ResourceProvider { "password": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OS_PASSWORD", nil), + DefaultFunc: schema.EnvDefaultFunc("OS_PASSWORD", ""), }, - "api_key": &schema.Schema{ + "token": &schema.Schema{ Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_TOKEN", ""), }, + "api_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OS_API_KEY", ""), + }, "domain_id": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -104,6 +109,7 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) { Username: d.Get("user_name").(string), UserID: d.Get("user_id").(string), Password: d.Get("password").(string), + Token: d.Get("token").(string), APIKey: d.Get("api_key").(string), TenantID: d.Get("tenant_id").(string), TenantName: d.Get("tenant_name").(string), diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index 52f94e46abc6..3a044ee62aea 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -46,7 +46,18 @@ The following arguments are supported: * `password` - (Optional; Required if not using `api_key`) If omitted, the `OS_PASSWORD` environment variable is used. -* `api_key` - (Optional; Required if not using `password`) +* `token` - (Optional; Required if not using `user_name` and `password`) + A token is an expiring, temporary means of access issued via the + Keystone service. By specifying a token, you do not have to + specify a username/password combination, since the token was + already created by a username/password out of band of Terraform. + If ommitted, the `OS_AUTH_TOKEN` environment variable is used. + +* `api_key` - (Optional; Required if not using `password`) An API Key + is issued by a cloud provider as alternative password. Unless + your cloud provider has documentation referencing an API Key, + you can safely ignore this argument. If omitted, the `OS_API_KEY` + environment variable is used. * `domain_id` - (Optional) If omitted, the `OS_DOMAIN_ID` environment variable is used. From 5824036ca66f3c51a30810d78c5e23b62aafdafc Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Mon, 11 Apr 2016 10:23:01 +0200 Subject: [PATCH 010/337] provider/openstack: Add value_specs for routers --- ...resource_openstack_networking_router_v2.go | 64 ++++++++++++++++++- .../r/networking_router_v2.html.markdown | 3 + 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_v2.go index 7b5f3b8c25a8..d4f80828cc3a 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2.go @@ -54,10 +54,59 @@ func resourceNetworkingRouterV2() *schema.Resource { ForceNew: true, Computed: true, }, + "value_specs": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, }, } } +// routerCreateOpts contains all the values needed to create a new router. There are +// no required values. +type RouterCreateOpts struct { + Name string + AdminStateUp *bool + Distributed *bool + TenantID string + GatewayInfo *routers.GatewayInfo + ValueSpecs map[string]string +} + +// ToRouterCreateMap casts a routerCreateOpts struct to a map. +func (opts RouterCreateOpts) ToRouterCreateMap() (map[string]interface{}, error) { + r := make(map[string]interface{}) + + if gophercloud.MaybeString(opts.Name) != nil { + r["name"] = opts.Name + } + + if opts.AdminStateUp != nil { + r["admin_state_up"] = opts.AdminStateUp + } + + if opts.Distributed != nil { + r["distributed"] = opts.Distributed + } + + if gophercloud.MaybeString(opts.TenantID) != nil { + r["tenant_id"] = opts.TenantID + } + + if opts.GatewayInfo != nil { + r["external_gateway_info"] = opts.GatewayInfo + } + + if opts.ValueSpecs != nil { + for k, v := range opts.ValueSpecs { + r[k] = v + } + } + + return map[string]interface{}{"router": r}, nil +} + func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) @@ -65,9 +114,10 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - createOpts := routers.CreateOpts{ - Name: d.Get("name").(string), - TenantID: d.Get("tenant_id").(string), + createOpts := RouterCreateOpts{ + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + ValueSpecs: routerValueSpecs(d), } if asuRaw, ok := d.GetOk("admin_state_up"); ok { @@ -239,3 +289,11 @@ func waitForRouterDelete(networkingClient *gophercloud.ServiceClient, routerId s return r, "ACTIVE", nil } } + +func routerValueSpecs(d *schema.ResourceData) map[string]string { + m := make(map[string]string) + for key, val := range d.Get("value_specs").(map[string]interface{}) { + m[key] = val.(string) + } + return m +} diff --git a/website/source/docs/providers/openstack/r/networking_router_v2.html.markdown b/website/source/docs/providers/openstack/r/networking_router_v2.html.markdown index 04a261a38cf2..5540adb62c40 100644 --- a/website/source/docs/providers/openstack/r/networking_router_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_router_v2.html.markdown @@ -48,6 +48,8 @@ The following arguments are supported: * `tenant_id` - (Optional) The owner of the floating IP. Required if admin wants to create a router for another tenant. Changing this creates a new router. +* `value_specs` - (Optional) Map of additional driver-specific options. + ## Attributes Reference The following attributes are exported: @@ -57,3 +59,4 @@ The following attributes are exported: * `admin_state_up` - See Argument Reference above. * `external_gateway` - See Argument Reference above. * `tenant_id` - See Argument Reference above. +* `value_specs` - See Argument Reference above. From c40f73960e6377d1e864d1417e39eb332010a0dd Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 24 Feb 2016 10:39:24 -0500 Subject: [PATCH 011/337] Support for Linked Cloning in vsphere, based off of https://github.com/mkuzmin/terraform-vsphere/tree/6814028be741262a575b41d8577fadde537d6c62 --- .../resource_vsphere_virtual_machine.go | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index be49c99e7652..d5c1fbd65f9a 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -50,6 +50,7 @@ type virtualMachine struct { cluster string resourcePool string datastore string + linkedClone bool vcpu int memoryMb int64 template string @@ -124,6 +125,12 @@ func resourceVSphereVirtualMachine() *schema.Resource { ForceNew: true, }, + "linkedClone": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, "gateway": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -318,6 +325,10 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ vm.timeZone = v.(string) } + if v, ok := d.GetOk("linkedClone"); ok { + vm.linkedClone = v.(bool) + } + if raw, ok := d.GetOk("dns_suffixes"); ok { for _, v := range raw.([]interface{}) { vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string)) @@ -707,8 +718,15 @@ func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.Virtu } // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine. -func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, initType string) (types.VirtualMachineRelocateSpec, error) { +func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) { var key int + var moveType string + if linkedClone { + moveType = "createNewChildDiskBacking" + } else { + moveType = "moveAllDiskBackingsAndDisallowSharing" + } + log.Printf("[DEBUG] relocate type: [%s]", moveType) devices, err := vm.Device(context.TODO()) if err != nil { @@ -724,8 +742,9 @@ func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *obje rpr := rp.Reference() dsr := ds.Reference() return types.VirtualMachineRelocateSpec{ - Datastore: &dsr, - Pool: &rpr, + Datastore: &dsr, + Pool: &rpr, + DiskMoveType: moveType, Disk: []types.VirtualMachineRelocateSpecDiskLocator{ types.VirtualMachineRelocateSpecDiskLocator{ Datastore: dsr, @@ -1099,7 +1118,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { } log.Printf("[DEBUG] datastore: %#v", datastore) - relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.hardDisks[0].initType) + relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) if err != nil { return err } @@ -1204,6 +1223,17 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { Config: &configSpec, PowerOn: false, } + if vm.linkedClone { + var template_mo mo.VirtualMachine + err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) + if err != nil { + return fmt.Errorf("Error reading base VM properties: %s", err) + } + if template_mo.Snapshot == nil { + return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") + } + cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot + } log.Printf("[DEBUG] clone spec: %v", cloneSpec) task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec) From 5f4a3ec09a275ae195d63b9657c9ccbf61bb40cd Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Thu, 10 Mar 2016 16:12:33 -0500 Subject: [PATCH 012/337] Creating different config spec based on template OS ID --- .../resource_vsphere_virtual_machine.go | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index d5c1fbd65f9a..a26afde3d609 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "strconv" "strings" "time" @@ -1198,16 +1199,50 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) } - // create CustomizationSpec - customSpec := types.CustomizationSpec{ - Identity: &types.CustomizationLinuxPrep{ + var template_mo mo.VirtualMachine + err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) + + var identity_options types.BaseCustomizationIdentitySettings + if strings.HasPrefix(template_mo.Config.GuestId, "win") { + var timeZone int + timeZone, err := strconv.Atoi(vm.timeZone) + if err != nil { + return fmt.Errorf("Error reading base VM properties: %s", err) + } + identity_options = &types.CustomizationSysprep{ + GuiUnattended: types.CustomizationGuiUnattended{ + AutoLogon: false, + AutoLogonCount: 1, + Password: &types.CustomizationPassword{ + PlainText: true, + Value: "NULL", + }, + TimeZone: timeZone, + }, + Identification: types.CustomizationIdentification{}, + UserData: types.CustomizationUserData{ + ComputerName: &types.CustomizationFixedName{ + Name: strings.Split(vm.name, ".")[0], + }, + FullName: "LSTTE", + OrgName: "LSTTE", + ProductId: "ruh roh", + }, + } + } else { + identity_options = &types.CustomizationLinuxPrep{ HostName: &types.CustomizationFixedName{ Name: strings.Split(vm.name, ".")[0], }, Domain: vm.domain, TimeZone: vm.timeZone, HwClockUTC: types.NewBool(true), - }, + } + } + + // create CustomizationSpec + customSpec := types.CustomizationSpec{ + Identity: identity_options, GlobalIPSettings: types.CustomizationGlobalIPSettings{ DnsSuffixList: vm.dnsSuffixes, DnsServerList: vm.dnsServers, @@ -1224,8 +1259,6 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { PowerOn: false, } if vm.linkedClone { - var template_mo mo.VirtualMachine - err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) if err != nil { return fmt.Errorf("Error reading base VM properties: %s", err) } From 7dfc0a6d1e1702e3e9fb6d4e58cbc741eb37482a Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 23 Mar 2016 16:46:33 -0400 Subject: [PATCH 013/337] Added windows clone options in vsphere and documented them --- .../resource_vsphere_virtual_machine.go | 163 ++++++++++++++---- .../vsphere/r/virtual_machine.html.markdown | 11 +- 2 files changed, 136 insertions(+), 38 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index a26afde3d609..cd0ce3ea1384 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -44,25 +44,35 @@ type hardDisk struct { initType string } +//Additional options Vsphere can use clones of windows machines +type windowsOptConfig struct { + productKey string + adminPassword string + domainUser string + domain string + domainUserPassword string +} + type virtualMachine struct { - name string - folder string - datacenter string - cluster string - resourcePool string - datastore string - linkedClone bool - vcpu int - memoryMb int64 - template string - networkInterfaces []networkInterface - hardDisks []hardDisk - gateway string - domain string - timeZone string - dnsSuffixes []string - dnsServers []string - customConfigurations map[string](types.AnyType) + name string + folder string + datacenter string + cluster string + resourcePool string + datastore string + linkedClone bool + vcpu int + memoryMb int64 + template string + networkInterfaces []networkInterface + hardDisks []hardDisk + gateway string + domain string + timeZone string + dnsSuffixes []string + dnsServers []string + windowsOptionalConfig windowsOptConfig + customConfigurations map[string](types.AnyType) } func (v virtualMachine) Path() string { @@ -171,6 +181,44 @@ func resourceVSphereVirtualMachine() *schema.Resource { Optional: true, ForceNew: true, }, + "windows_opt_config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "product_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "admin_password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "domain_user": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "domain": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "domain_user_password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, "network_interface": &schema.Schema{ Type: schema.TypeList, @@ -386,6 +434,28 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ log.Printf("[DEBUG] network_interface init: %v", networks) } + if vL, ok := d.GetOk("windows_opt_config"); ok { + var winOpt windowsOptConfig + custom_configs := (vL.([]interface{}))[0].(map[string]interface{}) + if v, ok := custom_configs["admin_password"].(string); ok && v != "" { + winOpt.adminPassword = v + } + if v, ok := custom_configs["domain"].(string); ok && v != "" { + winOpt.domain = v + } + if v, ok := custom_configs["domain_user"].(string); ok && v != "" { + winOpt.domainUser = v + } + if v, ok := custom_configs["product_key"].(string); ok && v != "" { + winOpt.productKey = v + } + if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" { + winOpt.domainUserPassword = v + } + vm.windowsOptionalConfig = winOpt + log.Printf("[DEBUG] windows config init: %v", winOpt) + } + if vL, ok := d.GetOk("disk"); ok { disks := make([]hardDisk, len(vL.([]interface{}))) for i, v := range vL.([]interface{}) { @@ -1207,27 +1277,46 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { var timeZone int timeZone, err := strconv.Atoi(vm.timeZone) if err != nil { - return fmt.Errorf("Error reading base VM properties: %s", err) + return fmt.Errorf("Error converting TimeZone: %s", err) } - identity_options = &types.CustomizationSysprep{ - GuiUnattended: types.CustomizationGuiUnattended{ - AutoLogon: false, - AutoLogonCount: 1, - Password: &types.CustomizationPassword{ - PlainText: true, - Value: "NULL", - }, - TimeZone: timeZone, - }, - Identification: types.CustomizationIdentification{}, - UserData: types.CustomizationUserData{ - ComputerName: &types.CustomizationFixedName{ - Name: strings.Split(vm.name, ".")[0], - }, - FullName: "LSTTE", - OrgName: "LSTTE", - ProductId: "ruh roh", + + guiUnattended := types.CustomizationGuiUnattended{ + AutoLogon: false, + AutoLogonCount: 1, + TimeZone: timeZone, + } + + customIdentification := types.CustomizationIdentification{} + + userData := types.CustomizationUserData{ + ComputerName: &types.CustomizationFixedName{ + Name: strings.Split(vm.name, ".")[0], }, + ProductId: vm.windowsOptionalConfig.productKey, + FullName: "terraform", + OrgName: "terraform", + } + + if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { + customIdentification.DomainAdminPassword = &types.CustomizationPassword{ + PlainText: true, + Value: vm.windowsOptionalConfig.domainUserPassword, + } + customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser + customIdentification.JoinDomain = vm.windowsOptionalConfig.domain + } + + if vm.windowsOptionalConfig.adminPassword != "" { + guiUnattended.Password = &types.CustomizationPassword{ + PlainText: true, + Value: vm.windowsOptionalConfig.adminPassword, + } + } + + identity_options = &types.CustomizationSysprep{ + GuiUnattended: guiUnattended, + Identification: customIdentification, + UserData: userData, } } else { identity_options = &types.CustomizationLinuxPrep{ diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index 9812a0aed870..92b038ecd354 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -41,12 +41,14 @@ The following arguments are supported: * `resource_pool` (Optional) The name of a Resource Pool in which to launch the virtual machine * `gateway` - (Optional) Gateway IP address to use for all network interfaces * `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local" -* `time_zone` - (Optional) The [time zone](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) to set on the virtual machine. Defaults to "Etc/UTC" +* `time_zone` - (Optional) The [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time zone to set on the virtual machine. Defaults to "Etc/UTC" * `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter * `dns_servers` - (Optional) List of DNS servers for the virtual network adapter; defaults to 8.8.8.8, 8.8.4.4 * `network_interface` - (Required) Configures virtual network interfaces; see [Network Interfaces](#network-interfaces) below for details. * `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for details * `boot_delay` - (Optional) Time in seconds to wait for machine network to be ready. +* `windows_opt_config` - (Optional) Extra options for clones of Windows machines. +* `linkedClone` - (Optional) Specifies if the new machine is a [linked clone](https://www.vmware.com/support/ws5/doc/ws_clone_overview.html#wp1036396) of another machine or not. * `custom_configuration_parameters` - (Optional) Map of values that is set as virtual machine custom configurations. The `network_interface` block supports: @@ -61,6 +63,13 @@ removed in a future version: * `ip_address` - __Deprecated, please use `ipv4_address` instead_. * `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead_. +The `windows_opt_config` block supports: + +* `product_key` - (Optional) Serial number for new installation of Windows. This serial number is ignored if the original guest operating system was installed using a volume-licensed CD. +* `admin_password` - (Optional) The password for the new `administrator` account. Omit for passwordless admin (using `""` does not work). +* `domain` - (Optional) Domain that the new machine will be placed into. If `domain`, `domain_user`, and `domain_user_password` are not all set, all three will be ignored. +* `domain_user` - (Optional) User that is a member of the specified domain. +* `domain_user_password` - (Optional) Password for domain user, in plain text. The `disk` block supports: From 338cb956ba329fbc7deb378bd362535b5e6e525b Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Fri, 8 Apr 2016 08:06:22 -0400 Subject: [PATCH 014/337] Rearranging code to clean up git diff --- builtin/providers/vsphere/resource_vsphere_virtual_machine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index cd0ce3ea1384..008c1e7944fd 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -60,7 +60,6 @@ type virtualMachine struct { cluster string resourcePool string datastore string - linkedClone bool vcpu int memoryMb int64 template string @@ -71,6 +70,7 @@ type virtualMachine struct { timeZone string dnsSuffixes []string dnsServers []string + linkedClone bool windowsOptionalConfig windowsOptConfig customConfigurations map[string](types.AnyType) } From 815c8840a78d3f996508e3c1ba6f71e2b2ed8d83 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 11 Apr 2016 17:14:19 +0200 Subject: [PATCH 015/337] Refactor the use of names vs IDs for parameters referencing other TF resources We have a curtesy function in place allowing you to specify both a `name` of `ID`. But in order for the graph to be build correctly when you recreate or taint stuff that other resources depend on, we need to reference the `ID` and *not* the `name`. So in order to enforce this and by that help people to not make this mistake unknowingly, I deprecated all the parameters this allies to and changed the logic, docs and tests accordingly. --- .../cloudstack/resource_cloudstack_disk.go | 10 +- .../resource_cloudstack_disk_test.go | 6 +- .../resource_cloudstack_egress_firewall.go | 26 +++++- ...esource_cloudstack_egress_firewall_test.go | 10 +- .../resource_cloudstack_firewall.go | 10 +- .../resource_cloudstack_firewall_test.go | 10 +- .../resource_cloudstack_instance.go | 36 +++++--- .../resource_cloudstack_instance_test.go | 14 +-- .../resource_cloudstack_ipaddress.go | 72 +++++++++------ .../resource_cloudstack_ipaddress_test.go | 10 +- .../resource_cloudstack_loadbalancer_rule.go | 91 ++++++++++++------- ...ource_cloudstack_loadbalancer_rule_test.go | 48 +++++----- .../cloudstack/resource_cloudstack_network.go | 67 ++++++++++---- .../resource_cloudstack_network_acl.go | 32 ++++--- .../resource_cloudstack_network_acl_rule.go | 26 +++++- ...source_cloudstack_network_acl_rule_test.go | 8 +- .../resource_cloudstack_network_acl_test.go | 4 +- .../resource_cloudstack_network_test.go | 4 +- .../cloudstack/resource_cloudstack_nic.go | 83 ++++++++++++++--- .../resource_cloudstack_nic_test.go | 20 ++-- .../resource_cloudstack_port_forward.go | 48 ++++++---- .../resource_cloudstack_port_forward_test.go | 48 ++-------- ...resource_cloudstack_secondary_ipaddress.go | 70 +++++++++++--- ...rce_cloudstack_secondary_ipaddress_test.go | 38 +++++--- .../resource_cloudstack_ssh_keypair.go | 4 + .../resource_cloudstack_ssh_keypair_test.go | 9 +- .../resource_cloudstack_static_nat.go | 33 ++----- .../resource_cloudstack_static_nat_test.go | 19 ++-- .../resource_cloudstack_template.go | 10 +- .../cloudstack/resource_cloudstack_vpc.go | 15 +-- .../resource_cloudstack_vpn_connection.go | 50 ++++++++-- ...resource_cloudstack_vpn_connection_test.go | 12 +-- ...ce_cloudstack_vpn_customer_gateway_test.go | 8 +- .../resource_cloudstack_vpn_gateway.go | 32 +++++-- .../resource_cloudstack_vpn_gateway_test.go | 4 +- .../r/egress_firewall.html.markdown | 9 +- .../cloudstack/r/firewall.html.markdown | 6 +- .../cloudstack/r/instance.html.markdown | 7 +- .../cloudstack/r/ipaddress.html.markdown | 18 +++- .../r/loadbalancer_rule.html.markdown | 27 ++++-- .../cloudstack/r/network.html.markdown | 12 ++- .../cloudstack/r/network_acl.html.markdown | 11 ++- .../r/network_acl_rule.html.markdown | 7 +- .../providers/cloudstack/r/nic.html.markdown | 21 +++-- .../cloudstack/r/port_forward.html.markdown | 13 ++- .../r/secondary_ipaddress.html.markdown | 26 ++++-- .../cloudstack/r/static_nat.html.markdown | 22 ++--- .../cloudstack/r/template.html.markdown | 9 +- .../cloudstack/r/vpn_connection.html.markdown | 14 ++- .../cloudstack/r/vpn_gateway.html.markdown | 7 +- 50 files changed, 742 insertions(+), 454 deletions(-) diff --git a/builtin/providers/cloudstack/resource_cloudstack_disk.go b/builtin/providers/cloudstack/resource_cloudstack_disk.go index 63a788f66237..7e5b79b74a31 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_disk.go +++ b/builtin/providers/cloudstack/resource_cloudstack_disk.go @@ -94,14 +94,8 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // Retrieve the zone ID diff --git a/builtin/providers/cloudstack/resource_cloudstack_disk_test.go b/builtin/providers/cloudstack/resource_cloudstack_disk_test.go index 5eee8ed8dd4c..e22c649f8abc 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_disk_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_disk_test.go @@ -175,7 +175,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -200,7 +200,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -224,7 +224,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true diff --git a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go index 0ff330ef40a2..3744cf8fdbf3 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go +++ b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "strconv" "strings" @@ -20,10 +21,19 @@ func resourceCloudStackEgressFirewall() *schema.Resource { Delete: resourceCloudStackEgressFirewallDelete, Schema: map[string]*schema.Schema{ + "network_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"network"}, + }, + "network": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `network_id` field instead", + ConflictsWith: []string{"network_id"}, }, "managed": &schema.Schema{ @@ -99,8 +109,16 @@ func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interfa return err } + network, ok := d.GetOk("network_id") + if !ok { + network, ok = d.GetOk("network") + } + if !ok { + return errors.New("Either `network_id` or [deprecated] `network` must be provided.") + } + // Retrieve the network ID - networkid, e := retrieveID(cs, "network", d.Get("network").(string)) + networkid, e := retrieveID(cs, "network", network.(string)) if e != nil { return e.Error() } diff --git a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall_test.go b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall_test.go index 07f4e0d8a247..cc640ac951f5 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_egress_firewall_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_egress_firewall_test.go @@ -21,7 +21,7 @@ func TestAccCloudStackEgressFirewall_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1), + "cloudstack_egress_firewall.foo", "network_id", CLOUDSTACK_NETWORK_1), resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( @@ -59,7 +59,7 @@ func TestAccCloudStackEgressFirewall_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1), + "cloudstack_egress_firewall.foo", "network_id", CLOUDSTACK_NETWORK_1), resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( @@ -88,7 +88,7 @@ func TestAccCloudStackEgressFirewall_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackEgressFirewallRulesExist("cloudstack_egress_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_egress_firewall.foo", "network", CLOUDSTACK_NETWORK_1), + "cloudstack_egress_firewall.foo", "network_id", CLOUDSTACK_NETWORK_1), resource.TestCheckResourceAttr( "cloudstack_egress_firewall.foo", "rule.#", "3"), resource.TestCheckResourceAttr( @@ -188,7 +188,7 @@ func testAccCheckCloudStackEgressFirewallDestroy(s *terraform.State) error { var testAccCloudStackEgressFirewall_basic = fmt.Sprintf(` resource "cloudstack_egress_firewall" "foo" { - network = "%s" + network_id = "%s" rule { cidr_list = ["%s/32"] @@ -208,7 +208,7 @@ resource "cloudstack_egress_firewall" "foo" { var testAccCloudStackEgressFirewall_update = fmt.Sprintf(` resource "cloudstack_egress_firewall" "foo" { - network = "%s" + network_id = "%s" rule { cidr_list = ["%s/32", "%s/32"] diff --git a/builtin/providers/cloudstack/resource_cloudstack_firewall.go b/builtin/providers/cloudstack/resource_cloudstack_firewall.go index f10f5a6384b6..3b8ebe13c118 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_firewall.go +++ b/builtin/providers/cloudstack/resource_cloudstack_firewall.go @@ -21,7 +21,7 @@ func resourceCloudStackFirewall() *schema.Resource { Delete: resourceCloudStackFirewallDelete, Schema: map[string]*schema.Schema{ - "ip_address": &schema.Schema{ + "ip_address_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -32,8 +32,8 @@ func resourceCloudStackFirewall() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Deprecated: "Please use the `ip_address` field instead", - ConflictsWith: []string{"ip_address"}, + Deprecated: "Please use the `ip_address_id` field instead", + ConflictsWith: []string{"ip_address_id"}, }, "managed": &schema.Schema{ @@ -109,12 +109,12 @@ func resourceCloudStackFirewallCreate(d *schema.ResourceData, meta interface{}) return err } - ipaddress, ok := d.GetOk("ip_address") + ipaddress, ok := d.GetOk("ip_address_id") if !ok { ipaddress, ok = d.GetOk("ipaddress") } if !ok { - return errors.New("Either `ip_address` or [deprecated] `ipaddress` must be provided.") + return errors.New("Either `ip_address_id` or [deprecated] `ipaddress` must be provided.") } // Retrieve the ipaddress ID diff --git a/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go b/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go index f7fda8110bbf..1b4f48959b71 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_firewall_test.go @@ -21,7 +21,7 @@ func TestAccCloudStackFirewall_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_firewall.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( @@ -55,7 +55,7 @@ func TestAccCloudStackFirewall_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_firewall.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "2"), resource.TestCheckResourceAttr( @@ -80,7 +80,7 @@ func TestAccCloudStackFirewall_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackFirewallRulesExist("cloudstack_firewall.foo"), resource.TestCheckResourceAttr( - "cloudstack_firewall.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_firewall.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( "cloudstack_firewall.foo", "rule.#", "3"), resource.TestCheckResourceAttr( @@ -174,7 +174,7 @@ func testAccCheckCloudStackFirewallDestroy(s *terraform.State) error { var testAccCloudStackFirewall_basic = fmt.Sprintf(` resource "cloudstack_firewall" "foo" { - ip_address = "%s" + ip_address_id = "%s" rule { cidr_list = ["10.0.0.0/24"] @@ -191,7 +191,7 @@ resource "cloudstack_firewall" "foo" { var testAccCloudStackFirewall_update = fmt.Sprintf(` resource "cloudstack_firewall" "foo" { - ip_address = "%s" + ip_address_id = "%s" rule { cidr_list = ["10.0.0.0/24", "10.0.1.0/24"] diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance.go b/builtin/providers/cloudstack/resource_cloudstack_instance.go index 6408faaa0f09..78e64788ec20 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance.go @@ -4,6 +4,7 @@ import ( "crypto/sha1" "encoding/base64" "encoding/hex" + "errors" "fmt" "log" "strings" @@ -37,12 +38,20 @@ func resourceCloudStackInstance() *schema.Resource { Required: true, }, - "network": &schema.Schema{ + "network_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `network_id` field instead", + }, + "ip_address": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -149,11 +158,21 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) } if zone.Networktype == "Advanced" { + network, ok := d.GetOk("network_id") + if !ok { + network, ok = d.GetOk("network") + } + if !ok { + return errors.New( + "Either `network_id` or [deprecated] `network` must be provided when using a zone with network type `advanced`.") + } + // Retrieve the network ID - networkid, e := retrieveID(cs, "network", d.Get("network").(string)) + networkid, e := retrieveID(cs, "network", network.(string)) if e != nil { return e.Error() } + // Set the default network ID p.SetNetworkids([]string{networkid}) } @@ -168,14 +187,8 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // If a keypair is supplied, add it to the parameter struct @@ -240,10 +253,9 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er // Update the config d.Set("name", vm.Name) d.Set("display_name", vm.Displayname) + d.Set("network_id", vm.Nic[0].Networkid) d.Set("ip_address", vm.Nic[0].Ipaddress) - //NB cloudstack sometimes sends back the wrong keypair name, so dont update it - setValueOrID(d, "network", vm.Nic[0].Networkname, vm.Nic[0].Networkid) setValueOrID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid) setValueOrID(d, "template", vm.Templatename, vm.Templateid) setValueOrID(d, "project", vm.Project, vm.Projectid) diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance_test.go b/builtin/providers/cloudstack/resource_cloudstack_instance_test.go index f6416b8cf211..2d9743d30d9e 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance_test.go @@ -180,8 +180,8 @@ func testAccCheckCloudStackInstanceAttributes( return fmt.Errorf("Bad template: %s", instance.Templatename) } - if instance.Nic[0].Networkname != CLOUDSTACK_NETWORK_1 { - return fmt.Errorf("Bad network: %s", instance.Nic[0].Networkname) + if instance.Nic[0].Networkid != CLOUDSTACK_NETWORK_1 { + return fmt.Errorf("Bad network ID: %s", instance.Nic[0].Networkid) } return nil @@ -234,7 +234,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" user_data = "foobar\nfoo\nbar" @@ -250,7 +250,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-updated" display_name = "terraform-updated" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" user_data = "foobar\nfoo\nbar" @@ -266,7 +266,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" ip_address = "%s" template = "%s" zone = "%s" @@ -287,7 +287,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" ip_address = "%s" template = "%s" zone = "%s" @@ -305,7 +305,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" project = "%s" zone = "%s" diff --git a/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go b/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go index 4c140639a8f6..2c21d222e02f 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ipaddress.go @@ -16,18 +16,34 @@ func resourceCloudStackIPAddress() *schema.Resource { Delete: resourceCloudStackIPAddressDelete, Schema: map[string]*schema.Schema{ - "network": &schema.Schema{ + "network_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, - "vpc": &schema.Schema{ + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `network_id` field instead", + }, + + "vpc_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, + "vpc": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `vpc_id` field instead", + }, + "project": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -52,7 +68,11 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) // Create a new parameter struct p := cs.Address.NewAssociateIpAddressParams() - if network, ok := d.GetOk("network"); ok { + network, ok := d.GetOk("network_id") + if !ok { + network, ok = d.GetOk("network") + } + if ok { // Retrieve the network ID networkid, e := retrieveID(cs, "network", network.(string)) if e != nil { @@ -63,7 +83,11 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) p.SetNetworkid(networkid) } - if vpc, ok := d.GetOk("vpc"); ok { + vpc, ok := d.GetOk("vpc_id") + if !ok { + vpc, ok = d.GetOk("vpc") + } + if ok { // Retrieve the vpc ID vpcid, e := retrieveID(cs, "vpc", vpc.(string)) if e != nil { @@ -75,14 +99,8 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // Associate a new IP address @@ -115,24 +133,16 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e // Updated the IP address d.Set("ip_address", ip.Ipaddress) - if _, ok := d.GetOk("network"); ok { - // Get the network details - n, _, err := cs.Network.GetNetworkByID(ip.Associatednetworkid) - if err != nil { - return err - } - - setValueOrID(d, "network", n.Name, ip.Associatednetworkid) + _, networkID := d.GetOk("network_id") + _, network := d.GetOk("network") + if networkID || network { + d.Set("network_id", ip.Associatednetworkid) } - if _, ok := d.GetOk("vpc"); ok { - // Get the VPC details - v, _, err := cs.VPC.GetVPCByID(ip.Vpcid) - if err != nil { - return err - } - - setValueOrID(d, "vpc", v.Name, ip.Vpcid) + _, vpcID := d.GetOk("vpc_id") + _, vpc := d.GetOk("vpc") + if vpcID || vpc { + d.Set("vpc_id", ip.Vpcid) } setValueOrID(d, "project", ip.Project, ip.Projectid) @@ -162,12 +172,14 @@ func resourceCloudStackIPAddressDelete(d *schema.ResourceData, meta interface{}) } func verifyIPAddressParams(d *schema.ResourceData) error { + _, networkID := d.GetOk("network_id") _, network := d.GetOk("network") + _, vpcID := d.GetOk("vpc_id") _, vpc := d.GetOk("vpc") - if network && vpc || !network && !vpc { + if (networkID || network) && (vpcID || vpc) || (!networkID && !network) && (!vpcID && !vpc) { return fmt.Errorf( - "You must supply a value for either (so not both) the 'network' or 'vpc' parameter") + "You must supply a value for either (so not both) the 'network_id' or 'vpc_id' parameter") } return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_ipaddress_test.go b/builtin/providers/cloudstack/resource_cloudstack_ipaddress_test.go index edf120573f73..6b74e96922d9 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ipaddress_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ipaddress_test.go @@ -42,8 +42,6 @@ func TestAccCloudStackIPAddress_vpc(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackIPAddressExists( "cloudstack_ipaddress.foo", &ipaddr), - resource.TestCheckResourceAttr( - "cloudstack_ipaddress.foo", "vpc", "terraform-vpc"), ), }, }, @@ -83,8 +81,8 @@ func testAccCheckCloudStackIPAddressAttributes( ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc { return func(s *terraform.State) error { - if ipaddr.Associatednetworkname != CLOUDSTACK_NETWORK_1 { - return fmt.Errorf("Bad network: %s", ipaddr.Associatednetworkname) + if ipaddr.Associatednetworkid != CLOUDSTACK_NETWORK_1 { + return fmt.Errorf("Bad network ID: %s", ipaddr.Associatednetworkid) } return nil @@ -114,7 +112,7 @@ func testAccCheckCloudStackIPAddressDestroy(s *terraform.State) error { var testAccCloudStackIPAddress_basic = fmt.Sprintf(` resource "cloudstack_ipaddress" "foo" { - network = "%s" + network_id = "%s" }`, CLOUDSTACK_NETWORK_1) var testAccCloudStackIPAddress_vpc = fmt.Sprintf(` @@ -126,7 +124,7 @@ resource "cloudstack_vpc" "foobar" { } resource "cloudstack_ipaddress" "foo" { - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, diff --git a/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule.go b/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule.go index d4f3143ccc48..829d7296e766 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -29,27 +29,34 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { Computed: true, }, - "ip_address": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"ipaddress"}, + "ip_address_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, }, "ipaddress": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Deprecated: "Please use the `ip_address` field instead", - ConflictsWith: []string{"ip_address"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `ip_address_id` field instead", }, - "network": &schema.Schema{ + "network_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `network_id` field instead", + }, + "algorithm": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -67,11 +74,21 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { ForceNew: true, }, + "member_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ConflictsWith: []string{"members"}, + }, + "members": &schema.Schema{ - Type: schema.TypeList, - Required: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Deprecated: "Please use the `member_ids` field instead", + ConflictsWith: []string{"member_ids"}, }, }, } @@ -99,23 +116,27 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter p.SetDescription(d.Get("name").(string)) } - // Retrieve the network and the ID - if network, ok := d.GetOk("network"); ok { + network, ok := d.GetOk("network_id") + if !ok { + network, ok = d.GetOk("network") + } + if ok { + // Retrieve the network ID networkid, e := retrieveID(cs, "network", network.(string)) if e != nil { return e.Error() } - // Set the default network ID + // Set the networkid p.SetNetworkid(networkid) } - ipaddress, ok := d.GetOk("ip_address") + ipaddress, ok := d.GetOk("ip_address_id") if !ok { ipaddress, ok = d.GetOk("ipaddress") } if !ok { - return errors.New("Either `ip_address` or [deprecated] `ipaddress` must be provided.") + return errors.New("Either `ip_address_id` or [deprecated] `ipaddress` must be provided.") } // Retrieve the ipaddress ID @@ -135,8 +156,8 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter d.SetId(r.Id) d.SetPartial("name") d.SetPartial("description") - d.SetPartial("ip_address") - d.SetPartial("network") + d.SetPartial("ip_address_id") + d.SetPartial("network_id") d.SetPartial("algorithm") d.SetPartial("private_port") d.SetPartial("public_port") @@ -144,8 +165,16 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter // Create a new parameter struct ap := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(r.Id) + members, ok := d.GetOk("member_ids") + if !ok { + members, ok = d.GetOk("members") + } + if !ok { + return errors.New("Either `member_ids` or [deprecated] `members` must be provided.") + } + var mbs []string - for _, id := range d.Get("members").([]interface{}) { + for _, id := range members.([]interface{}) { mbs = append(mbs, id.(string)) } @@ -156,9 +185,10 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter return err } + d.SetPartial("member_ids") d.SetPartial("members") - d.Partial(false) + return resourceCloudStackLoadBalancerRuleRead(d, meta) } @@ -180,16 +210,13 @@ func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interfa d.Set("algorithm", lb.Algorithm) d.Set("public_port", lb.Publicport) d.Set("private_port", lb.Privateport) - - setValueOrID(d, "ip_address", lb.Publicip, lb.Publicipid) + d.Set("ip_address_id", lb.Publicipid) // Only set network if user specified it to avoid spurious diffs - if _, ok := d.GetOk("network"); ok { - network, _, err := cs.Network.GetNetworkByID(lb.Networkid) - if err != nil { - return err - } - setValueOrID(d, "network", network.Name, lb.Networkid) + _, networkID := d.GetOk("network_id") + _, network := d.GetOk("network") + if networkID || network { + d.Set("network_id", lb.Networkid) } return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule_test.go b/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule_test.go index b34c4f555f59..9d3f6ec1e651 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_loadbalancer_rule_test.go @@ -75,7 +75,7 @@ func TestAccCloudStackLoadBalancerRule_update(t *testing.T) { }) } -func TestAccCloudStackLoadBalancerRule_forcenew(t *testing.T) { +func TestAccCloudStackLoadBalancerRule_forceNew(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -138,7 +138,7 @@ func TestAccCloudStackLoadBalancerRule_vpc(t *testing.T) { }) } -func TestAccCloudStackLoadBalancerRule_vpc_update(t *testing.T) { +func TestAccCloudStackLoadBalancerRule_vpcUpdate(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -243,7 +243,7 @@ resource "cloudstack_instance" "foobar1" { name = "terraform-server1" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -251,11 +251,11 @@ resource "cloudstack_instance" "foobar1" { resource "cloudstack_loadbalancer_rule" "foo" { name = "terraform-lb" - ip_address = "%s" + ip_address_id = "%s" algorithm = "roundrobin" public_port = 80 private_port = 80 - members = ["${cloudstack_instance.foobar1.id}"] + member_ids = ["${cloudstack_instance.foobar1.id}"] } `, CLOUDSTACK_SERVICE_OFFERING_1, @@ -269,7 +269,7 @@ resource "cloudstack_instance" "foobar1" { name = "terraform-server1" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -277,11 +277,11 @@ resource "cloudstack_instance" "foobar1" { resource "cloudstack_loadbalancer_rule" "foo" { name = "terraform-lb-update" - ip_address = "%s" + ip_address_id = "%s" algorithm = "leastconn" public_port = 80 private_port = 80 - members = ["${cloudstack_instance.foobar1.id}"] + member_ids = ["${cloudstack_instance.foobar1.id}"] } `, CLOUDSTACK_SERVICE_OFFERING_1, @@ -295,7 +295,7 @@ resource "cloudstack_instance" "foobar1" { name = "terraform-server1" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -303,11 +303,11 @@ resource "cloudstack_instance" "foobar1" { resource "cloudstack_loadbalancer_rule" "foo" { name = "terraform-lb-update" - ip_address = "%s" + ip_address_id = "%s" algorithm = "leastconn" public_port = 443 private_port = 443 - members = ["${cloudstack_instance.foobar1.id}"] + member_ids = ["${cloudstack_instance.foobar1.id}"] } `, CLOUDSTACK_SERVICE_OFFERING_1, @@ -328,19 +328,19 @@ resource "cloudstack_network" "foo" { name = "terraform-network" cidr = "%s" network_offering = "%s" - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" zone = "${cloudstack_vpc.foobar.zone}" } resource "cloudstack_ipaddress" "foo" { - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" } resource "cloudstack_instance" "foobar1" { name = "terraform-server1" display_name = "terraform" service_offering= "%s" - network = "${cloudstack_network.foo.name}" + network_id = "${cloudstack_network.foo.id}" template = "%s" zone = "${cloudstack_network.foo.zone}" expunge = true @@ -348,12 +348,12 @@ resource "cloudstack_instance" "foobar1" { resource "cloudstack_loadbalancer_rule" "foo" { name = "terraform-lb" - ip_address = "${cloudstack_ipaddress.foo.ip_address}" + ip_address_id = "${cloudstack_ipaddress.foo.id}" algorithm = "roundrobin" - network = "${cloudstack_network.foo.id}" + network_id = "${cloudstack_network.foo.id}" public_port = 80 private_port = 80 - members = ["${cloudstack_instance.foobar1.id}"] + member_ids = ["${cloudstack_instance.foobar1.id}"] }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, @@ -375,19 +375,19 @@ resource "cloudstack_network" "foo" { name = "terraform-network" cidr = "%s" network_offering = "%s" - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" zone = "${cloudstack_vpc.foobar.zone}" } resource "cloudstack_ipaddress" "foo" { - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" } resource "cloudstack_instance" "foobar1" { name = "terraform-server1" display_name = "terraform" service_offering= "%s" - network = "${cloudstack_network.foo.name}" + network_id = "${cloudstack_network.foo.id}" template = "%s" zone = "${cloudstack_network.foo.zone}" expunge = true @@ -397,7 +397,7 @@ resource "cloudstack_instance" "foobar2" { name = "terraform-server2" display_name = "terraform" service_offering= "%s" - network = "${cloudstack_network.foo.name}" + network_id = "${cloudstack_network.foo.id}" template = "%s" zone = "${cloudstack_network.foo.zone}" expunge = true @@ -405,12 +405,12 @@ resource "cloudstack_instance" "foobar2" { resource "cloudstack_loadbalancer_rule" "foo" { name = "terraform-lb-update" - ip_address = "${cloudstack_ipaddress.foo.ip_address}" + ip_address_id = "${cloudstack_ipaddress.foo.id}" algorithm = "leastconn" - network = "${cloudstack_network.foo.id}" + network_id = "${cloudstack_network.foo.id}" public_port = 443 private_port = 443 - members = ["${cloudstack_instance.foobar1.id}", "${cloudstack_instance.foobar2.id}"] + member_ids = ["${cloudstack_instance.foobar1.id}", "${cloudstack_instance.foobar2.id}"] }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, diff --git a/builtin/providers/cloudstack/resource_cloudstack_network.go b/builtin/providers/cloudstack/resource_cloudstack_network.go index 261d0ec508d1..c8df5187d6d5 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network.go @@ -68,16 +68,33 @@ func resourceCloudStackNetwork() *schema.Resource { ForceNew: true, }, - "vpc": &schema.Schema{ + "vpc_id": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, + "vpc": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `vpc_id` field instead", + }, + + "acl_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"aclid"}, + }, + "aclid": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `acl_id` field instead", }, "project": &schema.Schema{ @@ -138,34 +155,34 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e } // Check is this network needs to be created in a VPC - vpc := d.Get("vpc").(string) - if vpc != "" { + vpc, ok := d.GetOk("vpc_id") + if !ok { + vpc, ok = d.GetOk("vpc") + } + if ok { // Retrieve the vpc ID - vpcid, e := retrieveID(cs, "vpc", vpc) + vpcid, e := retrieveID(cs, "vpc", vpc.(string)) if e != nil { return e.Error() } - // Set the vpc ID + // Set the vpcid p.SetVpcid(vpcid) // Since we're in a VPC, check if we want to assiciate an ACL list - aclid := d.Get("aclid").(string) - if aclid != "" { + aclid, ok := d.GetOk("acl_id") + if !ok { + aclid, ok = d.GetOk("acl") + } + if ok { // Set the acl ID - p.SetAclid(aclid) + p.SetAclid(aclid.(string)) } } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // Create the new network @@ -205,6 +222,18 @@ func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) err d.Set("cidr", n.Cidr) d.Set("gateway", n.Gateway) + _, vpcID := d.GetOk("vpc_id") + _, vpc := d.GetOk("vpc") + if vpcID || vpc { + d.Set("vpc_id", n.Vpcid) + } + + _, aclID := d.GetOk("acl_id") + _, acl := d.GetOk("aclid") + if aclID || acl { + d.Set("acl_id", n.Aclid) + } + // Read the tags and store them in a map tags := make(map[string]interface{}) for item := range n.Tags { diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl.go index 2504b762bfab..c39c695d9b76 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "log" "strings" @@ -29,11 +30,19 @@ func resourceCloudStackNetworkACL() *schema.Resource { ForceNew: true, }, - "vpc": &schema.Schema{ + "vpc_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + + "vpc": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `vpc_id` field instead", + }, }, } } @@ -43,8 +52,16 @@ func resourceCloudStackNetworkACLCreate(d *schema.ResourceData, meta interface{} name := d.Get("name").(string) + vpc, ok := d.GetOk("vpc_id") + if !ok { + vpc, ok = d.GetOk("vpc") + } + if !ok { + return errors.New("Either `vpc_id` or [deprecated] `vpc` must be provided.") + } + // Retrieve the vpc ID - vpcid, e := retrieveID(cs, "vpc", d.Get("vpc").(string)) + vpcid, e := retrieveID(cs, "vpc", vpc.(string)) if e != nil { return e.Error() } @@ -88,14 +105,7 @@ func resourceCloudStackNetworkACLRead(d *schema.ResourceData, meta interface{}) d.Set("name", f.Name) d.Set("description", f.Description) - - // Get the VPC details - v, _, err := cs.VPC.GetVPCByID(f.Vpcid) - if err != nil { - return err - } - - setValueOrID(d, "vpc", v.Name, v.Id) + d.Set("vpc_id", f.Vpcid) return nil } diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go index 14e39d99c9e7..88de58f911ff 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "strconv" "strings" @@ -20,10 +21,19 @@ func resourceCloudStackNetworkACLRule() *schema.Resource { Delete: resourceCloudStackNetworkACLRuleDelete, Schema: map[string]*schema.Schema{ + "acl_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"aclid"}, + }, + "aclid": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `acl_id` field instead", + ConflictsWith: []string{"acl_id"}, }, "managed": &schema.Schema{ @@ -109,8 +119,16 @@ func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interfa return err } + aclid, ok := d.GetOk("acl_id") + if !ok { + aclid, ok = d.GetOk("aclid") + } + if !ok { + return errors.New("Either `acl_id` or [deprecated] `aclid` must be provided.") + } + // We need to set this upfront in order to be able to save a partial state - d.SetId(d.Get("aclid").(string)) + d.SetId(aclid.(string)) // Create all rules that are configured if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 { diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go index 862418f704e3..3fb978172a75 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule_test.go @@ -219,11 +219,11 @@ resource "cloudstack_vpc" "foobar" { resource "cloudstack_network_acl" "foo" { name = "terraform-acl" description = "terraform-acl-text" - vpc = "${cloudstack_vpc.foobar.id}" + vpc_id = "${cloudstack_vpc.foobar.id}" } resource "cloudstack_network_acl_rule" "foo" { - aclid = "${cloudstack_network_acl.foo.id}" + acl_id = "${cloudstack_network_acl.foo.id}" rule { action = "allow" @@ -263,11 +263,11 @@ resource "cloudstack_vpc" "foobar" { resource "cloudstack_network_acl" "foo" { name = "terraform-acl" description = "terraform-acl-text" - vpc = "${cloudstack_vpc.foobar.id}" + vpc_id = "${cloudstack_vpc.foobar.id}" } resource "cloudstack_network_acl_rule" "foo" { - aclid = "${cloudstack_network_acl.foo.id}" + acl_id = "${cloudstack_network_acl.foo.id}" rule { action = "deny" diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_acl_test.go b/builtin/providers/cloudstack/resource_cloudstack_network_acl_test.go index c8a58a8fe6ee..d6431c39956b 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_acl_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_acl_test.go @@ -22,8 +22,6 @@ func TestAccCloudStackNetworkACL_basic(t *testing.T) { testAccCheckCloudStackNetworkACLExists( "cloudstack_network_acl.foo", &acl), testAccCheckCloudStackNetworkACLBasicAttributes(&acl), - resource.TestCheckResourceAttr( - "cloudstack_network_acl.foo", "vpc", "terraform-vpc"), ), }, }, @@ -106,7 +104,7 @@ resource "cloudstack_vpc" "foobar" { resource "cloudstack_network_acl" "foo" { name = "terraform-acl" description = "terraform-acl-text" - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, diff --git a/builtin/providers/cloudstack/resource_cloudstack_network_test.go b/builtin/providers/cloudstack/resource_cloudstack_network_test.go index 3bc1744b9bf7..49400dad7e8b 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_network_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_network_test.go @@ -44,8 +44,6 @@ func TestAccCloudStackNetwork_vpc(t *testing.T) { testAccCheckCloudStackNetworkExists( "cloudstack_network.foo", &network), testAccCheckCloudStackNetworkVPCAttributes(&network), - resource.TestCheckResourceAttr( - "cloudstack_network.foo", "vpc", "terraform-vpc"), ), }, }, @@ -187,7 +185,7 @@ resource "cloudstack_network" "foo" { name = "terraform-network" cidr = "%s" network_offering = "%s" - vpc = "${cloudstack_vpc.foobar.name}" + vpc_id = "${cloudstack_vpc.foobar.id}" zone = "${cloudstack_vpc.foobar.zone}" }`, CLOUDSTACK_VPC_CIDR_1, diff --git a/builtin/providers/cloudstack/resource_cloudstack_nic.go b/builtin/providers/cloudstack/resource_cloudstack_nic.go index 6902f197e5fe..0baae852ead0 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_nic.go +++ b/builtin/providers/cloudstack/resource_cloudstack_nic.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "log" "strings" @@ -16,12 +17,20 @@ func resourceCloudStackNIC() *schema.Resource { Delete: resourceCloudStackNICDelete, Schema: map[string]*schema.Schema{ - "network": &schema.Schema{ + "network_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `network_id` field instead", + }, + "ip_address": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -32,16 +41,23 @@ func resourceCloudStackNIC() *schema.Resource { "ipaddress": &schema.Schema{ Type: schema.TypeString, Optional: true, - Computed: true, ForceNew: true, Deprecated: "Please use the `ip_address` field instead", }, - "virtual_machine": &schema.Schema{ + "virtual_machine_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + + "virtual_machine": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `virtual_machine_id` field instead", + }, }, } } @@ -49,14 +65,31 @@ func resourceCloudStackNIC() *schema.Resource { func resourceCloudStackNICCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + network, ok := d.GetOk("network_id") + if !ok { + network, ok = d.GetOk("network") + } + if !ok { + return errors.New("Either `network_id` or [deprecated] `network` must be provided.") + } + // Retrieve the network ID - networkid, e := retrieveID(cs, "network", d.Get("network").(string)) + networkid, e := retrieveID(cs, "network", network.(string)) if e != nil { return e.Error() } + virtualmachine, ok := d.GetOk("virtual_machine_id") + if !ok { + virtualmachine, ok = d.GetOk("virtual_machine") + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) if e != nil { return e.Error() } @@ -89,7 +122,7 @@ func resourceCloudStackNICCreate(d *schema.ResourceData, meta interface{}) error } if !found { - return fmt.Errorf("Could not find NIC ID for network: %s", d.Get("network").(string)) + return fmt.Errorf("Could not find NIC ID for network ID: %s", networkid) } return resourceCloudStackNICRead(d, meta) @@ -98,8 +131,23 @@ func resourceCloudStackNICCreate(d *schema.ResourceData, meta interface{}) error func resourceCloudStackNICRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + virtualmachine, ok := d.GetOk("virtual_machine_id") + if !ok { + virtualmachine, ok = d.GetOk("virtual_machine") + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + + // Retrieve the virtual_machine ID + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) + if e != nil { + return e.Error() + } + // Get the virtual machine details - vm, count, err := cs.VirtualMachine.GetVirtualMachineByName(d.Get("virtual_machine").(string)) + vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) if err != nil { if count == 0 { log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string)) @@ -115,15 +163,15 @@ func resourceCloudStackNICRead(d *schema.ResourceData, meta interface{}) error { for _, n := range vm.Nic { if n.Id == d.Id() { d.Set("ip_address", n.Ipaddress) - setValueOrID(d, "network", n.Networkname, n.Networkid) - setValueOrID(d, "virtual_machine", vm.Name, vm.Id) + d.Set("network_id", n.Networkid) + d.Set("virtual_machine_id", vm.Id) found = true break } } if !found { - log.Printf("[DEBUG] NIC for network %s does no longer exist", d.Get("network").(string)) + log.Printf("[DEBUG] NIC for network ID %s does no longer exist", d.Get("network_id").(string)) d.SetId("") } @@ -133,8 +181,17 @@ func resourceCloudStackNICRead(d *schema.ResourceData, meta interface{}) error { func resourceCloudStackNICDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + virtualmachine, ok := d.GetOk("virtual_machine_id") + if !ok { + virtualmachine, ok = d.GetOk("virtual_machine") + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) if e != nil { return e.Error() } diff --git a/builtin/providers/cloudstack/resource_cloudstack_nic_test.go b/builtin/providers/cloudstack/resource_cloudstack_nic_test.go index 249c02d89d9c..a7e6fcff6d30 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_nic_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_nic_test.go @@ -103,8 +103,8 @@ func testAccCheckCloudStackNICAttributes( nic *cloudstack.Nic) resource.TestCheckFunc { return func(s *terraform.State) error { - if nic.Networkname != CLOUDSTACK_2ND_NIC_NETWORK { - return fmt.Errorf("Bad network: %s", nic.Networkname) + if nic.Networkid != CLOUDSTACK_2ND_NIC_NETWORK { + return fmt.Errorf("Bad network ID: %s", nic.Networkid) } return nil @@ -115,8 +115,8 @@ func testAccCheckCloudStackNICIPAddress( nic *cloudstack.Nic) resource.TestCheckFunc { return func(s *terraform.State) error { - if nic.Networkname != CLOUDSTACK_2ND_NIC_NETWORK { - return fmt.Errorf("Bad network: %s", nic.Networkname) + if nic.Networkid != CLOUDSTACK_2ND_NIC_NETWORK { + return fmt.Errorf("Bad network ID: %s", nic.Networkname) } if nic.Ipaddress != CLOUDSTACK_2ND_NIC_IPADDRESS { @@ -154,15 +154,15 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true } resource "cloudstack_nic" "foo" { - network = "%s" - virtual_machine = "${cloudstack_instance.foobar.name}" + network_id = "%s" + virtual_machine_id = "${cloudstack_instance.foobar.id}" }`, CLOUDSTACK_SERVICE_OFFERING_1, CLOUDSTACK_NETWORK_1, @@ -175,16 +175,16 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true } resource "cloudstack_nic" "foo" { - network = "%s" + network_id = "%s" ip_address = "%s" - virtual_machine = "${cloudstack_instance.foobar.name}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" }`, CLOUDSTACK_SERVICE_OFFERING_1, CLOUDSTACK_NETWORK_1, diff --git a/builtin/providers/cloudstack/resource_cloudstack_port_forward.go b/builtin/providers/cloudstack/resource_cloudstack_port_forward.go index 64fd6a3bb95c..ca84f91bab48 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_port_forward.go +++ b/builtin/providers/cloudstack/resource_cloudstack_port_forward.go @@ -22,7 +22,7 @@ func resourceCloudStackPortForward() *schema.Resource { Delete: resourceCloudStackPortForwardDelete, Schema: map[string]*schema.Schema{ - "ip_address": &schema.Schema{ + "ip_address_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -33,8 +33,8 @@ func resourceCloudStackPortForward() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Deprecated: "Please use the `ip_address` field instead", - ConflictsWith: []string{"ip_address"}, + Deprecated: "Please use the `ip_address_id` field instead", + ConflictsWith: []string{"ip_address_id"}, }, "managed": &schema.Schema{ @@ -69,9 +69,15 @@ func resourceCloudStackPortForward() *schema.Resource { Required: true, }, - "virtual_machine": &schema.Schema{ + "virtual_machine_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + }, + + "virtual_machine": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Deprecated: "Please use the `virtual_machine_id` field instead", }, "uuid": &schema.Schema{ @@ -88,12 +94,12 @@ func resourceCloudStackPortForward() *schema.Resource { func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - ipaddress, ok := d.GetOk("ip_address") + ipaddress, ok := d.GetOk("ip_address_id") if !ok { ipaddress, ok = d.GetOk("ipaddress") } if !ok { - return errors.New("Either `ip_address` or [deprecated] `ipaddress` must be provided.") + return errors.New("Either `ip_address_id` or [deprecated] `ipaddress` must be provided.") } // Retrieve the ipaddress ID @@ -173,8 +179,17 @@ func createPortForward( return err } + virtualmachine, ok := forward["virtual_machine_id"] + if !ok { + virtualmachine, ok = forward["virtual_machine"] + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", forward["virtual_machine"].(string)) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) if e != nil { return e.Error() } @@ -265,12 +280,7 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) forward["protocol"] = f.Protocol forward["private_port"] = privPort forward["public_port"] = pubPort - - if isID(forward["virtual_machine"].(string)) { - forward["virtual_machine"] = f.Virtualmachineid - } else { - forward["virtual_machine"] = f.Virtualmachinename - } + forward["virtual_machine_id"] = f.Virtualmachineid forwards.Add(forward) } @@ -282,11 +292,11 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) for uuid := range forwardMap { // Make a dummy forward to hold the unknown UUID forward := map[string]interface{}{ - "protocol": uuid, - "private_port": 0, - "public_port": 0, - "virtual_machine": uuid, - "uuid": uuid, + "protocol": uuid, + "private_port": 0, + "public_port": 0, + "virtual_machine_id": uuid, + "uuid": uuid, } // Add the dummy forward to the forwards set diff --git a/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go b/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go index 8e9104ea13fd..f9038b21c839 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_port_forward_test.go @@ -21,15 +21,9 @@ func TestAccCloudStackPortForward_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"), resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_port_forward.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.protocol", "tcp"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.private_port", "443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.public_port", "8443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.virtual_machine", "terraform-test"), + "cloudstack_port_forward.foo", "forward.#", "1"), ), }, }, @@ -47,17 +41,9 @@ func TestAccCloudStackPortForward_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"), resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_port_forward.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( "cloudstack_port_forward.foo", "forward.#", "1"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.protocol", "tcp"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.private_port", "443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.public_port", "8443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.virtual_machine", "terraform-test"), ), }, @@ -66,25 +52,9 @@ func TestAccCloudStackPortForward_update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"), resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "ip_address", CLOUDSTACK_PUBLIC_IPADDRESS), + "cloudstack_port_forward.foo", "ip_address_id", CLOUDSTACK_PUBLIC_IPADDRESS), resource.TestCheckResourceAttr( "cloudstack_port_forward.foo", "forward.#", "2"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.260687715.protocol", "tcp"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.260687715.private_port", "80"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.260687715.public_port", "8080"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.260687715.virtual_machine", "terraform-test"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.protocol", "tcp"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.private_port", "443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.public_port", "8443"), - resource.TestCheckResourceAttr( - "cloudstack_port_forward.foo", "forward.952396423.virtual_machine", "terraform-test"), ), }, }, @@ -161,13 +131,13 @@ resource "cloudstack_instance" "foobar" { } resource "cloudstack_port_forward" "foo" { - ip_address = "%s" + ip_address_id = "%s" forward { protocol = "tcp" private_port = 443 public_port = 8443 - virtual_machine = "${cloudstack_instance.foobar.name}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" } }`, CLOUDSTACK_SERVICE_OFFERING_1, @@ -187,20 +157,20 @@ resource "cloudstack_instance" "foobar" { } resource "cloudstack_port_forward" "foo" { - ip_address = "%s" + ip_address_id = "%s" forward { protocol = "tcp" private_port = 443 public_port = 8443 - virtual_machine = "${cloudstack_instance.foobar.name}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" } forward { protocol = "tcp" private_port = 80 public_port = 8080 - virtual_machine = "${cloudstack_instance.foobar.name}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" } }`, CLOUDSTACK_SERVICE_OFFERING_1, diff --git a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go index cac479791e9e..a9940fd4c14a 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go +++ b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "log" "strings" @@ -26,23 +27,37 @@ func resourceCloudStackSecondaryIPAddress() *schema.Resource { "ipaddress": &schema.Schema{ Type: schema.TypeString, Optional: true, - Computed: true, ForceNew: true, Deprecated: "Please use the `ip_address` field instead", }, - "nicid": &schema.Schema{ + "nic_id": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "virtual_machine": &schema.Schema{ + "nicid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `nic_id` field instead", + }, + + "virtual_machine_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + + "virtual_machine": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `virtual_machine_id` field instead", + }, }, } } @@ -50,10 +65,22 @@ func resourceCloudStackSecondaryIPAddress() *schema.Resource { func resourceCloudStackSecondaryIPAddressCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - nicid := d.Get("nicid").(string) - if nicid == "" { + nicid, ok := d.GetOk("nic_id") + if !ok { + nicid, ok = d.GetOk("nicid") + } + if !ok { + virtualmachine, ok := d.GetOk("virtual_machine_id") + if !ok { + virtualmachine, ok = d.GetOk("virtual_machine") + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) if e != nil { return e.Error() } @@ -62,7 +89,7 @@ func resourceCloudStackSecondaryIPAddressCreate(d *schema.ResourceData, meta int vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) if err != nil { if count == 0 { - log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string)) + log.Printf("[DEBUG] Virtual Machine %s does no longer exist", virtualmachineid) d.SetId("") return nil } @@ -73,7 +100,7 @@ func resourceCloudStackSecondaryIPAddressCreate(d *schema.ResourceData, meta int } // Create a new parameter struct - p := cs.Nic.NewAddIpToNicParams(nicid) + p := cs.Nic.NewAddIpToNicParams(nicid.(string)) // If there is a ipaddres supplied, add it to the parameter struct ipaddress, ok := d.GetOk("ip_address") @@ -97,8 +124,17 @@ func resourceCloudStackSecondaryIPAddressCreate(d *schema.ResourceData, meta int func resourceCloudStackSecondaryIPAddressRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + virtualmachine, ok := d.GetOk("virtual_machine_id") + if !ok { + virtualmachine, ok = d.GetOk("virtual_machine") + } + if !ok { + return errors.New( + "Either `virtual_machine_id` or [deprecated] `virtual_machine` must be provided.") + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string)) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine.(string)) if e != nil { return e.Error() } @@ -107,20 +143,23 @@ func resourceCloudStackSecondaryIPAddressRead(d *schema.ResourceData, meta inter vm, count, err := cs.VirtualMachine.GetVirtualMachineByID(virtualmachineid) if err != nil { if count == 0 { - log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("virtual_machine").(string)) + log.Printf("[DEBUG] Virtual Machine %s does no longer exist", virtualmachineid) d.SetId("") return nil } return err } - nicid := d.Get("nicid").(string) - if nicid == "" { + nicid, ok := d.GetOk("nic_id") + if !ok { + nicid, ok = d.GetOk("nicid") + } + if !ok { nicid = vm.Nic[0].Id } p := cs.Nic.NewListNicsParams(virtualmachineid) - p.SetNicid(nicid) + p.SetNicid(nicid.(string)) l, err := cs.Nic.ListNics(p) if err != nil { @@ -140,7 +179,8 @@ func resourceCloudStackSecondaryIPAddressRead(d *schema.ResourceData, meta inter for _, ip := range l.Nics[0].Secondaryip { if ip.Id == d.Id() { d.Set("ip_address", ip.Ipaddress) - d.Set("nicid", l.Nics[0].Id) + d.Set("nic_id", l.Nics[0].Id) + d.Set("virtual_machine_id", l.Nics[0].Virtualmachineid) return nil } } diff --git a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go index 8b9614831ea3..879ebd4a1e3e 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_secondary_ipaddress_test.go @@ -64,9 +64,13 @@ func testAccCheckCloudStackSecondaryIPAddressExists( cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + virtualmachine, ok := rs.Primary.Attributes["virtual_machine_id"] + if !ok { + virtualmachine, ok = rs.Primary.Attributes["virtual_machine"] + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID( - cs, "virtual_machine", rs.Primary.Attributes["virtual_machine"]) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine) if e != nil { return e.Error() } @@ -80,8 +84,11 @@ func testAccCheckCloudStackSecondaryIPAddressExists( return err } - nicid := rs.Primary.Attributes["nicid"] - if nicid == "" { + nicid, ok := rs.Primary.Attributes["nic_id"] + if !ok { + nicid, ok = rs.Primary.Attributes["nicid"] + } + if !ok { nicid = vm.Nic[0].Id } @@ -136,9 +143,13 @@ func testAccCheckCloudStackSecondaryIPAddressDestroy(s *terraform.State) error { return fmt.Errorf("No IP address ID is set") } + virtualmachine, ok := rs.Primary.Attributes["virtual_machine_id"] + if !ok { + virtualmachine, ok = rs.Primary.Attributes["virtual_machine"] + } + // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID( - cs, "virtual_machine", rs.Primary.Attributes["virtual_machine"]) + virtualmachineid, e := retrieveID(cs, "virtual_machine", virtualmachine) if e != nil { return e.Error() } @@ -152,8 +163,11 @@ func testAccCheckCloudStackSecondaryIPAddressDestroy(s *terraform.State) error { return err } - nicid := rs.Primary.Attributes["nicid"] - if nicid == "" { + nicid, ok := rs.Primary.Attributes["nic_id"] + if !ok { + nicid, ok = rs.Primary.Attributes["nicid"] + } + if !ok { nicid = vm.Nic[0].Id } @@ -189,14 +203,14 @@ var testAccCloudStackSecondaryIPAddress_basic = fmt.Sprintf(` resource "cloudstack_instance" "foobar" { name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true } resource "cloudstack_secondary_ipaddress" "foo" { - virtual_machine = "${cloudstack_instance.foobar.id}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" } `, CLOUDSTACK_SERVICE_OFFERING_1, @@ -208,7 +222,7 @@ var testAccCloudStackSecondaryIPAddress_fixedIP = fmt.Sprintf(` resource "cloudstack_instance" "foobar" { name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" expunge = true @@ -216,7 +230,7 @@ resource "cloudstack_instance" "foobar" { resource "cloudstack_secondary_ipaddress" "foo" { ip_address = "%s" - virtual_machine = "${cloudstack_instance.foobar.id}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" }`, CLOUDSTACK_SERVICE_OFFERING_1, CLOUDSTACK_NETWORK_1, diff --git a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go index a418c4cf65ca..508077c4121d 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go @@ -63,6 +63,7 @@ func resourceCloudStackSSHKeyPairCreate(d *schema.ResourceData, meta interface{} p := cs.SSH.NewRegisterSSHKeyPairParams(name, string(key)) + // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } @@ -75,6 +76,7 @@ func resourceCloudStackSSHKeyPairCreate(d *schema.ResourceData, meta interface{} // No key supplied, must create one and return the private key p := cs.SSH.NewCreateSSHKeyPairParams(name) + // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } @@ -100,6 +102,7 @@ func resourceCloudStackSSHKeyPairRead(d *schema.ResourceData, meta interface{}) p := cs.SSH.NewListSSHKeyPairsParams() p.SetName(d.Id()) + // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } @@ -127,6 +130,7 @@ func resourceCloudStackSSHKeyPairDelete(d *schema.ResourceData, meta interface{} // Create a new parameter struct p := cs.SSH.NewDeleteSSHKeyPairParams(d.Id()) + // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } diff --git a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair_test.go b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair_test.go index ba70518d5b36..e367d1a73a39 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair_test.go @@ -146,12 +146,11 @@ func testAccCheckCloudStackSSHKeyPairDestroy(s *terraform.State) error { if err != nil { return err } - if list.Count != 1 { - return fmt.Errorf("Found more Key pair %s still exists", rs.Primary.ID) - } - if list.SSHKeyPairs[0].Name == rs.Primary.ID { - return fmt.Errorf("Key pair %s still exists", rs.Primary.ID) + for _, keyPair := range list.SSHKeyPairs { + if keyPair.Name == rs.Primary.ID { + return fmt.Errorf("Key pair %s still exists", rs.Primary.ID) + } } } diff --git a/builtin/providers/cloudstack/resource_cloudstack_static_nat.go b/builtin/providers/cloudstack/resource_cloudstack_static_nat.go index 0f7d7a439bd8..b96991eef0f2 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_static_nat.go +++ b/builtin/providers/cloudstack/resource_cloudstack_static_nat.go @@ -17,20 +17,20 @@ func resourceCloudStackStaticNAT() *schema.Resource { Delete: resourceCloudStackStaticNATDelete, Schema: map[string]*schema.Schema{ - "ipaddress": &schema.Schema{ + "ip_address_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "network": &schema.Schema{ + "network_id": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "virtual_machine": &schema.Schema{ + "virtual_machine_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, @@ -49,29 +49,14 @@ func resourceCloudStackStaticNAT() *schema.Resource { func resourceCloudStackStaticNATCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - // Retrieve the ipaddress ID - ipaddressid, e := retrieveID(cs, "ipaddress", d.Get("ipaddress").(string)) - if e != nil { - return e.Error() - } - - // Retrieve the virtual_machine ID - virtualmachineid, e := retrieveID(cs, "virtual_machine", d.Get("virtual_machine").(string)) - if e != nil { - return e.Error() - } + ipaddressid := d.Get("ip_address_id").(string) + virtualmachineid := d.Get("virtual_machine_id").(string) // Create a new parameter struct p := cs.NAT.NewEnableStaticNatParams(ipaddressid, virtualmachineid) - if network, ok := d.GetOk("network"); ok { - // Retrieve the network ID - networkid, e := retrieveID(cs, "network", network.(string)) - if e != nil { - return e.Error() - } - - p.SetNetworkid(networkid) + if networkid, ok := d.GetOk("network_id"); ok { + p.SetNetworkid(networkid.(string)) } if vmGuestIP, ok := d.GetOk("vm_guest_ip"); ok { @@ -126,8 +111,8 @@ func resourceCloudStackStaticNATRead(d *schema.ResourceData, meta interface{}) e return nil } - setValueOrID(d, "network", ip.Associatednetworkname, ip.Associatednetworkid) - setValueOrID(d, "virtual_machine", ip.Virtualmachinename, ip.Virtualmachineid) + d.Set("network_id", ip.Associatednetworkid) + d.Set("virtual_machine_id", ip.Virtualmachineid) d.Set("vm_guest_ip", ip.Vmipaddress) return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_static_nat_test.go b/builtin/providers/cloudstack/resource_cloudstack_static_nat_test.go index f6b86364f46e..be0bd6560b35 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_static_nat_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_static_nat_test.go @@ -66,12 +66,8 @@ func testAccCheckCloudStackStaticNATAttributes( ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc { return func(s *terraform.State) error { - if ipaddr.Associatednetworkname != CLOUDSTACK_NETWORK_1 { - return fmt.Errorf("Bad network: %s", ipaddr.Associatednetworkname) - } - - if ipaddr.Virtualmachinename != "terraform-test" { - return fmt.Errorf("Bad virtual_machine: %s", ipaddr.Virtualmachinename) + if ipaddr.Associatednetworkid != CLOUDSTACK_NETWORK_1 { + return fmt.Errorf("Bad network ID: %s", ipaddr.Associatednetworkid) } return nil @@ -104,7 +100,7 @@ resource "cloudstack_instance" "foobar" { name = "terraform-test" display_name = "terraform-test" service_offering= "%s" - network = "%s" + network_id = "%s" template = "%s" zone = "%s" user_data = "foobar\nfoo\nbar" @@ -112,17 +108,16 @@ resource "cloudstack_instance" "foobar" { } resource "cloudstack_ipaddress" "foo" { - network = "%s" + network_id = "${cloudstack_instance.foobar.network_id}" } resource "cloudstack_static_nat" "foo" { - ipaddress = "${cloudstack_ipaddress.foo.id}" - network = "${cloudstack_ipaddress.foo.network}" - virtual_machine = "${cloudstack_instance.foobar.id}" + ip_address_id = "${cloudstack_ipaddress.foo.id}" + network_id = "${cloudstack_ipaddress.foo.network_id}" + virtual_machine_id = "${cloudstack_instance.foobar.id}" }`, CLOUDSTACK_SERVICE_OFFERING_1, CLOUDSTACK_NETWORK_1, CLOUDSTACK_TEMPLATE, CLOUDSTACK_ZONE, - CLOUDSTACK_NETWORK_1, ) diff --git a/builtin/providers/cloudstack/resource_cloudstack_template.go b/builtin/providers/cloudstack/resource_cloudstack_template.go index 04aaca22ede0..10c24fdbb6da 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_template.go +++ b/builtin/providers/cloudstack/resource_cloudstack_template.go @@ -168,14 +168,8 @@ func resourceCloudStackTemplateCreate(d *schema.ResourceData, meta interface{}) } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // Create the new template diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpc.go b/builtin/providers/cloudstack/resource_cloudstack_vpc.go index d99a4042a523..a51b4dd3136e 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpc.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpc.go @@ -106,14 +106,8 @@ func resourceCloudStackVPCCreate(d *schema.ResourceData, meta interface{}) error } // If there is a project supplied, we retrieve and set the project id - if project, ok := d.GetOk("project"); ok { - // Retrieve the project ID - projectid, e := retrieveID(cs, "project", project.(string)) - if e != nil { - return e.Error() - } - // Set the default project ID - p.SetProjectid(projectid) + if err := setProjectid(p, cs, d); err != nil { + return err } // Create the new VPC @@ -163,8 +157,9 @@ func resourceCloudStackVPCRead(d *schema.ResourceData, meta interface{}) error { p.SetVpcid(d.Id()) p.SetIssourcenat(true) - if _, ok := d.GetOk("project"); ok { - p.SetProjectid(v.Projectid) + // If there is a project supplied, we retrieve and set the project id + if err := setProjectid(p, cs, d); err != nil { + return err } // Get the source NAT IP assigned to the VPC diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_connection.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_connection.go index 322f07a2c9d0..98fb27b9da09 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_connection.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_connection.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "log" "strings" @@ -16,17 +17,33 @@ func resourceCloudStackVPNConnection() *schema.Resource { Delete: resourceCloudStackVPNConnectionDelete, Schema: map[string]*schema.Schema{ - "customergatewayid": &schema.Schema{ + "customer_gateway_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, - "vpngatewayid": &schema.Schema{ + "customergatewayid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `customer_gateway_id` field instead", + }, + + "vpn_gateway_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + + "vpngatewayid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `vpn_gateway_id` field instead", + }, }, } } @@ -34,10 +51,27 @@ func resourceCloudStackVPNConnection() *schema.Resource { func resourceCloudStackVPNConnectionCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + customergatewayid, ok := d.GetOk("customer_gateway_id") + if !ok { + customergatewayid, ok = d.GetOk("customergatewayid") + } + if !ok { + return errors.New( + "Either `customer_gateway_id` or [deprecated] `customergatewayid` must be provided.") + } + + vpngatewayid, ok := d.GetOk("vpn_gateway_id") + if !ok { + vpngatewayid, ok = d.GetOk("vpngatewayid") + } + if !ok { + return errors.New("Either `vpn_gateway_id` or [deprecated] `vpngatewayid` must be provided.") + } + // Create a new parameter struct p := cs.VPN.NewCreateVpnConnectionParams( - d.Get("customergatewayid").(string), - d.Get("vpngatewayid").(string), + customergatewayid.(string), + vpngatewayid.(string), ) // Create the new VPN Connection @@ -66,8 +100,8 @@ func resourceCloudStackVPNConnectionRead(d *schema.ResourceData, meta interface{ return err } - d.Set("customergatewayid", v.S2scustomergatewayid) - d.Set("vpngatewayid", v.S2svpngatewayid) + d.Set("customer_gateway_id", v.S2scustomergatewayid) + d.Set("vpn_gateway_id", v.S2svpngatewayid) return nil } diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_connection_test.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_connection_test.go index 7d09eea9bb5e..930866853901 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_connection_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_connection_test.go @@ -96,11 +96,11 @@ resource "cloudstack_vpc" "bar" { } resource "cloudstack_vpn_gateway" "foo" { - vpc = "${cloudstack_vpc.foo.name}" + vpc_id = "${cloudstack_vpc.foo.id}" } resource "cloudstack_vpn_gateway" "bar" { - vpc = "${cloudstack_vpc.bar.name}" + vpc_id = "${cloudstack_vpc.bar.id}" } resource "cloudstack_vpn_customer_gateway" "foo" { @@ -122,13 +122,13 @@ resource "cloudstack_vpn_customer_gateway" "bar" { } resource "cloudstack_vpn_connection" "foo-bar" { - customergatewayid = "${cloudstack_vpn_customer_gateway.foo.id}" - vpngatewayid = "${cloudstack_vpn_gateway.bar.id}" + customer_gateway_id = "${cloudstack_vpn_customer_gateway.foo.id}" + vpn_gateway_id = "${cloudstack_vpn_gateway.bar.id}" } resource "cloudstack_vpn_connection" "bar-foo" { - customergatewayid = "${cloudstack_vpn_customer_gateway.bar.id}" - vpngatewayid = "${cloudstack_vpn_gateway.foo.id}" + customer_gateway_id = "${cloudstack_vpn_customer_gateway.bar.id}" + vpn_gateway_id = "${cloudstack_vpn_gateway.foo.id}" }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway_test.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway_test.go index b24eb356721c..acf181ace677 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway_test.go @@ -188,11 +188,11 @@ resource "cloudstack_vpc" "bar" { } resource "cloudstack_vpn_gateway" "foo" { - vpc = "${cloudstack_vpc.foo.name}" + vpc_id = "${cloudstack_vpc.foo.id}" } resource "cloudstack_vpn_gateway" "bar" { - vpc = "${cloudstack_vpc.bar.name}" + vpc_id = "${cloudstack_vpc.bar.id}" } resource "cloudstack_vpn_customer_gateway" "foo" { @@ -235,11 +235,11 @@ resource "cloudstack_vpc" "bar" { } resource "cloudstack_vpn_gateway" "foo" { - vpc = "${cloudstack_vpc.foo.name}" + vpc_id = "${cloudstack_vpc.foo.id}" } resource "cloudstack_vpn_gateway" "bar" { - vpc = "${cloudstack_vpc.bar.name}" + vpc_id = "${cloudstack_vpc.bar.id}" } resource "cloudstack_vpn_customer_gateway" "foo" { diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway.go index 17533a3a6250..b6a926dc128e 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway.go @@ -1,6 +1,7 @@ package cloudstack import ( + "errors" "fmt" "log" "strings" @@ -16,12 +17,20 @@ func resourceCloudStackVPNGateway() *schema.Resource { Delete: resourceCloudStackVPNGatewayDelete, Schema: map[string]*schema.Schema{ - "vpc": &schema.Schema{ + "vpc_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, + "vpc": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use the `vpc_id` field instead", + }, + "public_ip": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -33,8 +42,16 @@ func resourceCloudStackVPNGateway() *schema.Resource { func resourceCloudStackVPNGatewayCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + vpc, ok := d.GetOk("vpc_id") + if !ok { + vpc, ok = d.GetOk("vpc") + } + if !ok { + return errors.New("Either `vpc_id` or [deprecated] `vpc` must be provided.") + } + // Retrieve the VPC ID - vpcid, e := retrieveID(cs, "vpc", d.Get("vpc").(string)) + vpcid, e := retrieveID(cs, "vpc", vpc.(string)) if e != nil { return e.Error() } @@ -45,7 +62,7 @@ func resourceCloudStackVPNGatewayCreate(d *schema.ResourceData, meta interface{} // Create the new VPN Gateway v, err := cs.VPN.CreateVpnGateway(p) if err != nil { - return fmt.Errorf("Error creating VPN Gateway for VPC %s: %s", d.Get("vpc").(string), err) + return fmt.Errorf("Error creating VPN Gateway for VPC ID %s: %s", vpcid, err) } d.SetId(v.Id) @@ -61,7 +78,7 @@ func resourceCloudStackVPNGatewayRead(d *schema.ResourceData, meta interface{}) if err != nil { if count == 0 { log.Printf( - "[DEBUG] VPN Gateway for VPC %s does no longer exist", d.Get("vpc").(string)) + "[DEBUG] VPN Gateway for VPC ID %s does no longer exist", d.Get("vpc_id").(string)) d.SetId("") return nil } @@ -69,8 +86,7 @@ func resourceCloudStackVPNGatewayRead(d *schema.ResourceData, meta interface{}) return err } - setValueOrID(d, "vpc", d.Get("vpc").(string), v.Vpcid) - + d.Set("vpc_id", v.Vpcid) d.Set("public_ip", v.Publicip) return nil @@ -92,7 +108,7 @@ func resourceCloudStackVPNGatewayDelete(d *schema.ResourceData, meta interface{} return nil } - return fmt.Errorf("Error deleting VPN Gateway for VPC %s: %s", d.Get("vpc").(string), err) + return fmt.Errorf("Error deleting VPN Gateway for VPC %s: %s", d.Get("vpc_id").(string), err) } return nil diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway_test.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway_test.go index 61fc151601b9..862daefe97aa 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway_test.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_gateway_test.go @@ -22,8 +22,6 @@ func TestAccCloudStackVPNGateway_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckCloudStackVPNGatewayExists( "cloudstack_vpn_gateway.foo", &vpnGateway), - resource.TestCheckResourceAttr( - "cloudstack_vpn_gateway.foo", "vpc", "terraform-vpc"), ), }, }, @@ -90,7 +88,7 @@ resource "cloudstack_vpc" "foo" { } resource "cloudstack_vpn_gateway" "foo" { - vpc = "${cloudstack_vpc.foo.name}" + vpc_id = "${cloudstack_vpc.foo.id}" }`, CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_OFFERING, diff --git a/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown b/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown index acec778d9847..4abd541ae73e 100644 --- a/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown +++ b/website/source/docs/providers/cloudstack/r/egress_firewall.html.markdown @@ -14,7 +14,7 @@ Creates egress firewall rules for a given network. ``` resource "cloudstack_egress_firewall" "default" { - network = "test-network" + network_id = "6eb22f91-7454-4107-89f4-36afcdf33021" rule { cidr_list = ["10.0.0.0/8"] @@ -28,8 +28,11 @@ resource "cloudstack_egress_firewall" "default" { The following arguments are supported: -* `network` - (Required) The network for which to create the egress firewall - rules. Changing this forces a new resource to be created. +* `network_id` - (Required) The network ID for which to create the egress + firewall rules. Changing this forces a new resource to be created. + +* `network` - (Required, Deprecated) The network for which to create the egress + firewall rules. Changing this forces a new resource to be created. * `managed` - (Optional) USE WITH CAUTION! If enabled all the egress firewall rules for this network will be managed by this resource. This means it will diff --git a/website/source/docs/providers/cloudstack/r/firewall.html.markdown b/website/source/docs/providers/cloudstack/r/firewall.html.markdown index 4120306f53b5..f5e174aeb23e 100644 --- a/website/source/docs/providers/cloudstack/r/firewall.html.markdown +++ b/website/source/docs/providers/cloudstack/r/firewall.html.markdown @@ -14,7 +14,7 @@ Creates firewall rules for a given IP address. ``` resource "cloudstack_firewall" "default" { - ip_address = "192.168.0.1" + ip_address_id = "30b21801-d4b3-4174-852b-0c0f30bdbbfb" rule { cidr_list = ["10.0.0.0/8"] @@ -28,8 +28,8 @@ resource "cloudstack_firewall" "default" { The following arguments are supported: -* `ip_address` - (Required) The IP address or ID for which to create the firewall - rules. Changing this forces a new resource to be created. +* `ip_address_id` - (Required) The IP address ID for which to create the + firewall rules. Changing this forces a new resource to be created. * `ipaddress` - (Required, Deprecated) The IP address or ID for which to create the firewall rules. Changing this forces a new resource to be created. diff --git a/website/source/docs/providers/cloudstack/r/instance.html.markdown b/website/source/docs/providers/cloudstack/r/instance.html.markdown index 40bbc6d82761..cd3c8b9185bf 100644 --- a/website/source/docs/providers/cloudstack/r/instance.html.markdown +++ b/website/source/docs/providers/cloudstack/r/instance.html.markdown @@ -17,7 +17,7 @@ disk offering, and template. resource "cloudstack_instance" "web" { name = "server-1" service_offering= "small" - network = "network-1" + network_id = "6eb22f91-7454-4107-89f4-36afcdf33021" template = "CentOS 6.5" zone = "zone-1" } @@ -34,9 +34,12 @@ The following arguments are supported: * `service_offering` - (Required) The name or ID of the service offering used for this instance. -* `network` - (Optional) The name or ID of the network to connect this instance +* `network_id` - (Optional) The ID of the network to connect this instance to. Changing this forces a new resource to be created. +* `network` - (Optional, Deprecated) The name or ID of the network to connect + this instance to. Changing this forces a new resource to be created. + * `ip_address` - (Optional) The IP address to assign to this instance. Changing this forces a new resource to be created. diff --git a/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown b/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown index 45315a0f7821..eeed95f9b65c 100644 --- a/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown +++ b/website/source/docs/providers/cloudstack/r/ipaddress.html.markdown @@ -14,7 +14,7 @@ Acquires and associates a public IP. ``` resource "cloudstack_ipaddress" "default" { - network = "test-network" + network_id = "6eb22f91-7454-4107-89f4-36afcdf33021" } ``` @@ -22,16 +22,24 @@ resource "cloudstack_ipaddress" "default" { The following arguments are supported: -* `network` - (Optional) The name or ID of the network for which an IP address should +* `network_id` - (Optional) The ID of the network for which an IP address should be acquired and associated. Changing this forces a new resource to be created. -* `vpc` - (Optional) The name or ID of the VPC for which an IP address should - be acquired and associated. Changing this forces a new resource to be created. +* `network` - (Optional, Deprecated) The name or ID of the network for which an IP + addess should be acquired and associated. Changing this forces a new resource + to be created. + +* `vpc_id` - (Optional) The ID of the VPC for which an IP address should be + acquired and associated. Changing this forces a new resource to be created. + +* `vpc` - (Optional, Deprecated) The name or ID of the VPC for which an IP address + should be acquired and associated. Changing this forces a new resource to be + created. * `project` - (Optional) The name or ID of the project to deploy this instance to. Changing this forces a new resource to be created. -*NOTE: Either `network` or `vpc` should have a value!* +*NOTE: Either `network_id` or `vpc_id` should have a value!* ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/loadbalancer_rule.html.markdown b/website/source/docs/providers/cloudstack/r/loadbalancer_rule.html.markdown index eb374096bc6f..65a252a2d9fb 100644 --- a/website/source/docs/providers/cloudstack/r/loadbalancer_rule.html.markdown +++ b/website/source/docs/providers/cloudstack/r/loadbalancer_rule.html.markdown @@ -16,11 +16,11 @@ Creates a loadbalancer rule. resource "cloudstack_loadbalancer_rule" "default" { name = "loadbalancer-rule-1" description = "Loadbalancer rule 1" - ip_address = "192.168.0.1" + ip_address_id = "30b21801-d4b3-4174-852b-0c0f30bdbbfb" algorithm = "roundrobin" private_port = 80 public_port = 80 - members = ["server-1", "server-2"] + member_ids = ["f8141e2f-4e7e-4c63-9362-986c908b7ea7"] } ``` @@ -33,16 +33,20 @@ The following arguments are supported: * `description` - (Optional) The description of the load balancer rule. -* `ip_address` - (Required) Public ip address from where the network traffic - will be load balanced from. Changing this forces a new resource to be - created. +* `ip_address_id` - (Required) Public IP address ID from where the network + traffic will be load balanced from. Changing this forces a new resource + to be created. -* `ipaddress` - (Required, Deprecated) Public ip address from where the +* `ipaddress` - (Required, Deprecated) Public IP address from where the network traffic will be load balanced from. Changing this forces a new resource to be created. -* `network` - (Optional) The guest network this rule will be created for. - Required when public IP address is not associated with any Guest network +* `network_id` - (Optional) The network ID this rule will be created for. + Required when public IP address is not associated with any network yet + (VPC case). + +* `network` - (Optional, Deprecated) The network this rule will be created + for. Required when public IP address is not associated with any network yet (VPC case). * `algorithm` - (Required) Load balancer rule algorithm (source, roundrobin, @@ -56,8 +60,11 @@ The following arguments are supported: will be load balanced from. Changing this forces a new resource to be created. -* `members` - (Required) List of instances to assign to the load balancer rule. - Changing this forces a new resource to be created. +* `member_ids` - (Required) List of instance IDs to assign to the load balancer + rule. Changing this forces a new resource to be created. + +* `members` - (Required, Deprecated) List of instances to assign to the load + balancer rule. Changing this forces a new resource to be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/network.html.markdown b/website/source/docs/providers/cloudstack/r/network.html.markdown index cf7b1ae67bd9..3f00f2ee584f 100644 --- a/website/source/docs/providers/cloudstack/r/network.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network.html.markdown @@ -50,11 +50,17 @@ The following arguments are supported: required by the Network Offering if specifyVlan=true is set. Only the ROOT admin can set this value. -* `vpc` - (Optional) The name or ID of the VPC to create this network for. Changing +* `vpc_id` - (Optional) The ID of the VPC to create this network for. Changing this forces a new resource to be created. -* `aclid` - (Optional) The ID of a network ACL that should be attached to the - network. Changing this forces a new resource to be created. +* `vpc` - (Optional, Deprecated) The name or ID of the VPC to create this network + for. Changing this forces a new resource to be created. + +* `acl_id` - (Optional) The network ACL ID that should be attached to the network. + Changing this forces a new resource to be created. + +* `aclid` - (Optional, Deprecated) The ID of a network ACL that should be attached + to the network. Changing this forces a new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this instance to. Changing this forces a new resource to be created. diff --git a/website/source/docs/providers/cloudstack/r/network_acl.html.markdown b/website/source/docs/providers/cloudstack/r/network_acl.html.markdown index 0001cbffb755..c8d5e433ab79 100644 --- a/website/source/docs/providers/cloudstack/r/network_acl.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network_acl.html.markdown @@ -15,7 +15,7 @@ Creates a Network ACL for the given VPC. ``` resource "cloudstack_network_acl" "default" { name = "test-acl" - vpc = "vpc-1" + vpc_id = "76f6e8dc-07e3-4971-b2a2-8831b0cc4cb4" } ``` @@ -25,10 +25,15 @@ The following arguments are supported: * `name` - (Required) The name of the ACL. Changing this forces a new resource to be created. + * `description` - (Optional) The description of the ACL. Changing this forces a new resource to be created. -* `vpc` - (Required) The name or ID of the VPC to create this ACL for. Changing - this forces a new resource to be created. + +* `vpc_id` - (Required) The ID of the VPC to create this ACL for. Changing this + forces a new resource to be created. + +* `vpc` - (Required, Deprecated) The name or ID of the VPC to create this ACL + for. Changing this forces a new resource to be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown b/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown index 267eca346558..4b0ebaa9dfaf 100644 --- a/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown +++ b/website/source/docs/providers/cloudstack/r/network_acl_rule.html.markdown @@ -14,7 +14,7 @@ Creates network ACL rules for a given network ACL. ``` resource "cloudstack_network_acl_rule" "default" { - aclid = "f3843ce0-334c-4586-bbd3-0c2e2bc946c6" + acl_id = "f3843ce0-334c-4586-bbd3-0c2e2bc946c6" rule { action = "allow" @@ -30,9 +30,12 @@ resource "cloudstack_network_acl_rule" "default" { The following arguments are supported: -* `aclid` - (Required) The network ACL ID for which to create the rules. +* `acl_id` - (Required) The network ACL ID for which to create the rules. Changing this forces a new resource to be created. +* `aclid` - (Required, Deprecated) The network ACL ID for which to create + the rules. Changing this forces a new resource to be created. + * `managed` - (Optional) USE WITH CAUTION! If enabled all the firewall rules for this network ACL will be managed by this resource. This means it will delete all firewall rules that are not in your config! (defaults false) diff --git a/website/source/docs/providers/cloudstack/r/nic.html.markdown b/website/source/docs/providers/cloudstack/r/nic.html.markdown index 38aacd87d48c..597b40f6cd60 100644 --- a/website/source/docs/providers/cloudstack/r/nic.html.markdown +++ b/website/source/docs/providers/cloudstack/r/nic.html.markdown @@ -16,9 +16,9 @@ Basic usage: ``` resource "cloudstack_nic" "test" { - network = "network-2" + network_id = "6eb22f91-7454-4107-89f4-36afcdf33021" ip_address = "192.168.1.1" - virtual_machine = "server-1" + virtual_machine_id = "f8141e2f-4e7e-4c63-9362-986c908b7ea7" } ``` @@ -26,17 +26,24 @@ resource "cloudstack_nic" "test" { The following arguments are supported: -* `network` - (Required) The name or ID of the network to plug the NIC into. Changing +* `network_id` - (Required) The ID of the network to plug the NIC into. Changing this forces a new resource to be created. +* `network` - (Required, Deprecated) The name or ID of the network to plug the + NIC into. Changing this forces a new resource to be created. + * `ip_address` - (Optional) The IP address to assign to the NIC. Changing this forces a new resource to be created. -* `ipaddress` - (Optional, Deprecated) The IP address to assign to the NIC. Changing - this forces a new resource to be created. +* `ipaddress` - (Optional, Deprecated) The IP address to assign to the NIC. + Changing this forces a new resource to be created. + +* `virtual_machine_id` - (Required) The ID of the virtual machine to which to + attach the NIC. Changing this forces a new resource to be created. -* `virtual_machine` - (Required) The name or ID of the virtual machine to which - to attach the NIC. Changing this forces a new resource to be created. +* `virtual_machine` - (Required, Deprecated) The name or ID of the virtual + machine to which to attach the NIC. Changing this forces a new resource to + be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/port_forward.html.markdown b/website/source/docs/providers/cloudstack/r/port_forward.html.markdown index 41e3b0b39f72..19e7d4ab6de5 100644 --- a/website/source/docs/providers/cloudstack/r/port_forward.html.markdown +++ b/website/source/docs/providers/cloudstack/r/port_forward.html.markdown @@ -14,13 +14,13 @@ Creates port forwards. ``` resource "cloudstack_port_forward" "default" { - ip_address = "192.168.0.1" + ip_address_id = "30b21801-d4b3-4174-852b-0c0f30bdbbfb" forward { protocol = "tcp" private_port = 80 public_port = 8080 - virtual_machine = "server-1" + virtual_machine_id = "f8141e2f-4e7e-4c63-9362-986c908b7ea7" } } ``` @@ -29,8 +29,8 @@ resource "cloudstack_port_forward" "default" { The following arguments are supported: -* `ip_address` - (Required) The IP address for which to create the port forwards. - Changing this forces a new resource to be created. +* `ip_address_id` - (Required) The IP address ID for which to create the port + forwards. Changing this forces a new resource to be created. * `ipaddress` - (Required, Deprecated) The IP address for which to create the port forwards. Changing this forces a new resource to be created. @@ -51,7 +51,10 @@ The `forward` block supports: * `public_port` - (Required) The public port to forward from. -* `virtual_machine` - (Required) The name or ID of the virtual machine to forward to. +* `virtual_machine_id` - (Required) The ID of the virtual machine to forward to. + +* `virtual_machine` - (Required, Deprecated) The name or ID of the virtual + machine to forward to. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown b/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown index 6907796f5603..85d27b01c323 100644 --- a/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown +++ b/website/source/docs/providers/cloudstack/r/secondary_ipaddress.html.markdown @@ -14,7 +14,7 @@ Assigns a secondary IP to a NIC. ``` resource "cloudstack_secondary_ipaddress" "default" { - virtual_machine = "server-1" + virtual_machine_id = "server-1" } ``` @@ -23,20 +23,28 @@ resource "cloudstack_secondary_ipaddress" "default" { The following arguments are supported: * `ip_address` - (Optional) The IP address to attach the to NIC. If not supplied - an IP address will be selected randomly. Changing this forces a new resource - to be created. + an IP address will be selected randomly. Changing this forces a new resource + to be created. * `ipaddress` - (Optional, Deprecated) The IP address to attach the to NIC. If not supplied an IP address will be selected randomly. Changing this forces a new resource to be created. -* `nicid` - (Optional) The ID of the NIC to which you want to attach the - secondary IP address. Changing this forces a new resource to be - created (defaults to the ID of the primary NIC) +* `nic_id` - (Optional) The NIC ID to which you want to attach the secondary IP + address. Changing this forces a new resource to be created (defaults to the + ID of the primary NIC) -* `virtual_machine` - (Required) The name or ID of the virtual machine to which - you want to attach the secondary IP address. Changing this forces a new - resource to be created. +* `nicid` - (Optional, Deprecated) The ID of the NIC to which you want to attach + the secondary IP address. Changing this forces a new resource to be created + (defaults to the ID of the primary NIC) + +* `virtual_machine_id` - (Required) The ID of the virtual machine to which you + want to attach the secondary IP address. Changing this forces a new resource + to be created. + +* `virtual_machine` - (Required, Deprecated) The name or ID of the virtual + machine to which you want to attach the secondary IP address. Changing this + forces a new resource to be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/static_nat.html.markdown b/website/source/docs/providers/cloudstack/r/static_nat.html.markdown index f899309a092c..2f7caf1ab5c3 100644 --- a/website/source/docs/providers/cloudstack/r/static_nat.html.markdown +++ b/website/source/docs/providers/cloudstack/r/static_nat.html.markdown @@ -14,8 +14,8 @@ Enables static NAT for a given IP address ``` resource "cloudstack_static_nat" "default" { - ipaddress = "192.168.0.1" - virtual_machine = "server-1" + ip_address_id = "f8141e2f-4e7e-4c63-9362-986c908b7ea7" + virtual_machine_id = "6ca2a163-bc68-429c-adc8-ab4a620b1bb3" } ``` @@ -23,18 +23,16 @@ resource "cloudstack_static_nat" "default" { The following arguments are supported: -* `ipaddress` - (Required) The name or ID of the public IP address for which - static NAT will be enabled. Changing this forces a new resource to be - created. +* `ip_address_id` - (Required) The public IP address ID for which static + NAT will be enabled. Changing this forces a new resource to be created. -* `network` - (Optional) The name or ID of the network of the VM the static - NAT will be enabled for. Required when public IP address is not - associated with any guest network yet (VPC case). Changing this forces - a new resource to be created. +* `network_id` - (Optional) The network ID of the VM the static NAT will be + enabled for. Required when public IP address is not associated with any + guest network yet (VPC case). Changing this forces a new resource to be + created. -* `virtual_machine` - (Required) The name or ID of the virtual machine to - enable the static NAT feature for. Changing this forces a new resource - to be created. +* `virtual_machine_id` - (Required) The virtual machine ID to enable the + static NAT feature for. Changing this forces a new resource to be created. * `vm_guest_ip` - (Optional) The virtual machine IP address for the port forwarding rule (useful when the virtual machine has a secondairy NIC). diff --git a/website/source/docs/providers/cloudstack/r/template.html.markdown b/website/source/docs/providers/cloudstack/r/template.html.markdown index b31c24db026e..99525395d05a 100644 --- a/website/source/docs/providers/cloudstack/r/template.html.markdown +++ b/website/source/docs/providers/cloudstack/r/template.html.markdown @@ -31,8 +31,8 @@ The following arguments are supported: * `display_text` - (Optional) The display name of the template. -* `format` - (Required) The format of the template. Valid values are "QCOW2", - "RAW", and "VHD". +* `format` - (Required) The format of the template. Valid values are `QCOW2`, + `RAW`, and `VHD`. * `hypervisor` - (Required) The target hypervisor for the template. Changing this forces a new resource to be created. @@ -43,11 +43,14 @@ The following arguments are supported: * `url` - (Required) The URL of where the template is hosted. Changing this forces a new resource to be created. +* `project` - (Optional) The name or ID of the project to create this template for. + Changing this forces a new resource to be created. + * `zone` - (Required) The name or ID of the zone where this template will be created. Changing this forces a new resource to be created. * `is_dynamically_scalable` - (Optional) Set to indicate if the template contains - tools to support dynamic scaling of VM cpu/memory. + tools to support dynamic scaling of VM cpu/memory (defaults false) * `is_extractable` - (Optional) Set to indicate if the template is extractable (defaults false) diff --git a/website/source/docs/providers/cloudstack/r/vpn_connection.html.markdown b/website/source/docs/providers/cloudstack/r/vpn_connection.html.markdown index 3ecf17cbca65..355fcdb086c8 100644 --- a/website/source/docs/providers/cloudstack/r/vpn_connection.html.markdown +++ b/website/source/docs/providers/cloudstack/r/vpn_connection.html.markdown @@ -16,8 +16,8 @@ Basic usage: ``` resource "cloudstack_vpn_connection" "default" { - customergatewayid = "xxx" - vpngatewayid = "xxx" + customer_gateway_id = "8dab9381-ae73-48b8-9a3d-c460933ef5f7" + vpn_gateway_id = "a7900060-f8a8-44eb-be15-ea54cf499703" } ``` @@ -25,10 +25,16 @@ resource "cloudstack_vpn_connection" "default" { The following arguments are supported: -* `customergatewayid` - (Required) The Customer Gateway ID to connect. +* `customer_gateway_id` - (Required) The Customer Gateway ID to connect. Changing this forces a new resource to be created. -* `vpngatewayid` - (Required) The VPN Gateway ID to connect. +* `customergatewayid` - (Required, Deprecated) The Customer Gateway ID + to connect. Changing this forces a new resource to be created. + +* `vpn_gateway_id` - (Required) The VPN Gateway ID to connect. Changing + this forces a new resource to be created. + +* `vpngatewayid` - (Required, Deprecated) The VPN Gateway ID to connect. Changing this forces a new resource to be created. ## Attributes Reference diff --git a/website/source/docs/providers/cloudstack/r/vpn_gateway.html.markdown b/website/source/docs/providers/cloudstack/r/vpn_gateway.html.markdown index 5bf9cf389cc4..1c74bf1a14d7 100644 --- a/website/source/docs/providers/cloudstack/r/vpn_gateway.html.markdown +++ b/website/source/docs/providers/cloudstack/r/vpn_gateway.html.markdown @@ -16,7 +16,7 @@ Basic usage: ``` resource "cloudstack_vpn_gateway" "default" { - vpc = "test-vpc" + vpc_id = "f8141e2f-4e7e-4c63-9362-986c908b7ea7" } ``` @@ -24,9 +24,12 @@ resource "cloudstack_vpn_gateway" "default" { The following arguments are supported: -* `vpc` - (Required) The name or ID of the VPC for which to create the VPN Gateway. +* `vpc_id` - (Required) The ID of the VPC for which to create the VPN Gateway. Changing this forces a new resource to be created. +* `vpc` - (Required, Deprecated) The name or ID of the VPC for which to create + the VPN Gateway. Changing this forces a new resource to be created. + ## Attributes Reference The following attributes are exported: From 337895b51e2d11a04324adb708e02fe0bd394c03 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Sun, 10 Apr 2016 19:31:40 -0400 Subject: [PATCH 016/337] Read more default envvars for GCP - Closes #5874 - Fixes #5872 --- builtin/providers/google/provider.go | 21 ++++++++--- builtin/providers/google/provider_test.go | 37 +++++++++++++++---- .../docs/providers/google/index.html.markdown | 27 ++++++++++---- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index 8fd5339f51b3..89e176979a62 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -27,20 +27,29 @@ func Provider() terraform.ResourceProvider { DefaultFunc: schema.MultiEnvDefaultFunc([]string{ "GOOGLE_CREDENTIALS", "GOOGLE_CLOUD_KEYFILE_JSON", + "GCLOUD_KEYFILE_JSON", }, nil), ValidateFunc: validateCredentials, }, "project": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("GOOGLE_PROJECT", ""), + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_PROJECT", + "GCLOUD_PROJECT", + "CLOUDSDK_CORE_PROJECT", + }, nil), }, "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("GOOGLE_REGION", nil), + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_REGION", + "GCLOUD_REGION", + "CLOUDSDK_COMPUTE_REGION", + }, nil), }, }, diff --git a/builtin/providers/google/provider_test.go b/builtin/providers/google/provider_test.go index 9bf5414b74e4..40bf1654efaa 100644 --- a/builtin/providers/google/provider_test.go +++ b/builtin/providers/google/provider_test.go @@ -3,6 +3,7 @@ package google import ( "io/ioutil" "os" + "strings" "testing" "github.com/hashicorp/terraform/helper/schema" @@ -38,18 +39,40 @@ func testAccPreCheck(t *testing.T) { os.Setenv("GOOGLE_CREDENTIALS", string(creds)) } - if v := os.Getenv("GOOGLE_CREDENTIALS"); v == "" { - if w := os.Getenv("GOOGLE_CLOUD_KEYFILE_JSON"); w == "" { - t.Fatal("GOOGLE_CREDENTIALS or GOOGLE_CLOUD_KEYFILE_JSON must be set for acceptance tests") + multiEnvSearch := func(ks []string) string { + for _, k := range ks { + if v := os.Getenv(k); v != "" { + return v + } } + return "" } - if v := os.Getenv("GOOGLE_PROJECT"); v == "" { - t.Fatal("GOOGLE_PROJECT must be set for acceptance tests") + creds := []string{ + "GOOGLE_CREDENTIALS", + "GOOGLE_CLOUD_KEYFILE_JSON", + "GCLOUD_KEYFILE_JSON", + } + if v := multiEnvSearch(creds); v == "" { + t.Fatalf("One of %s must be set for acceptance tests", strings.Join(creds, ", ")) } - if v := os.Getenv("GOOGLE_REGION"); v != "us-central1" { - t.Fatal("GOOGLE_REGION must be set to us-central1 for acceptance tests") + projs := []string{ + "GOOGLE_PROJECT", + "GCLOUD_PROJECT", + "CLOUDSDK_CORE_PROJECT", + } + if v := multiEnvSearch(projs); v == "" { + t.Fatalf("One of %s must be set for acceptance tests", strings.Join(creds, ", ")) + } + + regs := []string{ + "GOOGLE_REGION", + "GCLOUD_REGION", + "CLOUDSDK_COMPUTE_REGION", + } + if v := multiEnvSearch(regs); v != "us-central-1" { + t.Fatalf("One of %s must be set to us-central-1 for acceptance tests", strings.Join(creds, ", ")) } } diff --git a/website/source/docs/providers/google/index.html.markdown b/website/source/docs/providers/google/index.html.markdown index 641e2b419093..936cc26121b4 100644 --- a/website/source/docs/providers/google/index.html.markdown +++ b/website/source/docs/providers/google/index.html.markdown @@ -39,17 +39,28 @@ The following keys can be used to configure the provider. retrieving this file are below. Credentials may be blank if you are running Terraform from a GCE instance with a properly-configured [Compute Engine Service Account](https://cloud.google.com/compute/docs/authentication). This - can also be specified with the `GOOGLE_CREDENTIALS` or `GOOGLE_CLOUD_KEYFILE_JSON` - shell environment variable, containing the contents of the credentials file. + can also be specified using any of the following environment variables + (listed in order of precedence): + + * `GOOGLE_CREDENTIALS` + * `GOOGLE_CLOUD_KEYFILE_JSON` + * `GCLOUD_KEYFILE_JSON` + +* `project` - (Required) The ID of the project to apply any resources to. This + can be specified using any of the following environment variables (listed in + order of precedence): + + * `GOOGLE_PROJECT` + * `GCLOUD_PROJECT` + * `CLOUDSDK_CORE_PROJECT` * `region` - (Required) The region to operate under. This can also be specified - with the `GOOGLE_REGION` shell environment variable. + using any of the following environment variables (listed in order of + precedence): -* `project` - (Optional) The ID of the project to apply resources in. This - can also be specified with the `GOOGLE_PROJECT` shell environment variable. - If unspecified, users will need to specify the `project` attribute for - all resources. If specified, resources which do not depend on a project will - ignore this value. + * `GOOGLE_REGION` + * `GCLOUD_REGION` + * `CLOUDSDK_COMPUTE_REGION` The following keys are supported for backwards compatibility, and may be removed in a future version: From 2ea8c64079e7d166adb5b8d91eb802fdec510d27 Mon Sep 17 00:00:00 2001 From: Clint Date: Mon, 11 Apr 2016 12:06:28 -0500 Subject: [PATCH 017/337] provider/aws: More randomization to our Acc tests (#6124) * provider/aws: Add more Randomization to DB Parameter Group Tests, to avoid collisions * provider/aws: Add more randomization to Autoscaling group tests --- .../resource_aws_autoscaling_group_test.go | 59 +++++++++++-------- .../resource_aws_db_parameter_group_test.go | 41 +++++++------ 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index ec29cda490ba..c41b3dc6e88c 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -18,21 +19,23 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { var group autoscaling.Group var lc autoscaling.LaunchConfiguration + randName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSAutoScalingGroupConfig, + Config: testAccAWSAutoScalingGroupConfig(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSAutoScalingGroupHealthyCapacity(&group, 2), - testAccCheckAWSAutoScalingGroupAttributes(&group), + testAccCheckAWSAutoScalingGroupAttributes(&group, randName), resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"), resource.TestCheckResourceAttr( - "aws_autoscaling_group.bar", "name", "foobar3-terraform-test"), + "aws_autoscaling_group.bar", "name", randName), resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "max_size", "5"), resource.TestCheckResourceAttr( @@ -53,7 +56,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { }, resource.TestStep{ - Config: testAccAWSAutoScalingGroupConfigUpdate, + Config: testAccAWSAutoScalingGroupConfigUpdate(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.new", &lc), @@ -139,13 +142,15 @@ func TestAccAWSAutoScalingGroup_terminationPolicies(t *testing.T) { func TestAccAWSAutoScalingGroup_tags(t *testing.T) { var group autoscaling.Group + randName := fmt.Sprintf("tfautotags-%s", acctest.RandString(5)) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSAutoScalingGroupConfig, + Config: testAccAWSAutoScalingGroupConfig(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAutoscalingTags(&group.Tags, "Foo", map[string]interface{}{ @@ -156,7 +161,7 @@ func TestAccAWSAutoScalingGroup_tags(t *testing.T) { }, resource.TestStep{ - Config: testAccAWSAutoScalingGroupConfigUpdate, + Config: testAccAWSAutoScalingGroupConfigUpdate(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAutoscalingTagNotExists(&group.Tags, "Foo"), @@ -217,7 +222,7 @@ func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) { CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: fmt.Sprintf(testAccAWSAutoScalingGroupConfigWithLoadBalancer), + Config: testAccAWSAutoScalingGroupConfigWithLoadBalancer, Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSAutoScalingGroupAttributesLoadBalancer(&group), @@ -230,17 +235,18 @@ func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) { func TestAccAWSAutoScalingGroup_withPlacementGroup(t *testing.T) { var group autoscaling.Group + randName := fmt.Sprintf("tf_placement_test-%s", acctest.RandString(5)) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSAutoScalingGroupConfig_withPlacementGroup, + Config: testAccAWSAutoScalingGroupConfig_withPlacementGroup(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), resource.TestCheckResourceAttr( - "aws_autoscaling_group.bar", "placement_group", "tf_placement_test"), + "aws_autoscaling_group.bar", "placement_group", randName), ), }, }, @@ -310,14 +316,14 @@ func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error { return nil } -func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.Group) resource.TestCheckFunc { +func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.Group, name string) resource.TestCheckFunc { return func(s *terraform.State) error { if *group.AvailabilityZones[0] != "us-west-2a" { return fmt.Errorf("Bad availability_zones: %#v", group.AvailabilityZones[0]) } - if *group.AutoScalingGroupName != "foobar3-terraform-test" { - return fmt.Errorf("Bad name: %s", *group.AutoScalingGroupName) + if *group.AutoScalingGroupName != name { + return fmt.Errorf("Bad Autoscaling Group name, expected (%s), got (%s)", name, *group.AutoScalingGroupName) } if *group.MaxSize != 5 { @@ -539,20 +545,21 @@ resource "aws_autoscaling_group" "bar" { } ` -const testAccAWSAutoScalingGroupConfig = ` +func testAccAWSAutoScalingGroupConfig(name string) string { + return fmt.Sprintf(` resource "aws_launch_configuration" "foobar" { image_id = "ami-21f78e11" instance_type = "t1.micro" } resource "aws_placement_group" "test" { - name = "test" + name = "%s" strategy = "cluster" } resource "aws_autoscaling_group" "bar" { availability_zones = ["us-west-2a"] - name = "foobar3-terraform-test" + name = "%s" max_size = 5 min_size = 2 health_check_type = "ELB" @@ -568,9 +575,11 @@ resource "aws_autoscaling_group" "bar" { propagate_at_launch = true } } -` +`, name, name) +} -const testAccAWSAutoScalingGroupConfigUpdate = ` +func testAccAWSAutoScalingGroupConfigUpdate(name string) string { + return fmt.Sprintf(` resource "aws_launch_configuration" "foobar" { image_id = "ami-21f78e11" instance_type = "t1.micro" @@ -583,7 +592,7 @@ resource "aws_launch_configuration" "new" { resource "aws_autoscaling_group" "bar" { availability_zones = ["us-west-2a"] - name = "foobar3-terraform-test" + name = "%s" max_size = 5 min_size = 2 health_check_grace_period = 300 @@ -600,7 +609,8 @@ resource "aws_autoscaling_group" "bar" { propagate_at_launch = true } } -` +`, name) +} const testAccAWSAutoScalingGroupConfigWithLoadBalancer = ` resource "aws_vpc" "foo" { @@ -668,7 +678,6 @@ resource "aws_launch_configuration" "foobar" { resource "aws_autoscaling_group" "bar" { availability_zones = ["${aws_subnet.foo.availability_zone}"] vpc_zone_identifier = ["${aws_subnet.foo.id}"] - name = "foobar3-terraform-test" max_size = 2 min_size = 2 health_check_grace_period = 300 @@ -747,20 +756,21 @@ resource "aws_autoscaling_group" "bar" { } ` -const testAccAWSAutoScalingGroupConfig_withPlacementGroup = ` +func testAccAWSAutoScalingGroupConfig_withPlacementGroup(name string) string { + return fmt.Sprintf(` resource "aws_launch_configuration" "foobar" { image_id = "ami-21f78e11" instance_type = "c3.large" } resource "aws_placement_group" "test" { - name = "tf_placement_test" + name = "%s" strategy = "cluster" } resource "aws_autoscaling_group" "bar" { availability_zones = ["us-west-2a"] - name = "foobar3-terraform-test" + name = "%s" max_size = 1 min_size = 1 health_check_grace_period = 300 @@ -778,7 +788,8 @@ resource "aws_autoscaling_group" "bar" { propagate_at_launch = true } } -` +`, name, name) +} const testAccAWSAutoscalingMetricsCollectionConfig_allMetricsCollected = ` resource "aws_launch_configuration" "foobar" { diff --git a/builtin/providers/aws/resource_aws_db_parameter_group_test.go b/builtin/providers/aws/resource_aws_db_parameter_group_test.go index d4b24204b8c4..e076b02aeb5c 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group_test.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group_test.go @@ -17,18 +17,20 @@ import ( func TestAccAWSDBParameterGroup_basic(t *testing.T) { var v rds.DBParameterGroup + groupName := fmt.Sprintf("parameter-group-test-terraform-%d", acctest.RandInt()) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSDBParameterGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSDBParameterGroupConfig(acctest.RandInt()), + Config: testAccAWSDBParameterGroupConfig(groupName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSDBParameterGroupExists("aws_db_parameter_group.bar", &v), - testAccCheckAWSDBParameterGroupAttributes(&v), + testAccCheckAWSDBParameterGroupAttributes(&v, groupName), resource.TestCheckResourceAttr( - "aws_db_parameter_group.bar", "name", "parameter-group-test-terraform"), + "aws_db_parameter_group.bar", "name", groupName), resource.TestCheckResourceAttr( "aws_db_parameter_group.bar", "family", "mysql5.6"), resource.TestCheckResourceAttr( @@ -50,12 +52,12 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) { ), }, resource.TestStep{ - Config: testAccAWSDBParameterGroupAddParametersConfig(acctest.RandInt()), + Config: testAccAWSDBParameterGroupAddParametersConfig(groupName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSDBParameterGroupExists("aws_db_parameter_group.bar", &v), - testAccCheckAWSDBParameterGroupAttributes(&v), + testAccCheckAWSDBParameterGroupAttributes(&v, groupName), resource.TestCheckResourceAttr( - "aws_db_parameter_group.bar", "name", "parameter-group-test-terraform"), + "aws_db_parameter_group.bar", "name", groupName), resource.TestCheckResourceAttr( "aws_db_parameter_group.bar", "family", "mysql5.6"), resource.TestCheckResourceAttr( @@ -88,21 +90,22 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) { }) } -func TestAccAWSDBParameterGroupOnly(t *testing.T) { +func TestAccAWSDBParameterGroup_Only(t *testing.T) { var v rds.DBParameterGroup + groupName := fmt.Sprintf("parameter-group-test-terraform-%d", acctest.RandInt()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSDBParameterGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSDBParameterGroupOnlyConfig(acctest.RandInt()), + Config: testAccAWSDBParameterGroupOnlyConfig(groupName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSDBParameterGroupExists("aws_db_parameter_group.bar", &v), - testAccCheckAWSDBParameterGroupAttributes(&v), + testAccCheckAWSDBParameterGroupAttributes(&v, groupName), resource.TestCheckResourceAttr( - "aws_db_parameter_group.bar", "name", "parameter-group-test-terraform"), + "aws_db_parameter_group.bar", "name", groupName), resource.TestCheckResourceAttr( "aws_db_parameter_group.bar", "family", "mysql5.6"), resource.TestCheckResourceAttr( @@ -187,11 +190,11 @@ func testAccCheckAWSDBParameterGroupDestroy(s *terraform.State) error { return nil } -func testAccCheckAWSDBParameterGroupAttributes(v *rds.DBParameterGroup) resource.TestCheckFunc { +func testAccCheckAWSDBParameterGroupAttributes(v *rds.DBParameterGroup, name string) resource.TestCheckFunc { return func(s *terraform.State) error { - if *v.DBParameterGroupName != "parameter-group-test-terraform" { - return fmt.Errorf("bad name: %#v", v.DBParameterGroupName) + if *v.DBParameterGroupName != name { + return fmt.Errorf("Bad Parameter Group name, expected (%s), got (%s)", name, *v.DBParameterGroupName) } if *v.DBParameterGroupFamily != "mysql5.6" { @@ -250,10 +253,10 @@ func randomString(strlen int) string { return string(result) } -func testAccAWSDBParameterGroupConfig(n int) string { +func testAccAWSDBParameterGroupConfig(n string) string { return fmt.Sprintf(` resource "aws_db_parameter_group" "bar" { - name = "parameter-group-test-terraform-%d" + name = "%s" family = "mysql5.6" description = "Test parameter group for terraform" parameter { @@ -274,10 +277,10 @@ resource "aws_db_parameter_group" "bar" { }`, n) } -func testAccAWSDBParameterGroupAddParametersConfig(n int) string { +func testAccAWSDBParameterGroupAddParametersConfig(n string) string { return fmt.Sprintf(` resource "aws_db_parameter_group" "bar" { - name = "parameter-group-test-terraform-%d" + name = "%s" family = "mysql5.6" description = "Test parameter group for terraform" parameter { @@ -307,10 +310,10 @@ resource "aws_db_parameter_group" "bar" { }`, n) } -func testAccAWSDBParameterGroupOnlyConfig(n int) string { +func testAccAWSDBParameterGroupOnlyConfig(n string) string { return fmt.Sprintf(` resource "aws_db_parameter_group" "bar" { - name = "parameter-group-test-terraform-%d" + name = "%s" family = "mysql5.6" description = "Test parameter group for terraform" }`, n) From 3a2c40d02705b630380539d7b215f3231876514b Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 11 Apr 2016 13:18:56 -0400 Subject: [PATCH 018/337] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d640911183..130c357278c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ IMPROVEMENTS: * provider/google: Make "project" attribute on provider configuration optional [GH-6112] * provider/google: Add "project" argument and attribute to all GCP compute resources which inherit from the provider's value [GH-6112] *provider/google: Deprecate unused "region" attribute in `global_forwarding_rule`; this attribute was never used anywhere in the computation of the resource [GH-6112] + * provider/google: Read more common configuration values from the environment and clarify precedence ordering [GH-6114] * provider/github: Add support for privacy to `github_team` [GH-6116] * provider/cloudstack: Deprecate `ipaddress` in favour of `ip_address` in all resources [GH-6010] * provider/openstack: Allow subnets with no gateway [GH-6060] From fc9825e4c4ca003e0419bdc8b72dd753aa0c7a59 Mon Sep 17 00:00:00 2001 From: Xavier Sellier Date: Mon, 4 Apr 2016 11:40:28 -0400 Subject: [PATCH 019/337] - Add support for 'ssl_mode' options present in lib/pq - Update psotgresql provider's documentation - Enforce default value to 'require' for ssl_mode --- builtin/providers/postgresql/config.go | 3 ++- builtin/providers/postgresql/provider.go | 7 +++++++ .../source/docs/providers/postgresql/index.html.markdown | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/builtin/providers/postgresql/config.go b/builtin/providers/postgresql/config.go index 8bf7b2daa512..3d80ea6a15a6 100644 --- a/builtin/providers/postgresql/config.go +++ b/builtin/providers/postgresql/config.go @@ -13,6 +13,7 @@ type Config struct { Port int Username string Password string + SslMode string } // Client struct holding connection string @@ -23,7 +24,7 @@ type Client struct { //NewClient returns new client config func (c *Config) NewClient() (*Client, error) { - connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=postgres", c.Host, c.Port, c.Username, c.Password) + connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=postgres sslmode=%s", c.Host, c.Port, c.Username, c.Password, c.SslMode) client := Client{ connStr: connStr, diff --git a/builtin/providers/postgresql/provider.go b/builtin/providers/postgresql/provider.go index c048ec3ece76..8a8da8c8f48b 100644 --- a/builtin/providers/postgresql/provider.go +++ b/builtin/providers/postgresql/provider.go @@ -35,6 +35,12 @@ func Provider() terraform.ResourceProvider { DefaultFunc: schema.EnvDefaultFunc("POSTGRESQL_PASSWORD", nil), Description: "Password for postgresql server connection", }, + "ssl_mode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "require", + Description: "Connection mode for postgresql server", + }, }, ResourcesMap: map[string]*schema.Resource{ @@ -52,6 +58,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { Port: d.Get("port").(int), Username: d.Get("username").(string), Password: d.Get("password").(string), + SslMode: d.Get("ssl_mode").(string), } client, err := config.NewClient() diff --git a/website/source/docs/providers/postgresql/index.html.markdown b/website/source/docs/providers/postgresql/index.html.markdown index 36761b626a36..0689e0ddc639 100644 --- a/website/source/docs/providers/postgresql/index.html.markdown +++ b/website/source/docs/providers/postgresql/index.html.markdown @@ -20,6 +20,7 @@ provider "postgresql" { port = 5432 username = "postgres_user" password = "postgres_password" + ssl_mode = "require" } ``` @@ -60,4 +61,5 @@ The following arguments are supported: * `host` - (Required) The address for the postgresql server connection. * `port` - (Optional) The port for the postgresql server connection. (Default 5432) * `username` - (Required) Username for the server connection. -* `password` - (Optional) Password for the server connection. \ No newline at end of file +* `password` - (Optional) Password for the server connection. +* `ssl_mode` - (Optional) Set connection mode for postgresql server (Default "require", more options [lib/pq documentations](https://godoc.org/github.com/lib/pq)). \ No newline at end of file From f840f49fbbf429b0918599c0d91897ec143687e0 Mon Sep 17 00:00:00 2001 From: Jeff LaPlante Date: Mon, 11 Apr 2016 10:23:19 -0700 Subject: [PATCH 020/337] Added support to read and update group attribute from existing vm state. --- .../resource_cloudstack_instance.go | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/builtin/providers/cloudstack/resource_cloudstack_instance.go b/builtin/providers/cloudstack/resource_cloudstack_instance.go index 75b1ce06ede0..fbc0c4860e35 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_instance.go +++ b/builtin/providers/cloudstack/resource_cloudstack_instance.go @@ -240,6 +240,8 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("name", vm.Name) d.Set("display_name", vm.Displayname) d.Set("ipaddress", vm.Nic[0].Ipaddress) + d.Set("group", vm.Group) + //NB cloudstack sometimes sends back the wrong keypair name, so dont update it setValueOrID(d, "network", vm.Nic[0].Networkname, vm.Nic[0].Networkid) @@ -277,6 +279,26 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) d.SetPartial("display_name") } + // Check if the group is changed and if so, update the virtual machine + if d.HasChange("group") { + log.Printf("[DEBUG] Group changed for %s, starting update", name) + + // Create a new parameter struct + p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) + + // Set the new group + p.SetGroup(d.Get("group").(string)) + + // Update the display name + _, err := cs.VirtualMachine.UpdateVirtualMachine(p) + if err != nil { + return fmt.Errorf( + "Error updating the group for instance %s: %s", name, err) + } + + d.SetPartial("group") + } + // Attributes that require reboot to update if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("keypair") { // Before we can actually make these changes, the virtual machine must be stopped From b44f7f28e083e8030f0c682067bd395f938493d5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 11 Apr 2016 10:24:08 -0700 Subject: [PATCH 021/337] Document saved plan use in `terraform apply -help` (#6126) Wording borrowed from the website docs. --- command/apply.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/command/apply.go b/command/apply.go index 62ed3dd9ab0a..f18865bdb193 100644 --- a/command/apply.go +++ b/command/apply.go @@ -276,11 +276,16 @@ func (c *ApplyCommand) Synopsis() string { func (c *ApplyCommand) helpApply() string { helpText := ` -Usage: terraform apply [options] [DIR] +Usage: terraform apply [options] [DIR-OR-PLAN] Builds or changes infrastructure according to Terraform configuration files in DIR. + By default, apply scans the current directory for the configuration + and applies the changes appropriately. However, a path to another + configuration or an execution plan can be provided. Execution plans can be + used to only execute a pre-determined set of actions. + DIR can also be a SOURCE as given to the "init" command. In this case, apply behaves as though "init" was called followed by "apply". This only works for sources that aren't files, and only if the current working From 014f2d5671471e58cfbece31999189686d1476dc Mon Sep 17 00:00:00 2001 From: Jeff LaPlante Date: Mon, 11 Apr 2016 10:26:46 -0700 Subject: [PATCH 022/337] Added group attribute to cloudstack instance documentation markdown --- .../source/docs/providers/cloudstack/r/instance.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/source/docs/providers/cloudstack/r/instance.html.markdown b/website/source/docs/providers/cloudstack/r/instance.html.markdown index 1351ab107346..a78086f3180b 100644 --- a/website/source/docs/providers/cloudstack/r/instance.html.markdown +++ b/website/source/docs/providers/cloudstack/r/instance.html.markdown @@ -31,6 +31,8 @@ The following arguments are supported: * `display_name` - (Optional) The display name of the instance. +* `group` - (Optional) The group name of the instance. + * `service_offering` - (Required) The name or ID of the service offering used for this instance. From c5dbf1d04a0f9c0b89be0afa1e87db1e8736b6f8 Mon Sep 17 00:00:00 2001 From: captainill Date: Mon, 11 Apr 2016 13:02:15 -0500 Subject: [PATCH 023/337] annoucnement bnr --- website/.bundle/config | 3 +- website/Gemfile.lock | 3 - .../assets/stylesheets/_announcement-bnr.scss | 141 ++++++++++++++++++ .../assets/stylesheets/application.scss | 1 + website/source/layouts/_announcement-bnr.erb | 18 +++ website/source/layouts/_header.erb | 1 + website/source/layouts/_meta.erb | 2 +- .../source/layouts/svg/_svg-enterprise.erb | 41 +++++ 8 files changed, 204 insertions(+), 6 deletions(-) create mode 100755 website/source/assets/stylesheets/_announcement-bnr.scss create mode 100644 website/source/layouts/_announcement-bnr.erb create mode 100644 website/source/layouts/svg/_svg-enterprise.erb diff --git a/website/.bundle/config b/website/.bundle/config index df11c7518e0c..2fbf0ffd7101 100644 --- a/website/.bundle/config +++ b/website/.bundle/config @@ -1,2 +1 @@ ---- -BUNDLE_DISABLE_SHARED_GEMS: '1' +--- {} diff --git a/website/Gemfile.lock b/website/Gemfile.lock index f6cb5aa72698..b80006599e38 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -187,6 +187,3 @@ PLATFORMS DEPENDENCIES middleman-hashicorp! - -BUNDLED WITH - 1.11.2 diff --git a/website/source/assets/stylesheets/_announcement-bnr.scss b/website/source/assets/stylesheets/_announcement-bnr.scss new file mode 100755 index 000000000000..7520d828b838 --- /dev/null +++ b/website/source/assets/stylesheets/_announcement-bnr.scss @@ -0,0 +1,141 @@ +// +// announcement bnr +// -------------------------------------------------- + +$enterprise-bnr-font-weight: 300; +$enterprise-bnr-consul-color: #B52A55; +$enterprise-color-dark-white: #A9B1B5; + +body{ + // when _announcment-bnr.erb (ie. Consul Enterprise Announcment) is being used in layout we need to push down content to accomodate + // add this class to body + &.-displaying-bnr{ + #header{ + > .container{ + padding-top: 8px; + -webkit-transform: translateY(32px); + -ms-transform: translateY(32px); + transform: translateY(32px); + } + } + + #jumbotron { + .container{ + .jumbo-logo-wrap{ + margin-top: 160px; + } + } + } + + &.page-sub{ + #header{ + > .container{ + padding-bottom: 32px; + } + } + } + } +} + + +#announcement-bnr { + height: 40px; + flex-shrink: 0; + background-color: #000; + + &.-absolute{ + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 9999; + } + + a,p{ + font-size: 14px; + color: $enterprise-color-dark-white; + font-family: $header-font-family; + font-weight: $enterprise-bnr-font-weight; + font-size: 13px; + line-height: 40px; + margin-bottom: 0; + } + + .link-highlight{ + display: inline-block; + margin-left: 3px; + color: lighten($purple, 10%); + font-weight: 400; + -webkit-transform: translateY(1px); + -ms-transform: translateY(1px); + transform: translateY(1px); + } + + .enterprise-logo{ + position: relative; + top: 4px; + + &:hover{ + text-decoration: none; + + svg{ + rect{ + fill: $enterprise-color-dark-white; + } + } + } + + svg{ + width: 156px; + fill: $white; + margin-right: 4px; + margin-left: 3px; + + rect{ + @include transition(all .1s ease-in); + } + } + } +} + +.hcaret{ + display: inline-block; + -moz-transform: translate(0, -1px) rotate(135deg); + -webkit-transform: translate(0, -1px) rotate(135deg); + transform: translate(0, -1px) rotate(135deg); + width: 7px; + height: 7px; + border-top: 1px solid lighten($purple, 10%); + border-left: 1px solid lighten($purple, 10%); + @include transition(all .1s ease-in); +} + +@media (max-width: 768px) { + #announcement-bnr { + .tagline{ + display: none; + } + } +} + +@media (max-width: 320px) { + #announcement-bnr { + a,p{ + font-size: 12px; + } + + .link-highlight{ + display: inline-block; + margin-left: 1px; + } + + .enterprise-logo svg{ + width: 128px; + margin-left: 2px; + } + + .hcaret{ + display: none; + } + } +} diff --git a/website/source/assets/stylesheets/application.scss b/website/source/assets/stylesheets/application.scss index 3776f905661a..27dd8558462d 100755 --- a/website/source/assets/stylesheets/application.scss +++ b/website/source/assets/stylesheets/application.scss @@ -22,6 +22,7 @@ @import 'hashicorp-shared/_hashicorp-sidebar'; // Components +@import '_announcement-bnr'; @import '_header'; @import '_footer'; @import '_jumbotron'; diff --git a/website/source/layouts/_announcement-bnr.erb b/website/source/layouts/_announcement-bnr.erb new file mode 100644 index 000000000000..4773605a682c --- /dev/null +++ b/website/source/layouts/_announcement-bnr.erb @@ -0,0 +1,18 @@ +
+
+
+
+

+ Announcing + + Collaborative Infrastructure Automation + + Find out more + +

+
+
+
+
diff --git a/website/source/layouts/_header.erb b/website/source/layouts/_header.erb index f6a3533533bb..8d98f7095d3f 100644 --- a/website/source/layouts/_header.erb +++ b/website/source/layouts/_header.erb @@ -1,4 +1,5 @@