package azure import ( "context" "errors" "fmt" "regexp" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/go-autorest/autorest/to" "github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/core" ) // getVM tries to get a single azure vm. func (m *Metadata) getVM(ctx context.Context, providerID string) (core.Instance, error) { _, resourceGroup, instanceName, err := splitVMProviderID(providerID) if err != nil { return core.Instance{}, err } vmResp, err := m.virtualMachinesAPI.Get(ctx, resourceGroup, instanceName, nil) if err != nil { return core.Instance{}, err } interfaceIPConfigurations, err := m.getVMInterfaces(ctx, vmResp.VirtualMachine, resourceGroup) if err != nil { return core.Instance{}, err } return convertVMToCoreInstance(vmResp.VirtualMachine, interfaceIPConfigurations) } // listVMs lists all individual VMs in the current resource group. func (m *Metadata) listVMs(ctx context.Context, resourceGroup string) ([]core.Instance, error) { instances := []core.Instance{} pager := m.virtualMachinesAPI.List(resourceGroup, nil) for pager.NextPage(ctx) { for _, vm := range pager.PageResponse().Value { if vm == nil { continue } interfaces, err := m.getVMInterfaces(ctx, *vm, resourceGroup) if err != nil { return nil, err } instance, err := convertVMToCoreInstance(*vm, interfaces) if err != nil { return nil, err } instances = append(instances, instance) } } return instances, nil } // setTag merges key-value pair into VM tags. func (m *Metadata) setTag(ctx context.Context, key, value string) error { instanceMetadata, err := m.imdsAPI.Retrieve(ctx) if err != nil { return err } _, err = m.tagsAPI.UpdateAtScope(ctx, instanceMetadata.Compute.ResourceID, armresources.TagsPatchResource{ Operation: armresources.TagsPatchOperationMerge.ToPtr(), Properties: &armresources.Tags{ Tags: map[string]*string{ key: to.StringPtr(value), }, }, }, nil) return err } // splitVMProviderID splits a provider's id belonging to a single azure instance into core components. // A providerID for individual VMs is build after the following schema: // - 'azure:///subscriptions//resourceGroups//providers/Microsoft.Compute/virtualMachines/' func splitVMProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) { // providerIDregex is a regex matching an azure vm providerID with each part of the URI being a submatch. providerIDregex := regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`) matches := providerIDregex.FindStringSubmatch(providerID) if len(matches) != 4 { return "", "", "", errors.New("error splitting providerID") } return matches[1], matches[2], matches[3], nil } // convertVMToCoreInstance converts an azure virtual machine with interface configurations into a core.Instance. func convertVMToCoreInstance(vm armcompute.VirtualMachine, interfaceIPConfigs []*armnetwork.InterfaceIPConfiguration) (core.Instance, error) { if vm.Name == nil || vm.ID == nil { return core.Instance{}, fmt.Errorf("retrieving instance from armcompute API client returned invalid instance Name (%v) or ID (%v)", vm.Name, vm.ID) } var sshKeys map[string][]string if vm.Properties == nil || vm.Properties.OSProfile == nil || vm.Properties.OSProfile.LinuxConfiguration == nil || vm.Properties.OSProfile.LinuxConfiguration.SSH == nil { sshKeys = map[string][]string{} } else { sshKeys = extractSSHKeys(*vm.Properties.OSProfile.LinuxConfiguration.SSH) } metadata := extractInstanceTags(vm.Tags) return core.Instance{ Name: *vm.Name, ProviderID: "azure://" + *vm.ID, Role: cloudprovider.ExtractRole(metadata), IPs: extractPrivateIPs(interfaceIPConfigs), SSHKeys: sshKeys, }, nil }