diff --git a/outscale/data_source_outscale_vm.go b/outscale/data_source_outscale_vm.go index 4c6964187..fdb08168f 100644 --- a/outscale/data_source_outscale_vm.go +++ b/outscale/data_source_outscale_vm.go @@ -38,8 +38,6 @@ func dataSourceOutscaleOAPIVMRead(d *schema.ResourceData, meta interface{}) erro params.Filters.VmIds = &[]string{instanceID.(string)} } - log.Printf("[DEBUG] ReadVmsRequest -> %+v\n", params) - var resp oscgo.ReadVmsResponse err := resource.Retry(30*time.Second, func() *resource.RetryError { rp, httpResp, err := client.VmApi.ReadVms(context.Background()).ReadVmsRequest(params).Execute() @@ -82,6 +80,16 @@ func dataSourceOutscaleOAPIVMRead(d *schema.ResourceData, meta interface{}) erro // Populate vm attribute fields with the returned vm return resourceDataAttrSetter(d, func(set AttributeSetter) error { d.SetId(vm.GetVmId()) + + booTags, errTags := utils.GetBsuTagsMaps(vm, client) + if errTags != nil { + return errTags + } + if err := d.Set("block_device_mappings_created", getOscAPIVMBlockDeviceMapping( + booTags, vm.GetBlockDeviceMappings())); err != nil { + return err + } + return oapiVMDescriptionAttributes(set, &vm) }) } @@ -91,10 +99,6 @@ func oapiVMDescriptionAttributes(set AttributeSetter, vm *oscgo.Vm) error { if err := set("architecture", vm.GetArchitecture()); err != nil { return err } - if err := set("block_device_mappings_created", getOscAPIVMBlockDeviceMapping(vm.GetBlockDeviceMappings())); err != nil { - log.Printf("[DEBUG] BLOCKING DEVICE MAPPING ERR %+v", err) - return err - } if err := set("bsu_optimized", vm.GetBsuOptimized()); err != nil { return err } @@ -195,21 +199,26 @@ func oapiVMDescriptionAttributes(set AttributeSetter, vm *oscgo.Vm) error { return set("vm_type", vm.GetVmType()) } -func getOscAPIVMBlockDeviceMapping(blockDeviceMappings []oscgo.BlockDeviceMappingCreated) []map[string]interface{} { - blockDeviceMapping := make([]map[string]interface{}, len(blockDeviceMappings)) - - for k, v := range blockDeviceMappings { - blockDeviceMapping[k] = map[string]interface{}{ - "device_name": aws.StringValue(v.DeviceName), - "bsu": map[string]interface{}{ - "delete_on_vm_deletion": fmt.Sprintf("%t", aws.BoolValue(v.GetBsu().DeleteOnVmDeletion)), - "volume_id": aws.StringValue(v.GetBsu().VolumeId), - "state": aws.StringValue(v.GetBsu().State), - "link_date": aws.StringValue(v.GetBsu().LinkDate), - }, +func getOscAPIVMBlockDeviceMapping(busTagsMaps map[string]interface{}, blockDeviceMappings []oscgo.BlockDeviceMappingCreated) (blockDeviceMapping []map[string]interface{}) { + for _, v := range blockDeviceMappings { + blockDevice := map[string]interface{}{ + "device_name": v.GetDeviceName(), + "bsu": getbusToSet(v.GetBsu(), busTagsMaps, *v.DeviceName), } + blockDeviceMapping = append(blockDeviceMapping, blockDevice) } - return blockDeviceMapping + return +} + +func getbusToSet(bsu oscgo.BsuCreated, busTagsMaps map[string]interface{}, deviceName string) (res []map[string]interface{}) { + res = append(res, map[string]interface{}{ + "delete_on_vm_deletion": bsu.GetDeleteOnVmDeletion(), + "volume_id": bsu.GetVolumeId(), + "state": bsu.GetState(), + "link_date": bsu.GetLinkDate(), + "tags": getOscAPITagSet(busTagsMaps[deviceName].([]oscgo.ResourceTag)), + }) + return } func getOAPIVMSecurityGroups(groupSet []oscgo.SecurityGroupLight) []map[string]interface{} { @@ -284,7 +293,7 @@ func getOApiVMAttributesSchema() map[string]*schema.Schema { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "bsu": { - Type: schema.TypeMap, + Type: schema.TypeList, Optional: true, Computed: true, Elem: &schema.Resource{ @@ -298,13 +307,14 @@ func getOApiVMAttributesSchema() map[string]*schema.Schema { Computed: true, }, "state": { - Type: schema.TypeInt, + Type: schema.TypeString, Computed: true, }, "volume_id": { - Type: schema.TypeFloat, + Type: schema.TypeString, Computed: true, }, + "tags": tagsListOAPISchema(), }, }, }, diff --git a/outscale/data_source_outscale_vms.go b/outscale/data_source_outscale_vms.go index 4cefb246d..cfc50162a 100644 --- a/outscale/data_source_outscale_vms.go +++ b/outscale/data_source_outscale_vms.go @@ -114,10 +114,10 @@ func dataSourceOutscaleOApiVMSRead(d *schema.ResourceData, meta interface{}) err } d.SetId(resource.UniqueId()) - return d.Set("vms", dataSourceOAPIVMS(filteredVms)) + return d.Set("vms", dataSourceOAPIVMS(filteredVms, client)) } -func dataSourceOAPIVMS(i []oscgo.Vm) []map[string]interface{} { +func dataSourceOAPIVMS(i []oscgo.Vm, conn *oscgo.APIClient) []map[string]interface{} { vms := make([]map[string]interface{}, len(i)) for index, v := range i { vm := make(map[string]interface{}) @@ -130,6 +130,8 @@ func dataSourceOAPIVMS(i []oscgo.Vm) []map[string]interface{} { if err := oapiVMDescriptionAttributes(setterFunc, &v); err != nil { log.Fatalf("[DEBUG] oapiVMDescriptionAttributes ERROR %+v", err) } + mapsTags, _ := utils.GetBsuTagsMaps(v, conn) + vm["block_device_mappings_created"] = getOscAPIVMBlockDeviceMapping(mapsTags, v.GetBlockDeviceMappings()) vm["tags"] = getOscAPITagSet(v.GetTags()) vms[index] = vm diff --git a/outscale/oapi_tags.go b/outscale/oapi_tags.go index ae7530e0a..455f37ebc 100644 --- a/outscale/oapi_tags.go +++ b/outscale/oapi_tags.go @@ -16,23 +16,73 @@ import ( func setOSCAPITags(conn *oscgo.APIClient, d *schema.ResourceData) error { - if d.HasChange("tags") { - oraw, nraw := d.GetChange("tags") - o := oraw.(*schema.Set) - n := nraw.(*schema.Set) - create, remove := diffOSCAPITags(tagsFromSliceMap(o), tagsFromSliceMap(n)) - - // Set tag - if len(remove) > 0 { + oraw, nraw := d.GetChange("tags") + o := oraw.(*schema.Set) + n := nraw.(*schema.Set) + create, remove := diffOSCAPITags(tagsFromSliceMap(o), tagsFromSliceMap(n)) + resourceId := d.Id() + // Set tag + if len(remove) > 0 { + err := resource.Retry(60*time.Second, func() *resource.RetryError { + _, httpResp, err := conn.TagApi.DeleteTags(context.Background()).DeleteTagsRequest(oscgo.DeleteTagsRequest{ + ResourceIds: []string{resourceId}, + Tags: remove, + }).Execute() + if err != nil { + return utils.CheckThrottling(httpResp, err) + } + return nil + }) + if err != nil { + return err + } + } + if len(create) > 0 { + err := resource.Retry(60*time.Second, func() *resource.RetryError { + _, httpResp, err := conn.TagApi.CreateTags(context.Background()).CreateTagsRequest(oscgo.CreateTagsRequest{ + ResourceIds: []string{resourceId}, + Tags: create, + }).Execute() + if err != nil { + return utils.CheckThrottling(httpResp, err) + } + return nil + }) + if err != nil { + return err + } + } + return nil +} + +func updateBsuTags(conn *oscgo.APIClient, d *schema.ResourceData, addTags map[string]interface{}, delTags map[string]interface{}) error { + + var resp oscgo.ReadVmsResponse + err := resource.Retry(60*time.Second, func() *resource.RetryError { + rp, httpResp, err := conn.VmApi.ReadVms(context.Background()).ReadVmsRequest(oscgo.ReadVmsRequest{ + Filters: &oscgo.FiltersVm{ + VmIds: &[]string{d.Id()}, + }, + }).Execute() + + if err != nil { + return utils.CheckThrottling(httpResp, err) + } + resp = rp + return nil + }) + if err != nil { + return err + } + + if delTags != nil { + for dName := range delTags { err := resource.Retry(60*time.Second, func() *resource.RetryError { _, httpResp, err := conn.TagApi.DeleteTags(context.Background()).DeleteTagsRequest(oscgo.DeleteTagsRequest{ - ResourceIds: []string{d.Id()}, - Tags: remove, + ResourceIds: []string{utils.GetBsuId(resp.GetVms()[0], dName)}, + Tags: tagsFromSliceMap(delTags[dName].(*schema.Set)), }).Execute() if err != nil { - if strings.Contains(fmt.Sprint(err), ".NotFound") { - return resource.RetryableError(err) // retry - } return utils.CheckThrottling(httpResp, err) } return nil @@ -41,16 +91,15 @@ func setOSCAPITags(conn *oscgo.APIClient, d *schema.ResourceData) error { return err } } - if len(create) > 0 { + } + if addTags != nil { + for dName := range addTags { err := resource.Retry(60*time.Second, func() *resource.RetryError { _, httpResp, err := conn.TagApi.CreateTags(context.Background()).CreateTagsRequest(oscgo.CreateTagsRequest{ - ResourceIds: []string{d.Id()}, - Tags: create, + ResourceIds: []string{utils.GetBsuId(resp.GetVms()[0], dName)}, + Tags: tagsFromSliceMap(addTags[dName].(*schema.Set)), }).Execute() if err != nil { - if strings.Contains(fmt.Sprint(err), ".NotFound") { - return resource.RetryableError(err) // retry - } return utils.CheckThrottling(httpResp, err) } return nil @@ -60,7 +109,6 @@ func setOSCAPITags(conn *oscgo.APIClient, d *schema.ResourceData) error { } } } - return nil } diff --git a/outscale/resource_outscale_vm.go b/outscale/resource_outscale_vm.go index 168245a4e..4fe816f9b 100644 --- a/outscale/resource_outscale_vm.go +++ b/outscale/resource_outscale_vm.go @@ -37,7 +37,6 @@ func resourceOutscaleOApiVM() *schema.Resource { "block_device_mappings": { Type: schema.TypeList, Optional: true, - //ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "bsu": { @@ -85,6 +84,7 @@ func resourceOutscaleOApiVM() *schema.Resource { Optional: true, ForceNew: true, }, + "tags": tagsListOAPISchema(), }, }, }, @@ -376,13 +376,11 @@ func resourceOutscaleOApiVM() *schema.Resource { }, "block_device_mappings_created": { Type: schema.TypeList, - Optional: true, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "bsu": { - Type: schema.TypeMap, - Optional: true, + Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -395,19 +393,20 @@ func resourceOutscaleOApiVM() *schema.Resource { Computed: true, }, "state": { - Type: schema.TypeInt, + Type: schema.TypeString, Computed: true, }, "volume_id": { - Type: schema.TypeFloat, + Type: schema.TypeString, Computed: true, }, + "tags": tagsListOAPISchema(), }, }, }, "device_name": { Type: schema.TypeString, - Optional: true, + Computed: true, }, }, }, @@ -524,11 +523,10 @@ func resourceOutscaleOApiVM() *schema.Resource { func resourceOAPIVMCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*OutscaleClient).OSCAPI - vmOpts, err := buildCreateVmsRequest(d, meta) + vmOpts, bsuMapsTags, err := buildCreateVmsRequest(d, meta) if err != nil { return err } - vState := d.Get("state").(string) if vState != "stopped" && vState != "running" { return fmt.Errorf("Error: state should be `stopped or running`") @@ -578,7 +576,17 @@ func resourceOAPIVMCreate(d *schema.ResourceData, meta interface{}) error { } } - log.Println("[DEBUG] imprimo log subnet") + if bsuMapsTags != nil { + for _, tMaps := range bsuMapsTags { + for dName, tags := range tMaps { + err := assignTags(tags.(*schema.Set), utils.GetBsuId(vm, dName), conn) + if err != nil { + return err + } + } + } + } + if tags, ok := d.GetOk("tags"); ok { err := assignTags(tags.(*schema.Set), vm.GetVmId(), conn) if err != nil { @@ -668,6 +676,17 @@ func resourceOAPIVMRead(d *schema.ResourceData, meta interface{}) error { return err } d.SetId(vm.GetVmId()) + + bsuTagsMaps, errTags := utils.GetBsuTagsMaps(vm, conn) + if errTags != nil { + return errTags + } + + if err := d.Set("block_device_mappings_created", getOscAPIVMBlockDeviceMapping( + bsuTagsMaps, vm.GetBlockDeviceMappings())); err != nil { + return err + } + return oapiVMDescriptionAttributes(set, &vm) }); err != nil { return err @@ -825,8 +844,15 @@ func resourceOAPIVMUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChange("block_device_mappings") && !d.IsNewResource() { maps := d.Get("block_device_mappings").([]interface{}) - mappings := []oscgo.BlockDeviceMappingVmUpdate{} + oldT, newT := d.GetChange("block_device_mappings") + oldMapsTags, newMapsTags := getChangeTags(oldT, newT) + if oldMapsTags != nil || newMapsTags != nil { + if err := updateBsuTags(conn, d, oldMapsTags, newMapsTags); err != nil { + return err + } + } + mappings := []oscgo.BlockDeviceMappingVmUpdate{} for _, m := range maps { f := m.(map[string]interface{}) mapping := oscgo.BlockDeviceMappingVmUpdate{} @@ -855,7 +881,6 @@ func resourceOAPIVMUpdate(d *schema.ResourceData, meta interface{}) error { mapping.SetBsu(bsu) } } - mappings = append(mappings, mapping) } @@ -904,6 +929,38 @@ out: return resourceOAPIVMRead(d, meta) } +func getChangeTags(oldCh interface{}, newCh interface{}) (map[string]interface{}, map[string]interface{}) { + oldMapsTags := getbsuMapsTags(oldCh.([]interface{})) + newMapsTags := getbsuMapsTags(newCh.([]interface{})) + addMapsTags := make(map[string]interface{}) + delMapsTags := make(map[string]interface{}) + + for v := range oldMapsTags { + inter := oldMapsTags[v].(*schema.Set).Intersection(newMapsTags[v].(*schema.Set)) + if add := oldMapsTags[v].(*schema.Set).Difference(inter); len(add.List()) > 0 { + addMapsTags[v] = add + } + if del := newMapsTags[v].(*schema.Set).Difference(inter); len(del.List()) > 0 { + delMapsTags[v] = del + } + } + return delMapsTags, addMapsTags +} + +func getbsuMapsTags(changeMaps []interface{}) map[string]interface{} { + mapsTags := make(map[string]interface{}) + + for _, value := range changeMaps { + val := value.(map[string]interface{}) + bsuMaps := val["bsu"].([]interface{}) + for _, v := range bsuMaps { + bsu := v.(map[string]interface{}) + bsu_tags, _ := bsu["tags"] + mapsTags[val["device_name"].(string)] = bsu_tags + } + } + return mapsTags +} func resourceOAPIVMDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*OutscaleClient).OSCAPI @@ -947,7 +1004,7 @@ func resourceOAPIVMDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.CreateVmsRequest, error) { +func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.CreateVmsRequest, []map[string]interface{}, error) { request := oscgo.CreateVmsRequest{ DeletionProtection: oscgo.PtrBool(d.Get("deletion_protection").(bool)), BootOnCreation: oscgo.PtrBool(true), @@ -958,7 +1015,7 @@ func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.Crea placement, err := expandPlacement(d) if err != nil { - return request, err + return request, nil, err } else if placement != nil { request.SetPlacement(*placement) } @@ -967,9 +1024,9 @@ func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.Crea if subNet != "" { request.SetSubnetId(subNet) } - blockDevices, err := expandBlockDeviceOApiMappings(d) + blockDevices, bsuMapsTags, err := expandBlockDeviceOApiMappings(d) if err != nil { - return request, err + return request, nil, err } if len(blockDevices) > 0 { request.SetBlockDeviceMappings(blockDevices) @@ -977,7 +1034,7 @@ func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.Crea if nics := buildNetworkOApiInterfaceOpts(d); len(nics) > 0 { if subNet != "" || placement != nil { - return request, errors.New("If you specify nics parameter, you must not specify subnet_id and placement parameters.") + return request, nil, errors.New("If you specify nics parameter, you must not specify subnet_id and placement parameters.") } request.SetNics(nics) } @@ -996,7 +1053,7 @@ func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.Crea nestedVirtualization := d.Get("nested_virtualization").(bool) if tenacy := d.Get("placement_tenancy").(string); nestedVirtualization && tenacy != "dedicated" { - return request, errors.New("The field nested_virtualization can be true, only if placement_tenancy is \"dedicated\".") + return request, nil, errors.New("The field nested_virtualization can be true, only if placement_tenancy is \"dedicated\".") } request.SetNestedVirtualization(nestedVirtualization) @@ -1024,21 +1081,21 @@ func buildCreateVmsRequest(d *schema.ResourceData, meta interface{}) (oscgo.Crea request.SetPerformance(v) } - return request, nil + return request, bsuMapsTags, nil } -func expandBlockDeviceOApiMappings(d *schema.ResourceData) ([]oscgo.BlockDeviceMappingVmCreation, error) { +func expandBlockDeviceOApiMappings(d *schema.ResourceData) ([]oscgo.BlockDeviceMappingVmCreation, []map[string]interface{}, error) { var blockDevices []oscgo.BlockDeviceMappingVmCreation block := d.Get("block_device_mappings").([]interface{}) - - for _, v := range block { + bsuMapsTags := make([]map[string]interface{}, len(block)) + for k, v := range block { blockDevice := oscgo.BlockDeviceMappingVmCreation{} value := v.(map[string]interface{}) - - if bsu, ok := value["bsu"].([]interface{}); ok && len(bsu) > 0 { - expandBSU, err := expandBlockDeviceBSU(bsu[0].(map[string]interface{})) + if bsu := value["bsu"].([]interface{}); len(bsu) > 0 { + expandBSU, mapsTags, err := expandBlockDeviceBSU(bsu[0].(map[string]interface{}), value["device_name"].(string)) + bsuMapsTags[k] = mapsTags if err != nil { - return nil, err + return nil, nil, err } blockDevice.SetBsu(expandBSU) } @@ -1053,21 +1110,22 @@ func expandBlockDeviceOApiMappings(d *schema.ResourceData) ([]oscgo.BlockDeviceM } blockDevices = append(blockDevices, blockDevice) } - return blockDevices, nil + return blockDevices, bsuMapsTags, nil } -func expandBlockDeviceBSU(bsu map[string]interface{}) (oscgo.BsuToCreate, error) { +func expandBlockDeviceBSU(bsu map[string]interface{}, deviceName string) (oscgo.BsuToCreate, map[string]interface{}, error) { + bsuMapsTags := make(map[string]interface{}) bsuToCreate := oscgo.BsuToCreate{} snapshotID := bsu["snapshot_id"].(string) volumeType := bsu["volume_type"].(string) volumeSize := int32(bsu["volume_size"].(int)) if snapshotID == "" && volumeSize == 0 { - return bsuToCreate, fmt.Errorf("Error: 'volume_size' parameter is required if the volume is not created from a snapshot (SnapshotId unspecified)") + return bsuToCreate, nil, fmt.Errorf("Error: 'volume_size' parameter is required if the volume is not created from a snapshot (SnapshotId unspecified)") } if iops, _ := bsu["iops"]; iops.(int) > 0 { if volumeType != "io1" { - return bsuToCreate, fmt.Errorf("Error: %s", utils.VolumeIOPSError) + return bsuToCreate, nil, fmt.Errorf("Error: %s", utils.VolumeIOPSError) } bsuToCreate.SetIops(int32(iops.(int))) } else { @@ -1085,7 +1143,11 @@ func expandBlockDeviceBSU(bsu map[string]interface{}) (oscgo.BsuToCreate, error) if deleteOnVMDeletion, ok := bsu["delete_on_vm_deletion"]; ok && deleteOnVMDeletion != "" { bsuToCreate.SetDeleteOnVmDeletion(cast.ToBool(deleteOnVMDeletion)) } - return bsuToCreate, nil + if bsu_tags, _ := bsu["tags"]; len(bsu_tags.(*schema.Set).List()) != 0 { + bsuMapsTags[deviceName] = bsu_tags + } + + return bsuToCreate, bsuMapsTags, nil } func buildNetworkOApiInterfaceOpts(d *schema.ResourceData) []oscgo.NicForVmCreation { diff --git a/outscale/resource_outscale_vm_test.go b/outscale/resource_outscale_vm_test.go index 3d00d9866..e1969cf40 100644 --- a/outscale/resource_outscale_vm_test.go +++ b/outscale/resource_outscale_vm_test.go @@ -396,6 +396,10 @@ func testAccCheckOutscaleVMWithMultiBlockDeviceMapping(region, omi, keypair stri volume_type = "gp2" snapshot_id = outscale_snapshot.snapshot.id delete_on_vm_deletion = false + tags { + key = "name" + value = "bsu-tags-gp2" + } } } diff --git a/utils/utils.go b/utils/utils.go index b603311a3..9137023d7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "context" "encoding/json" "fmt" "log" @@ -81,6 +82,56 @@ func ToJSONString(v interface{}) string { return string(pretty) } +func GetBsuId(vmResp oscgo.Vm, deviceName string) string { + diskID := "" + blocks := vmResp.GetBlockDeviceMappings() + + for _, v := range blocks { + if v.GetDeviceName() == deviceName { + diskID = aws.StringValue(v.GetBsu().VolumeId) + break + } + } + return diskID +} + +func getBsuTags(volumeId string, conn *oscgo.APIClient) ([]oscgo.ResourceTag, error) { + request := oscgo.ReadVolumesRequest{ + Filters: &oscgo.FiltersVolume{VolumeIds: &[]string{volumeId}}, + } + var resp oscgo.ReadVolumesResponse + err := resource.Retry(5*time.Minute, func() *resource.RetryError { + r, httpResp, err := conn.VolumeApi.ReadVolumes(context.Background()).ReadVolumesRequest(request).Execute() + if err != nil { + return CheckThrottling(httpResp, err) + } + resp = r + return nil + }) + if err != nil { + return nil, err + } + return resp.GetVolumes()[0].GetTags(), nil +} + +func GetBsuTagsMaps(vmResp oscgo.Vm, conn *oscgo.APIClient) (map[string]interface{}, error) { + + blocks := vmResp.GetBlockDeviceMappings() + bsuTagsMaps := make(map[string]interface{}) + for _, v := range blocks { + volumeId := aws.StringValue(v.GetBsu().VolumeId) + bsuTags, err := getBsuTags(volumeId, conn) + if err != nil { + return nil, err + } + if bsuTags != nil { + bsuTagsMaps[v.GetDeviceName()] = bsuTags + } + } + + return bsuTagsMaps, nil +} + func GetErrorResponse(err error) error { if e, ok := err.(oscgo.GenericOpenAPIError); ok { if errorResponse, oker := e.Model().(oscgo.ErrorResponse); oker {