diff --git a/go.mod b/go.mod index 95cda3b9c9..4758792c30 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-cleanhttp v0.5.0 github.com/hashicorp/terraform v0.12.0 - github.com/huaweicloud/golangsdk v0.0.0-20190801094550-c3228f9b10d6 + github.com/huaweicloud/golangsdk v0.0.0-20190809023913-080d32c8d1aa github.com/jen20/awspolicyequivalence v0.0.0-20170831201602-3d48364a137a github.com/mitchellh/go-homedir v1.0.0 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect diff --git a/go.sum b/go.sum index e737d7e8df..3269039e68 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bA github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/golangsdk v0.0.0-20190801094550-c3228f9b10d6 h1:4ys1PB6+nv2Tm2bN8o0COXV8t4QU/GsM4X7VdWdnrHI= -github.com/huaweicloud/golangsdk v0.0.0-20190801094550-c3228f9b10d6/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= +github.com/huaweicloud/golangsdk v0.0.0-20190809023913-080d32c8d1aa h1:Wj+VsrMNgokKAoDJmzLqwx5TwctvU99nEvltoeEkG4Q= +github.com/huaweicloud/golangsdk v0.0.0-20190809023913-080d32c8d1aa/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jen20/awspolicyequivalence v0.0.0-20170831201602-3d48364a137a h1:FyS/ubzBR5xJlnJGRTwe7GUHpJOR4ukYK3y+LFNffuA= github.com/jen20/awspolicyequivalence v0.0.0-20170831201602-3d48364a137a/go.mod h1:uoIMjNxUfXi48Ci40IXkPRbghZ1vbti6v9LCbNqRgHY= diff --git a/huaweicloud/config.go b/huaweicloud/config.go index f8c82dd6e2..4a5ad3eb88 100644 --- a/huaweicloud/config.go +++ b/huaweicloud/config.go @@ -421,6 +421,20 @@ func (c *Config) blockStorageV2Client(region string) (*golangsdk.ServiceClient, }) } +func (c *Config) computeV1Client(region string) (*golangsdk.ServiceClient, error) { + return huaweisdk.NewComputeV1(c.HwClient, golangsdk.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getHwEndpointType(), + }) +} + +func (c *Config) computeV11Client(region string) (*golangsdk.ServiceClient, error) { + return huaweisdk.NewComputeV11(c.HwClient, golangsdk.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getHwEndpointType(), + }) +} + func (c *Config) computeV2Client(region string) (*golangsdk.ServiceClient, error) { return huaweisdk.NewComputeV2(c.HwClient, golangsdk.EndpointOpts{ Region: c.determineRegion(region), diff --git a/huaweicloud/ecs_tag_v1.go b/huaweicloud/ecs_tag_v1.go new file mode 100644 index 0000000000..e176e9ee76 --- /dev/null +++ b/huaweicloud/ecs_tag_v1.go @@ -0,0 +1,34 @@ +package huaweicloud + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags" +) + +func setTagForInstance(d *schema.ResourceData, meta interface{}, instanceID string, tagmap map[string]interface{}) error { + config := meta.(*Config) + client, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute v1 client: %s", err) + } + + rId := instanceID + taglist := []tags.Tag{} + for k, v := range tagmap { + tag := tags.Tag{ + Key: k, + Value: v.(string), + } + taglist = append(taglist, tag) + } + + createOpts := tags.BatchOpts{Action: tags.ActionCreate, Tags: taglist} + createTags := tags.BatchAction(client, rId, createOpts) + if createTags.Err != nil { + return fmt.Errorf("Error creating HuaweiCloud instance tags: %s", createTags.Err) + } + + return nil +} diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 956e0e78db..2c8ff08c32 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -241,6 +241,7 @@ func Provider() terraform.ResourceProvider { "huaweicloud_dns_recordset_v2": resourceDNSRecordSetV2(), "huaweicloud_dns_zone_v2": resourceDNSZoneV2(), "huaweicloud_dcs_instance_v1": resourceDcsInstanceV1(), + "huaweicloud_ecs_instance_v1": resourceEcsInstanceV1(), "huaweicloud_fw_firewall_group_v2": resourceFWFirewallGroupV2(), "huaweicloud_fw_policy_v2": resourceFWPolicyV2(), "huaweicloud_fw_rule_v2": resourceFWRuleV2(), diff --git a/huaweicloud/resource_huaweicloud_ecs_auto_recovery_v1.go b/huaweicloud/resource_huaweicloud_ecs_auto_recovery_v1.go new file mode 100644 index 0000000000..0b50b7b4a9 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_ecs_auto_recovery_v1.go @@ -0,0 +1,55 @@ +package huaweicloud + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery" +) + +func resourceECSAutoRecoveryV1Read(d *schema.ResourceData, meta interface{}, instanceID string) (bool, error) { + config := meta.(*Config) + client, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return false, fmt.Errorf("Error creating HuaweiCloud client: %s", err) + } + + rId := instanceID + + r, err := auto_recovery.Get(client, rId).Extract() + if err != nil { + return false, err + } + log.Printf("[DEBUG] Retrieved ECS-AutoRecovery:%#v of instance:%s", rId, r) + return strconv.ParseBool(r.SupportAutoRecovery) +} + +func setAutoRecoveryForInstance(d *schema.ResourceData, meta interface{}, instanceID string, ar bool) error { + config := meta.(*Config) + client, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud client: %s", err) + } + + rId := instanceID + + updateOpts := auto_recovery.UpdateOpts{SupportAutoRecovery: strconv.FormatBool(ar)} + + timeout := d.Timeout(schema.TimeoutUpdate) + + log.Printf("[DEBUG] Setting ECS-AutoRecovery for instance:%s with options: %#v", rId, updateOpts) + err = resource.Retry(timeout, func() *resource.RetryError { + err := auto_recovery.Update(client, rId, updateOpts) + if err != nil { + return checkForRetryableError(err) + } + return nil + }) + if err != nil { + return fmt.Errorf("Error setting ECS-AutoRecovery for instance%s: %s", rId, err) + } + return nil +} diff --git a/huaweicloud/resource_huaweicloud_ecs_instance_v1.go b/huaweicloud/resource_huaweicloud_ecs_instance_v1.go new file mode 100644 index 0000000000..7cf21138d2 --- /dev/null +++ b/huaweicloud/resource_huaweicloud_ecs_instance_v1.go @@ -0,0 +1,608 @@ +package huaweicloud + +import ( + "crypto/sha1" + "encoding/hex" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/compute/v2/extensions/secgroups" + "github.com/huaweicloud/golangsdk/openstack/compute/v2/servers" + "github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers" + "github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags" + "github.com/huaweicloud/golangsdk/openstack/networking/v2/ports" +) + +func resourceEcsInstanceV1() *schema.Resource { + return &schema.Resource{ + Create: resourceEcsInstanceV1Create, + Read: resourceEcsInstanceV1Read, + Update: resourceEcsInstanceV1Update, + Delete: resourceEcsInstanceV1Delete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "image_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "flavor": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "user_data": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + // just stash the hash for state & diff comparisons + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + hash := sha1.Sum([]byte(v.(string))) + return hex.EncodeToString(hash[:]) + default: + return "" + } + }, + }, + "password": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + "key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "nics": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MinItems: 1, + MaxItems: 12, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "ip_address": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "mac_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "system_disk_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "SATA", + ValidateFunc: validation.StringInSlice([]string{ + "SATA", "SAS", "SSD", "co-p1", "uh-l1", + }, true), + }, + "system_disk_size": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "data_disks": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MinItems: 1, + MaxItems: 23, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "security_groups": { + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "availability_zone": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "charging_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "postPaid", + ValidateFunc: validation.StringInSlice([]string{ + "prePaid", "postPaid", + }, true), + }, + "period_unit": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "month", + ValidateFunc: validation.StringInSlice([]string{ + "month", "year", + }, true), + }, + "period": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 1, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + ValidateFunc: validateECSTagValue, + }, + "auto_recovery": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceEcsInstanceV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV11Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute V1.1 client: %s", err) + } + computeV1Client, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute V1 client: %s", err) + } + + createOpts := &cloudservers.CreateOpts{ + Name: d.Get("name").(string), + ImageRef: d.Get("image_id").(string), + FlavorRef: d.Get("flavor").(string), + KeyName: d.Get("key_name").(string), + VpcId: d.Get("vpc_id").(string), + SecurityGroups: resourceInstanceSecGroupsV1(d), + AvailabilityZone: d.Get("availability_zone").(string), + Nics: resourceInstanceNicsV1(d), + RootVolume: resourceInstanceRootVolumeV1(d), + DataVolumes: resourceInstanceDataVolumesV1(d), + AdminPass: d.Get("password").(string), + UserData: []byte(d.Get("user_data").(string)), + } + + if d.Get("charging_mode") == "prePaid" { + extendparam := cloudservers.ServerExtendParam{ + ChargingMode: d.Get("charging_mode").(string), + PeriodType: d.Get("period_unit").(string), + PeriodNum: d.Get("period").(int), + } + createOpts.ExtendParam = &extendparam + } + + log.Printf("[DEBUG] Create Options: %#v", createOpts) + + n, err := cloudservers.Create(computeClient, createOpts).ExtractJobResponse() + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud server: %s", err) + } + + if err := cloudservers.WaitForJobSuccess(computeV1Client, int(d.Timeout(schema.TimeoutCreate)/time.Second), n.JobID); err != nil { + return err + } + + entity, err := cloudservers.GetJobEntity(computeV1Client, n.JobID, "server_id") + if err != nil { + return err + } + + if id, ok := entity.(string); ok { + d.SetId(id) + + if hasFilledOpt(d, "tags") { + tagmap := d.Get("tags").(map[string]interface{}) + log.Printf("[DEBUG] Setting tags: %v", tagmap) + err = setTagForInstance(d, meta, id, tagmap) + if err != nil { + log.Printf("[WARN] Error setting tags of instance:%s, err=%s", id, err) + } + } + + if hasFilledOpt(d, "auto_recovery") { + ar := d.Get("auto_recovery").(bool) + log.Printf("[DEBUG] Set auto recovery of instance to %t", ar) + err = setAutoRecoveryForInstance(d, meta, id, ar) + if err != nil { + log.Printf("[WARN] Error setting auto recovery of instance:%s, err=%s", id, err) + } + } + + return resourceEcsInstanceV1Read(d, meta) + } + + return fmt.Errorf("Unexpected conversion error in resourceEcsInstanceV1Create.") +} + +func resourceEcsInstanceV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute client: %s", err) + } + + server, err := cloudservers.Get(computeClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "server") + } + + log.Printf("[DEBUG] Retrieved Server %s: %+v", d.Id(), server) + + d.Set("name", server.Name) + d.Set("image_id", server.Image.ID) + d.Set("flavor", server.Flavor.ID) + d.Set("password", d.Get("password")) + d.Set("key_name", server.KeyName) + d.Set("vpc_id", server.Metadata.VpcID) + d.Set("availability_zone", server.AvailabilityZone) + + secGrpNames := []string{} + for _, sg := range server.SecurityGroups { + secGrpNames = append(secGrpNames, sg.Name) + } + d.Set("security_groups", secGrpNames) + + // Get the instance network and address information + nics := flattenInstanceNicsV1(d, meta, server.Addresses) + d.Set("nics", nics) + + // Set instance tags + if _, ok := d.GetOk("tags"); ok { + Taglist, err := tags.Get(computeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error fetching HuaweiCloud instance tags: %s", err) + } + + tagmap := make(map[string]string) + for _, val := range Taglist.Tags { + tagmap[val.Key] = val.Value + } + if err := d.Set("tags", tagmap); err != nil { + return fmt.Errorf("[DEBUG] Error saving tag to state for HuaweiCloud instance (%s): %s", d.Id(), err) + } + } + + ar, err := resourceECSAutoRecoveryV1Read(d, meta, d.Id()) + if err != nil && !isResourceNotFound(err) { + return fmt.Errorf("Error reading auto recovery of instance:%s, err=%s", d.Id(), err) + } + d.Set("auto_recovery", ar) + + return nil +} + +func resourceEcsInstanceV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute client: %s", err) + } + + var updateOpts servers.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + + if updateOpts != (servers.UpdateOpts{}) { + _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating HuaweiCloud server: %s", err) + } + } + + if d.HasChange("security_groups") { + oldSGRaw, newSGRaw := d.GetChange("security_groups") + oldSGSet := oldSGRaw.(*schema.Set) + newSGSet := newSGRaw.(*schema.Set) + secgroupsToAdd := newSGSet.Difference(oldSGSet) + secgroupsToRemove := oldSGSet.Difference(newSGSet) + + log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) + + log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) + + for _, g := range secgroupsToRemove.List() { + err := secgroups.RemoveServer(computeClient, d.Id(), g.(string)).ExtractErr() + if err != nil && err.Error() != "EOF" { + if _, ok := err.(golangsdk.ErrDefault404); ok { + continue + } + + return fmt.Errorf("Error removing security group (%s) from HuaweiCloud server (%s): %s", g, d.Id(), err) + } else { + log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g, d.Id()) + } + } + + for _, g := range secgroupsToAdd.List() { + err := secgroups.AddServer(computeClient, d.Id(), g.(string)).ExtractErr() + if err != nil && err.Error() != "EOF" { + return fmt.Errorf("Error adding security group (%s) to HuaweiCloud server (%s): %s", g, d.Id(), err) + } + log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g, d.Id()) + } + } + + if d.HasChange("flavor") { + newFlavorId := d.Get("flavor").(string) + + resizeOpts := &servers.ResizeOpts{ + FlavorRef: newFlavorId, + } + log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) + err := servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() + if err != nil { + return fmt.Errorf("Error resizing HuaweiCloud server: %s", err) + } + + // Wait for the instance to finish resizing. + log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"RESIZE"}, + Target: []string{"VERIFY_RESIZE"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) + } + + // Confirm resize. + log.Printf("[DEBUG] Confirming resize") + err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error confirming resize of HuaweiCloud server: %s", err) + } + + stateConf = &resource.StateChangeConf{ + Pending: []string{"VERIFY_RESIZE"}, + Target: []string{"ACTIVE"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) + } + } + + if d.HasChange("tags") { + computeClient, err := config.computeV1Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute v1 client: %s", err) + } + oldTags, err := tags.Get(computeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error fetching HuaweiCloud instance tags: %s", err) + } + if len(oldTags.Tags) > 0 { + deleteopts := tags.BatchOpts{Action: tags.ActionDelete, Tags: oldTags.Tags} + deleteTags := tags.BatchAction(computeClient, d.Id(), deleteopts) + if deleteTags.Err != nil { + return fmt.Errorf("Error updating HuaweiCloud instance tags: %s", deleteTags.Err) + } + } + + if hasFilledOpt(d, "tags") { + tagmap := d.Get("tags").(map[string]interface{}) + if len(tagmap) > 0 { + log.Printf("[DEBUG] Setting tags: %v", tagmap) + err = setTagForInstance(d, meta, d.Id(), tagmap) + if err != nil { + return fmt.Errorf("Error updating tags of instance:%s, err:%s", d.Id(), err) + } + } + } + } + + if d.HasChange("auto_recovery") { + ar := d.Get("auto_recovery").(bool) + log.Printf("[DEBUG] Update auto recovery of instance to %t", ar) + err = setAutoRecoveryForInstance(d, meta, d.Id(), ar) + if err != nil { + return fmt.Errorf("Error updating auto recovery of instance:%s, err:%s", d.Id(), err) + } + } + + return resourceEcsInstanceV1Read(d, meta) +} + +func resourceEcsInstanceV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(GetRegion(d, config)) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute client: %s", err) + } + + log.Printf("[DEBUG] Deleting HuaweiCloud Instance %s", d.Id()) + err = servers.Delete(computeClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting HuaweiCloud server: %s", err) + } + + // Wait for the instance to delete before moving on. + log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE", "SHUTOFF"}, + Target: []string{"DELETED", "SOFT_DELETED"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutDelete), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to delete: %s", + d.Id(), err) + } + + d.SetId("") + return nil +} + +func resourceInstanceNicsV1(d *schema.ResourceData) []cloudservers.Nic { + var nicRequests []cloudservers.Nic + + nics := d.Get("nics").([]interface{}) + for i := range nics { + nic := nics[i].(map[string]interface{}) + nicRequest := cloudservers.Nic{ + SubnetId: nic["network_id"].(string), + IpAddress: nic["ip_address"].(string), + } + + nicRequests = append(nicRequests, nicRequest) + } + return nicRequests +} + +func resourceInstanceRootVolumeV1(d *schema.ResourceData) cloudservers.RootVolume { + volRequest := cloudservers.RootVolume{ + VolumeType: d.Get("system_disk_type").(string), + Size: d.Get("system_disk_size").(int), + } + return volRequest +} + +func resourceInstanceDataVolumesV1(d *schema.ResourceData) []cloudservers.DataVolume { + var volRequests []cloudservers.DataVolume + + vols := d.Get("data_disks").([]interface{}) + for i := range vols { + vol := vols[i].(map[string]interface{}) + volRequest := cloudservers.DataVolume{ + VolumeType: vol["type"].(string), + Size: vol["size"].(int), + } + if vol["snapshot_id"] != "" { + extendparam := cloudservers.VolumeExtendParam{ + SnapshotId: vol["snapshot_id"].(string), + } + volRequest.Extendparam = &extendparam + } + + volRequests = append(volRequests, volRequest) + } + return volRequests +} + +func resourceInstanceSecGroupsV1(d *schema.ResourceData) []cloudservers.SecurityGroup { + rawSecGroups := d.Get("security_groups").(*schema.Set).List() + secgroups := make([]cloudservers.SecurityGroup, len(rawSecGroups)) + for i, raw := range rawSecGroups { + secgroups[i] = cloudservers.SecurityGroup{ + ID: raw.(string), + } + } + return secgroups +} + +func flattenInstanceNicsV1( + d *schema.ResourceData, meta interface{}, addresses map[string][]cloudservers.Address) []map[string]interface{} { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(GetRegion(d, config)) + if err != nil { + log.Printf("Error creating HuaweiCloud networking client: %s", err) + } + + var network string + nics := []map[string]interface{}{} + // Loop through all networks and addresses. + for _, addrs := range addresses { + for _, addr := range addrs { + // Skip if not fixed ip + if addr.Type != "fixed" { + continue + } + + p, err := ports.Get(networkingClient, addr.PortID).Extract() + if err != nil { + network = "" + log.Printf("[DEBUG] flattenInstanceNicsV1: failed to fetch port %s", addr.PortID) + } else { + network = p.NetworkID + } + + v := map[string]interface{}{ + "network_id": network, + "ip_address": addr.Addr, + "mac_address": addr.MacAddr, + } + nics = append(nics, v) + } + } + + log.Printf("[DEBUG] flattenInstanceNicsV1: %#v", nics) + return nics +} diff --git a/huaweicloud/resource_huaweicloud_ecs_instance_v1_test.go b/huaweicloud/resource_huaweicloud_ecs_instance_v1_test.go new file mode 100644 index 0000000000..5218a43c6c --- /dev/null +++ b/huaweicloud/resource_huaweicloud_ecs_instance_v1_test.go @@ -0,0 +1,173 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers" +) + +func TestAccEcsV1Instance_basic(t *testing.T) { + var instance cloudservers.CloudServer + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckEcsV1InstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEcsV1Instance_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckEcsV1InstanceExists("huaweicloud_ecs_instance_v1.instance_1", &instance), + resource.TestCheckResourceAttr( + "huaweicloud_ecs_instance_v1.instance_1", "availability_zone", OS_AVAILABILITY_ZONE), + resource.TestCheckResourceAttr( + "huaweicloud_ecs_instance_v1.instance_1", "auto_recovery", "true"), + ), + }, + { + Config: testAccEcsV1Instance_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckEcsV1InstanceExists("huaweicloud_ecs_instance_v1.instance_1", &instance), + resource.TestCheckResourceAttr( + "huaweicloud_ecs_instance_v1.instance_1", "availability_zone", OS_AVAILABILITY_ZONE), + resource.TestCheckResourceAttr( + "huaweicloud_ecs_instance_v1.instance_1", "auto_recovery", "false"), + ), + }, + }, + }) +} + +func testAccCheckEcsV1InstanceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV1Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "huaweicloud_ecs_instance_v1" { + continue + } + + server, err := cloudservers.Get(computeClient, rs.Primary.ID).Extract() + if err == nil { + if server.Status != "DELETED" { + return fmt.Errorf("Instance still exists") + } + } + } + + return nil +} + +func testAccCheckEcsV1InstanceExists(n string, instance *cloudservers.CloudServer) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV1Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating HuaweiCloud compute client: %s", err) + } + + found, err := cloudservers.Get(computeClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Instance not found") + } + + *instance = *found + + return nil + } +} + +var testAccEcsV1Instance_basic = fmt.Sprintf(` +resource "huaweicloud_ecs_instance_v1" "instance_1" { + name = "server_1" + image_id = "%s" + flavor = "%s" + vpc_id = "%s" + + nics { + network_id = "%s" + } + + system_disk_type = "SAS" + system_disk_size = 40 + + data_disks { + type = "SATA" + size = "10" + } + data_disks { + type = "SAS" + size = "20" + } + + password = "Password@123" + security_groups = ["default"] + availability_zone = "%s" + auto_recovery = true + + tags = { + foo = "bar" + key = "value" + } +} +`, OS_IMAGE_ID, OS_FLAVOR_NAME, OS_VPC_ID, OS_NETWORK_ID, OS_AVAILABILITY_ZONE) + +var testAccEcsV1Instance_update = fmt.Sprintf(` +resource "huaweicloud_compute_secgroup_v2" "secgroup_1" { + name = "secgroup_ecs" + description = "a security group" +} + +resource "huaweicloud_ecs_instance_v1" "instance_1" { + name = "server_updated" + image_id = "%s" + flavor = "%s" + vpc_id = "%s" + + nics { + network_id = "%s" + } + + system_disk_type = "SAS" + system_disk_size = 40 + + data_disks { + type = "SATA" + size = "10" + } + data_disks { + type = "SAS" + size = "20" + } + + password = "Password@123" + security_groups = ["default", "${huaweicloud_compute_secgroup_v2.secgroup_1.name}"] + availability_zone = "%s" + auto_recovery = false + + tags = { + foo = "bar1" + key1 = "value" + } +} +`, OS_IMAGE_ID, OS_FLAVOR_NAME, OS_VPC_ID, OS_NETWORK_ID, OS_AVAILABILITY_ZONE) diff --git a/huaweicloud/validators.go b/huaweicloud/validators.go index 0314036311..7e9dc9bd92 100644 --- a/huaweicloud/validators.go +++ b/huaweicloud/validators.go @@ -274,3 +274,16 @@ func validateVBSBackupDescription(v interface{}, k string) (ws []string, errors } return } + +func validateECSTagValue(v interface{}, k string) (ws []string, errors []error) { + tagmap := v.(map[string]interface{}) + vv := regexp.MustCompile(`^[0-9a-zA-Z-_]+$`) + for k, v := range tagmap { + value := v.(string) + if !vv.MatchString(value) { + errors = append(errors, fmt.Errorf("Tag value must be string only contains digits, letters, underscores(_) and hyphens(-), but got %s=%s", k, value)) + break + } + } + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/client.go b/vendor/github.com/huaweicloud/golangsdk/openstack/client.go index e0c97d6b05..a1ef5fc5a2 100644 --- a/vendor/github.com/huaweicloud/golangsdk/openstack/client.go +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/client.go @@ -689,6 +689,11 @@ func NewComputeV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) ( return sc, err } +func NewComputeV11(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "ecsv1.1") + return sc, err +} + func NewRdsTagV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { sc, err := initClientOpts(client, eo, "network") sc.Endpoint = strings.Replace(sc.Endpoint, "vpc", "rds", 1) diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/requests.go new file mode 100644 index 0000000000..274e3a38bd --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/requests.go @@ -0,0 +1,36 @@ +package auto_recovery + +import ( + "log" + + "github.com/huaweicloud/golangsdk" +) + +type UpdateOpts struct { + SupportAutoRecovery string `json:"support_auto_recovery" required:"true"` +} + +type UpdateOptsBuilder interface { + ToAutoRecoveryUpdateMap() (map[string]interface{}, error) +} + +func (opts UpdateOpts) ToAutoRecoveryUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +func Update(c *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) error { + b, err := opts.ToAutoRecoveryUpdateMap() + if err != nil { + return err + } + log.Printf("[DEBUG] update url:%q, body=%#v", updateURL(c, id), b) + _, err = c.Put(updateURL(c, id), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }) + return err +} + +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/results.go new file mode 100644 index 0000000000..3f1aa83d7f --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/results.go @@ -0,0 +1,16 @@ +package auto_recovery + +import "github.com/huaweicloud/golangsdk" + +type AutoRecovery struct { + SupportAutoRecovery string `json:"support_auto_recovery"` +} + +type GetResult struct { + golangsdk.Result +} + +func (r GetResult) Extract() (*AutoRecovery, error) { + s := &AutoRecovery{} + return s, r.ExtractInto(s) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/urls.go new file mode 100644 index 0000000000..d74450aa57 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery/urls.go @@ -0,0 +1,16 @@ +package auto_recovery + +import "github.com/huaweicloud/golangsdk" + +const ( + rootPath = "cloudservers" + resourcePath = "autorecovery" +) + +func updateURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, resourcePath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return updateURL(c, id) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/requests.go new file mode 100644 index 0000000000..27cae7c3e5 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/requests.go @@ -0,0 +1,189 @@ +package cloudservers + +import ( + "encoding/base64" + + "github.com/huaweicloud/golangsdk" +) + +type CreateOpts struct { + ImageRef string `json:"imageRef" required:"true"` + + FlavorRef string `json:"flavorRef" required:"true"` + + Name string `json:"name" required:"true"` + + UserData []byte `json:"-"` + + // AdminPass sets the root user password. If not set, a randomly-generated + // password will be created and returned in the response. + AdminPass string `json:"adminPass,omitempty"` + + KeyName string `json:"key_name,omitempty"` + + VpcId string `json:"vpcid" required:"true"` + + Nics []Nic `json:"nics" required:"true"` + + PublicIp *PublicIp `json:"publicip,omitempty"` + + Count int `json:"count,omitempty"` + + IsAutoRename *bool `json:"isAutoRename,omitempty"` + + RootVolume RootVolume `json:"root_volume" required:"true"` + + DataVolumes []DataVolume `json:"data_volumes,omitempty"` + + SecurityGroups []SecurityGroup `json:"security_groups,omitempty"` + + AvailabilityZone string `json:"availability_zone" required:"true"` + + ExtendParam *ServerExtendParam `json:"extendparam,omitempty"` + + MetaData *MetaData `json:"metadata,omitempty"` + + SchedulerHints *SchedulerHints `json:"os:scheduler_hints,omitempty"` + + Tags []string `json:"tags,omitempty"` + + ServerTags []ServerTags `json:"server_tags,omitempty"` +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToServerCreateMap() (map[string]interface{}, error) +} + +// ToServerCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.UserData != nil { + var userData string + if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { + userData = base64.StdEncoding.EncodeToString(opts.UserData) + } else { + userData = string(opts.UserData) + } + b["user_data"] = &userData + } + + return map[string]interface{}{"server": b}, nil +} + +type Nic struct { + SubnetId string `json:"subnet_id" required:"true"` + + IpAddress string `json:"ip_address,omitempty"` +} + +type PublicIp struct { + Id string `json:"id,omitempty"` + + Eip *Eip `json:"eip,omitempty"` +} + +type Eip struct { + IpType string `json:"iptype" required:"true"` + + BandWidth *BandWidth `json:"bandwidth" required:"true"` + + ExtendParam *EipExtendParam `json:"extendparam,omitempty"` +} + +type BandWidth struct { + Size int `json:"size,omitempty"` + + ShareType string `json:"sharetype" required:"true"` + + ChargeMode string `json:"chargemode,omitempty"` + + Id string `json:"id,omitempty"` +} + +type EipExtendParam struct { + ChargingMode string `json:"chargingMode,omitempty"` +} + +type RootVolume struct { + VolumeType string `json:"volumetype" required:"true"` + + Size int `json:"size,omitempty"` + + ExtendParam *VolumeExtendParam `json:"extendparam,omitempty"` +} + +type DataVolume struct { + VolumeType string `json:"volumetype" required:"true"` + + Size int `json:"size" required:"true"` + + MultiAttach *bool `json:"multiattach,omitempty"` + + PassThrough *bool `json:"hw:passthrough,omitempty"` + + Extendparam *VolumeExtendParam `json:"extendparam,omitempty"` +} + +type VolumeExtendParam struct { + SnapshotId string `json:"snapshotId,omitempty"` +} + +type ServerExtendParam struct { + ChargingMode string `json:"chargingMode,omitempty"` + + RegionID string `json:"regionID,omitempty"` + + PeriodType string `json:"periodType,omitempty"` + + PeriodNum int `json:"periodNum,omitempty"` + + IsAutoRenew string `json:"isAutoRenew,omitempty"` + + IsAutoPay string `json:"isAutoPay,omitempty"` + + SupportAutoRecovery string `json:"support_auto_recovery,omitempty"` +} + +type MetaData struct { + OpSvcUserId string `json:"op_svc_userid,omitempty"` +} + +type SecurityGroup struct { + ID string `json:"id" required:"true"` +} + +type SchedulerHints struct { + Group string `json:"group,omitempty"` +} + +type ServerTags struct { + Key string `json:"key" required:"true"` + Value string `json:"value,omitempty"` +} + +// Create requests a server to be provisioned to the user in the current tenant. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r JobResult) { + reqBody, err := opts.ToServerCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), reqBody, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) + return +} + +// Get retrieves a particular Server based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(getURL(c, id), &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results.go new file mode 100644 index 0000000000..6c9925f97e --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results.go @@ -0,0 +1,140 @@ +package cloudservers + +import ( + "time" + + "github.com/huaweicloud/golangsdk" +) + +type cloudServerResult struct { + golangsdk.Result +} + +type Flavor struct { + Disk string `json:"disk"` + Vcpus string `json:"vcpus"` + RAM string `json:"ram"` + ID string `json:"id"` + Name string `json:"name"` +} + +// Image defines a image struct in details of a server. +type Image struct { + ID string `json:"id"` +} + +type SysTags struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type OsSchedulerHints struct { + Group []string `json:"group"` +} + +// Metadata is only used for method that requests details on a single server, by ID. +// Because metadata struct must be a map. +type Metadata struct { + ChargingMode string `json:"charging_mode"` + OrderID string `json:"metering.order_id"` + ProductID string `json:"metering.product_id"` + VpcID string `json:"vpc_id"` + EcmResStatus string `json:"EcmResStatus"` + ImageID string `json:"metering.image_id"` + Imagetype string `json:"metering.imagetype"` + Resourcespeccode string `json:"metering.resourcespeccode"` + ImageName string `json:"image_name"` + OsBit string `json:"os_bit"` + LockCheckEndpoint string `json:"lock_check_endpoint"` + LockSource string `json:"lock_source"` + LockSourceID string `json:"lock_source_id"` + LockScene string `json:"lock_scene"` + VirtualEnvType string `json:"virtual_env_type"` +} + +type Address struct { + Version string `json:"version"` + Addr string `json:"addr"` + MacAddr string `json:"OS-EXT-IPS-MAC:mac_addr"` + PortID string `json:"OS-EXT-IPS:port_id"` + Type string `json:"OS-EXT-IPS:type"` +} + +type VolumeAttached struct { + ID string `json:"id"` + DeleteOnTermination string `json:"delete_on_termination"` + BootIndex string `json:"bootIndex"` + Device string `json:"device"` +} + +type SecurityGroups struct { + Name string `json:"name"` +} + +// CloudServer is only used for method that requests details on a single server, by ID. +// Because metadata struct must be a map. +type CloudServer struct { + Status string `json:"status"` + Updated time.Time `json:"updated"` + HostID string `json:"hostId"` + Addresses map[string][]Address `json:"addresses"` + ID string `json:"id"` + Name string `json:"name"` + AccessIPv4 string `json:"accessIPv4"` + AccessIPv6 string `json:"accessIPv6"` + Created time.Time `json:"created"` + Tags []string `json:"tags"` + Description string `json:"description"` + Locked *bool `json:"locked"` + ConfigDrive string `json:"config_drive"` + TenantID string `json:"tenant_id"` + UserID string `json:"user_id"` + HostStatus string `json:"host_status"` + EnterpriseProjectID string `json:"enterprise_project_id"` + SysTags []SysTags `json:"sys_tags"` + Flavor Flavor `json:"flavor"` + Metadata Metadata `json:"metadata"` + SecurityGroups []SecurityGroups `json:"security_groups"` + KeyName string `json:"key_name"` + Image Image `json:"image"` + Progress *int `json:"progress"` + PowerState *int `json:"OS-EXT-STS:power_state"` + VMState string `json:"OS-EXT-STS:vm_state"` + TaskState string `json:"OS-EXT-STS:task_state"` + DiskConfig string `json:"OS-DCF:diskConfig"` + AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` + LaunchedAt string `json:"OS-SRV-USG:launched_at"` + TerminatedAt string `json:"OS-SRV-USG:terminated_at"` + RootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name"` + RamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"` + KernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"` + LaunchIndex *int `json:"OS-EXT-SRV-ATTR:launch_index"` + ReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"` + Hostname string `json:"OS-EXT-SRV-ATTR:hostname"` + UserData string `json:"OS-EXT-SRV-ATTR:user_data"` + Host string `json:"OS-EXT-SRV-ATTR:host"` + InstanceName string `json:"OS-EXT-SRV-ATTR:instance_name"` + HypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"` + VolumeAttached []VolumeAttached `json:"os-extended-volumes:volumes_attached"` + OsSchedulerHints OsSchedulerHints `json:"os:scheduler_hints"` +} + +// NewCloudServer defines the response from details on a single server, by ID. +type NewCloudServer struct { + CloudServer + Metadata map[string]string `json:"metadata"` +} + +// GetResult is the response from a Get operation. Call its Extract +// method to interpret it as a Server. +type GetResult struct { + cloudServerResult +} + +func (r GetResult) Extract() (*CloudServer, error) { + var s struct { + Server *CloudServer `json:"server"` + } + err := r.ExtractInto(&s) + return s.Server, err +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results_job.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results_job.go new file mode 100644 index 0000000000..95a13577b3 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/results_job.go @@ -0,0 +1,116 @@ +package cloudservers + +import ( + "fmt" + "time" + + "github.com/huaweicloud/golangsdk" +) + +type JobResponse struct { + JobID string `json:"job_id"` +} + +type JobStatus struct { + Status string `json:"status"` + Entities JobEntity `json:"entities"` + JobID string `json:"job_id"` + JobType string `json:"job_type"` + BeginTime string `json:"begin_time"` + EndTime string `json:"end_time"` + ErrorCode string `json:"error_code"` + FailReason string `json:"fail_reason"` +} + +type JobEntity struct { + // Specifies the number of subtasks. + // When no subtask exists, the value of this parameter is 0. + SubJobsTotal int `json:"sub_jobs_total"` + + // Specifies the execution information of a subtask. + // When no subtask exists, the value of this parameter is left blank. + SubJobs []SubJob `json:"sub_jobs"` +} + +type SubJob struct { + // Specifies the task ID. + Id string `json:"job_id"` + + // Task type. + Type string `json:"job_type"` + + //Specifies the task status. + // SUCCESS: indicates the task is successfully executed. + // RUNNING: indicates that the task is in progress. + // FAIL: indicates that the task failed. + // INIT: indicates that the task is being initialized. + Status string `json:"status"` + + // Specifies the time when the task started. + BeginTime string `json:"begin_time"` + + // Specifies the time when the task finished. + EndTime string `json:"end_time"` + + // Specifies the returned error code when the task execution fails. + ErrorCode string `json:"error_code"` + + // Specifies the cause of the task execution failure. + FailReason string `json:"fail_reason"` + + // Specifies the object of the task. + Entities map[string]string `json:"entities"` +} + +type JobResult struct { + golangsdk.Result +} + +func (r JobResult) ExtractJobResponse() (*JobResponse, error) { + job := new(JobResponse) + err := r.ExtractInto(job) + return job, err +} + +func (r JobResult) ExtractJobStatus() (*JobStatus, error) { + job := new(JobStatus) + err := r.ExtractInto(job) + return job, err +} + +func WaitForJobSuccess(client *golangsdk.ServiceClient, secs int, jobID string) error { + return golangsdk.WaitFor(secs, func() (bool, error) { + job := new(JobStatus) + _, err := client.Get(jobURL(client, jobID), &job, nil) + time.Sleep(5 * time.Second) + if err != nil { + return false, err + } + + if job.Status == "SUCCESS" { + return true, nil + } + if job.Status == "FAIL" { + err = fmt.Errorf("Job failed with code %s: %s.\n", job.ErrorCode, job.FailReason) + return false, err + } + + return false, nil + }) +} + +func GetJobEntity(client *golangsdk.ServiceClient, jobID string, label string) (interface{}, error) { + job := new(JobStatus) + _, err := client.Get(jobURL(client, jobID), &job, nil) + if err != nil { + return nil, err + } + + if job.Status == "SUCCESS" { + if e := job.Entities.SubJobs[0].Entities[label]; e != "" { + return e, nil + } + } + + return nil, fmt.Errorf("Unexpected conversion error in GetJobEntity.") +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/urls.go new file mode 100644 index 0000000000..e9074c687e --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers/urls.go @@ -0,0 +1,19 @@ +package cloudservers + +import "github.com/huaweicloud/golangsdk" + +func createURL(sc *golangsdk.ServiceClient) string { + return sc.ServiceURL("cloudservers") +} + +func deleteURL(sc *golangsdk.ServiceClient) string { + return sc.ServiceURL("cloudservers", "delete") +} + +func getURL(sc *golangsdk.ServiceClient, serverID string) string { + return sc.ServiceURL("cloudservers", serverID) +} + +func jobURL(sc *golangsdk.ServiceClient, jobId string) string { + return sc.ServiceURL("jobs", jobId) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/requests.go new file mode 100644 index 0000000000..31f9ba2ca3 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/requests.go @@ -0,0 +1,61 @@ +package tags + +import ( + "github.com/huaweicloud/golangsdk" +) + +// Tag is a structure of key value pair. +type Tag struct { + //tag key + Key string `json:"key" required:"true"` + //tag value + Value string `json:"value" required:"true"` +} + +// BatchOptsBuilder allows extensions to add additional parameters to the +// BatchAction request. +type BatchOptsBuilder interface { + ToTagsBatchMap() (map[string]interface{}, error) +} + +// BatchOpts contains all the values needed to perform BatchAction on the instance tags. +type BatchOpts struct { + //List of tags to perform batch operation + Tags []Tag `json:"tags,omitempty"` + //Operator , Possible values are:create, update,delete + Action ActionType `json:"action" required:"true"` +} + +//ActionType specifies the type of batch operation action to be performed +type ActionType string + +var ( + // ActionCreate is used to set action operator to create + ActionCreate ActionType = "create" + // ActionDelete is used to set action operator to delete + ActionDelete ActionType = "delete" +) + +// ToTagsBatchMap builds a BatchAction request body from BatchOpts. +func (opts BatchOpts) ToTagsBatchMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +//BatchAction is used to create ,update or delete the tags of a specified instance. +func BatchAction(client *golangsdk.ServiceClient, serverID string, opts BatchOptsBuilder) (r ActionResults) { + b, err := opts.ToTagsBatchMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(actionURL(client, serverID), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{204}, + }) + return +} + +// Get retrieves the tags of a specific instance. +func Get(client *golangsdk.ServiceClient, serverID string) (r GetResult) { + _, r.Err = client.Get(resourceURL(client, serverID), &r.Body, nil) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/results.go new file mode 100644 index 0000000000..206e2ce2b5 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/results.go @@ -0,0 +1,28 @@ +package tags + +import ( + "github.com/huaweicloud/golangsdk" +) + +type RespTags struct { + //contains list of tags, i.e.key value pair + Tags []Tag `json:"tags"` +} + +type commonResult struct { + golangsdk.Result +} + +type ActionResults struct { + commonResult +} + +type GetResult struct { + commonResult +} + +func (r commonResult) Extract() (*RespTags, error) { + var response RespTags + err := r.ExtractInto(&response) + return &response, err +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/urls.go new file mode 100644 index 0000000000..faaf9da042 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags/urls.go @@ -0,0 +1,17 @@ +package tags + +import "github.com/huaweicloud/golangsdk" + +const ( + rootPath = "servers" + resourcePath = "tags" + actionPath = "tags/action" +) + +func actionURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, actionPath) +} + +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id, resourcePath) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index be4e2c748a..94529390ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -178,7 +178,7 @@ github.com/hashicorp/terraform/svchost/auth github.com/hashicorp/terraform-config-inspect/tfconfig # github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb github.com/hashicorp/yamux -# github.com/huaweicloud/golangsdk v0.0.0-20190801094550-c3228f9b10d6 +# github.com/huaweicloud/golangsdk v0.0.0-20190809023913-080d32c8d1aa github.com/huaweicloud/golangsdk github.com/huaweicloud/golangsdk/openstack github.com/huaweicloud/golangsdk/openstack/antiddos/v1/antiddos @@ -219,6 +219,9 @@ github.com/huaweicloud/golangsdk/openstack/dms/v1/products github.com/huaweicloud/golangsdk/openstack/dms/v1/queues github.com/huaweicloud/golangsdk/openstack/dns/v2/recordsets github.com/huaweicloud/golangsdk/openstack/dns/v2/zones +github.com/huaweicloud/golangsdk/openstack/ecs/v1/auto_recovery +github.com/huaweicloud/golangsdk/openstack/ecs/v1/cloudservers +github.com/huaweicloud/golangsdk/openstack/ecs/v1/tags github.com/huaweicloud/golangsdk/openstack/identity/v3/agency github.com/huaweicloud/golangsdk/openstack/identity/v3/domains github.com/huaweicloud/golangsdk/openstack/identity/v3/groups