示例#1
0
// RegisterResourceProviders registers current subscription to the specified
// resource provider namespaces if they are not already registered. Namespaces
// are case-insensitive.
func (a AzureClient) RegisterResourceProviders(namespaces ...string) error {
	l, err := a.providersClient().List(nil)
	if err != nil {
		return err
	}
	if l.Value == nil {
		return errors.New("Resource Providers list is returned as nil.")
	}

	m := make(map[string]bool)
	for _, p := range *l.Value {
		m[strings.ToLower(to.String(p.Namespace))] = to.String(p.RegistrationState) == "Registered"
	}

	for _, ns := range namespaces {
		registered, ok := m[strings.ToLower(ns)]
		if !ok {
			return fmt.Errorf("Unknown resource provider %q", ns)
		}
		if registered {
			log.Debugf("Already registered for %q", ns)
		} else {
			log.Info("Registering subscription to resource provider.", logutil.Fields{
				"ns":   ns,
				"subs": a.subscriptionID,
			})
			if _, err := a.providersClient().Register(ns); err != nil {
				return err
			}
		}
	}
	return nil
}
示例#2
0
文件: storage.go 项目: bac/juju
func (v *azureVolumeSource) detachVolume(
	vm *compute.VirtualMachine,
	p storage.VolumeAttachmentParams,
	storageAccount *armstorage.Account,
) (updated bool) {

	dataDisksRoot := dataDiskVhdRoot(storageAccount)
	dataDiskName := p.VolumeId
	vhdURI := dataDisksRoot + dataDiskName + vhdExtension

	var dataDisks []compute.DataDisk
	if vm.Properties.StorageProfile.DataDisks != nil {
		dataDisks = *vm.Properties.StorageProfile.DataDisks
	}
	for i, disk := range dataDisks {
		if to.String(disk.Name) != p.VolumeId {
			continue
		}
		if to.String(disk.Vhd.URI) != vhdURI {
			continue
		}
		dataDisks = append(dataDisks[:i], dataDisks[i+1:]...)
		if len(dataDisks) == 0 {
			vm.Properties.StorageProfile.DataDisks = nil
		} else {
			*vm.Properties.StorageProfile.DataDisks = dataDisks
		}
		return true
	}
	return false
}
示例#3
0
func (a AzureClient) findStorageAccount(resourceGroup, location, prefix string, storageType storage.AccountType) (*storage.AccountProperties, error) {
	f := logutil.Fields{
		"type":     storageType,
		"prefix":   prefix,
		"location": location}
	log.Debug("Querying existing storage accounts.", f)
	l, err := a.storageAccountsClient().ListByResourceGroup(resourceGroup)
	if err != nil {
		return nil, err
	}

	if l.Value != nil {
		for _, v := range *l.Value {
			log.Debug("Iterating...", logutil.Fields{
				"name":     to.String(v.Name),
				"type":     storageType,
				"location": to.String(v.Location),
			})
			if to.String(v.Location) == location && v.Properties.AccountType == storageType && strings.HasPrefix(to.String(v.Name), prefix) {
				log.Debug("Found eligible storage account.", logutil.Fields{"name": to.String(v.Name)})
				return v.Properties, nil
			}
		}
	}
	log.Debug("No account matching the pattern is found.", f)
	return nil, err
}
示例#4
0
文件: instance.go 项目: bac/juju
// Addresses is specified in the Instance interface.
func (inst *azureInstance) Addresses() ([]jujunetwork.Address, error) {
	addresses := make([]jujunetwork.Address, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses))
	for _, nic := range inst.networkInterfaces {
		if nic.Properties.IPConfigurations == nil {
			continue
		}
		for _, ipConfiguration := range *nic.Properties.IPConfigurations {
			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
			if privateIpAddress == nil {
				continue
			}
			addresses = append(addresses, jujunetwork.NewScopedAddress(
				to.String(privateIpAddress),
				jujunetwork.ScopeCloudLocal,
			))
		}
	}
	for _, pip := range inst.publicIPAddresses {
		if pip.Properties.IPAddress == nil {
			continue
		}
		addresses = append(addresses, jujunetwork.NewScopedAddress(
			to.String(pip.Properties.IPAddress),
			jujunetwork.ScopePublic,
		))
	}
	return addresses, nil
}
示例#5
0
// NodeResourcesPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client NodeResources) NodeResourcesPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#6
0
// ManagementLockListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client ManagementLockListResult) ManagementLockListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#7
0
// SharedAccessSignatureAuthorizationRuleListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client SharedAccessSignatureAuthorizationRuleListResult) SharedAccessSignatureAuthorizationRuleListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#8
0
// EventHubConsumerGroupsListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client EventHubConsumerGroupsListResult) EventHubConsumerGroupsListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#9
0
// VirtualMachineScaleSetVMListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist
func (client VirtualMachineScaleSetVMListResult) VirtualMachineScaleSetVMListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#10
0
// ResponseWithContinuationVirtualNetworkPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client ResponseWithContinuationVirtualNetwork) ResponseWithContinuationVirtualNetworkPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#11
0
// ClassicAdministratorListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client ClassicAdministratorListResult) ClassicAdministratorListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#12
0
// OperationResultCollectionPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client OperationResultCollection) OperationResultCollectionPreparer() (*http.Request, error) {
	if client.Nextlink == nil || len(to.String(client.Nextlink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.Nextlink)))
}
示例#13
0
// ProviderOperationsMetadataListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client ProviderOperationsMetadataListResult) ProviderOperationsMetadataListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#14
0
// StatusesDefaultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client StatusesDefault) StatusesDefaultPreparer() (*http.Request, error) {
	if client.Nextlink == nil || len(to.String(client.Nextlink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.Nextlink)))
}
示例#15
0
// WorkflowTriggerHistoryListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client WorkflowTriggerHistoryListResult) WorkflowTriggerHistoryListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#16
0
// DataLakeStoreFirewallRuleListResultPreparer prepares a request to retrieve the next set of results. It returns
// nil if no more results exist.
func (client DataLakeStoreFirewallRuleListResult) DataLakeStoreFirewallRuleListResultPreparer() (*http.Request, error) {
	if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 {
		return nil, nil
	}
	return autorest.Prepare(&http.Request{},
		autorest.AsJSON(),
		autorest.AsGet(),
		autorest.WithBaseURL(to.String(client.NextLink)))
}
示例#17
0
文件: environ.go 项目: bac/juju
// allInstances returns all of the instances in the given resource group,
// and optionally ensures that each instance's addresses are up-to-date.
func (env *azureEnviron) allInstances(
	resourceGroup string,
	refreshAddresses bool,
	controllerOnly bool,
) ([]instance.Instance, error) {
	deploymentsClient := resources.DeploymentsClient{env.resources}
	var deploymentsResult resources.DeploymentListResult
	if err := env.callAPI(func() (autorest.Response, error) {
		var err error
		deploymentsResult, err = deploymentsClient.List(resourceGroup, "", nil)
		return deploymentsResult.Response, err
	}); err != nil {
		if deploymentsResult.Response.Response != nil && deploymentsResult.StatusCode == http.StatusNotFound {
			// This will occur if the resource group does not
			// exist, e.g. in a fresh hosted environment.
			return nil, nil
		}
		return nil, errors.Trace(err)
	}
	if deploymentsResult.Value == nil || len(*deploymentsResult.Value) == 0 {
		return nil, nil
	}

	azureInstances := make([]*azureInstance, 0, len(*deploymentsResult.Value))
	for _, deployment := range *deploymentsResult.Value {
		name := to.String(deployment.Name)
		if deployment.Properties == nil || deployment.Properties.Dependencies == nil {
			continue
		}
		if controllerOnly && !isControllerDeployment(deployment) {
			continue
		}
		provisioningState := to.String(deployment.Properties.ProvisioningState)
		inst := &azureInstance{name, provisioningState, env, nil, nil}
		azureInstances = append(azureInstances, inst)
	}

	if len(azureInstances) > 0 && refreshAddresses {
		if err := setInstanceAddresses(
			env.callAPI,
			resourceGroup,
			network.InterfacesClient{env.network},
			network.PublicIPAddressesClient{env.network},
			azureInstances,
		); err != nil {
			return nil, errors.Trace(err)
		}
	}

	instances := make([]instance.Instance, len(azureInstances))
	for i, inst := range azureInstances {
		instances[i] = inst
	}
	return instances, nil
}
示例#18
0
文件: storage.go 项目: bac/juju
// getStorageClient returns a new storage client, given an environ config
// and a constructor.
func getStorageClient(
	newClient internalazurestorage.NewClientFunc,
	storageEndpoint string,
	storageAccount *armstorage.Account,
	storageAccountKey *armstorage.AccountKey,
) (internalazurestorage.Client, error) {
	storageAccountName := to.String(storageAccount.Name)
	const useHTTPS = true
	return newClient(
		storageAccountName,
		to.String(storageAccountKey.Value),
		storageEndpoint,
		azurestorage.DefaultAPIVersion,
		useHTTPS,
	)
}
示例#19
0
func (a AzureClient) CreatePublicIPAddress(ctx *DeploymentContext, resourceGroup, name, location string, isStatic bool) error {
	log.Info("Creating public IP address.", logutil.Fields{
		"name":   name,
		"static": isStatic})

	var ipType network.IPAllocationMethod
	if isStatic {
		ipType = network.Static
	} else {
		ipType = network.Dynamic
	}

	_, err := a.publicIPAddressClient().CreateOrUpdate(resourceGroup, name,
		network.PublicIPAddress{
			Location: to.StringPtr(location),
			Properties: &network.PublicIPAddressPropertiesFormat{
				PublicIPAllocationMethod: ipType,
			},
		}, nil)
	if err != nil {
		return err
	}
	ip, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
	ctx.PublicIPAddressID = to.String(ip.ID)
	return err
}
示例#20
0
func checkName(name string) {
	c, err := helpers.LoadCredentials()
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	ac := storage.NewAccountsClient(c["subscriptionID"])

	spt, err := helpers.NewServicePrincipalTokenFromCredentials(c, azure.PublicCloud.ResourceManagerEndpoint)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
	ac.Authorizer = spt

	ac.Sender = autorest.CreateSender(
		autorest.WithLogging(log.New(os.Stdout, "sdk-example: ", log.LstdFlags)))

	ac.RequestInspector = withInspection()
	ac.ResponseInspector = byInspecting()
	cna, err := ac.CheckNameAvailability(
		storage.AccountCheckNameAvailabilityParameters{
			Name: to.StringPtr(name),
			Type: to.StringPtr("Microsoft.Storage/storageAccounts")})

	if err != nil {
		log.Fatalf("Error: %v", err)
	} else {
		if to.Bool(cna.NameAvailable) {
			fmt.Printf("The name '%s' is available\n", name)
		} else {
			fmt.Printf("The name '%s' is unavailable because %s\n", name, to.String(cna.Message))
		}
	}
}
示例#21
0
文件: storage.go 项目: bac/juju
// virtualMachines returns a mapping of instance IDs to VirtualMachines and
// errors, for each of the specified instance IDs.
func (v *azureVolumeSource) virtualMachines(instanceIds []instance.Id) (map[instance.Id]*maybeVirtualMachine, error) {
	vmsClient := compute.VirtualMachinesClient{v.env.compute}
	var result compute.VirtualMachineListResult
	if err := v.env.callAPI(func() (autorest.Response, error) {
		var err error
		result, err = vmsClient.List(v.env.resourceGroup)
		return result.Response, err
	}); err != nil {
		return nil, errors.Annotate(err, "listing virtual machines")
	}

	all := make(map[instance.Id]*compute.VirtualMachine)
	if result.Value != nil {
		for _, vm := range *result.Value {
			vmCopy := vm
			all[instance.Id(to.String(vm.Name))] = &vmCopy
		}
	}
	results := make(map[instance.Id]*maybeVirtualMachine)
	for _, id := range instanceIds {
		result := &maybeVirtualMachine{vm: all[id]}
		if result.vm == nil {
			result.err = errors.NotFoundf("instance %v", id)
		}
		results[id] = result
	}
	return results, nil
}
示例#22
0
// tokenFromDeviceFlow prints a message to the screen for user to take action to
// consent application on a browser and in the meanwhile the authentication
// endpoint is polled until user gives consent, denies or the flow times out.
// Returned token must be saved.
func tokenFromDeviceFlow(oauthCfg azure.OAuthConfig, tokenPath, clientID, resource string) (*azure.ServicePrincipalToken, error) {
	cl := oauthClient()
	deviceCode, err := azure.InitiateDeviceAuth(&cl, oauthCfg, clientID, resource)
	if err != nil {
		return nil, fmt.Errorf("Failed to start device auth: %v", err)
	}
	log.Debug("Retrieved device code.", logutil.Fields{
		"expires_in": to.Int64(deviceCode.ExpiresIn),
		"interval":   to.Int64(deviceCode.Interval),
	})

	// Example message: “To sign in, open https://aka.ms/devicelogin and enter
	// the code 0000000 to authenticate.”
	log.Infof("Microsoft Azure: %s", to.String(deviceCode.Message))

	token, err := azure.WaitForUserCompletion(&cl, deviceCode)
	if err != nil {
		return nil, fmt.Errorf("Failed to complete device auth: %v", err)
	}

	spt, err := azure.NewServicePrincipalTokenFromManualToken(oauthCfg, clientID, resource, *token)
	if err != nil {
		return nil, fmt.Errorf("Error constructing service principal token: %v", err)
	}
	return spt, nil
}
示例#23
0
文件: storage.go 项目: bac/juju
// updateVirtualMachines updates virtual machines in the given map by iterating
// through the list of instance IDs in order, and updating each corresponding
// virtual machine at most once.
func (v *azureVolumeSource) updateVirtualMachines(
	virtualMachines map[instance.Id]*maybeVirtualMachine, instanceIds []instance.Id,
) ([]error, error) {
	results := make([]error, len(instanceIds))
	vmsClient := compute.VirtualMachinesClient{v.env.compute}
	for i, instanceId := range instanceIds {
		vm, ok := virtualMachines[instanceId]
		if !ok {
			continue
		}
		if vm.err != nil {
			results[i] = vm.err
			continue
		}
		if err := v.env.callAPI(func() (autorest.Response, error) {
			return vmsClient.CreateOrUpdate(
				v.env.resourceGroup, to.String(vm.vm.Name), *vm.vm,
				nil, // abort channel
			)
		}); err != nil {
			results[i] = err
			vm.err = err
			continue
		}
		// successfully updated, don't update again
		delete(virtualMachines, instanceId)
	}
	return results, nil
}
示例#24
0
文件: storage.go 项目: bac/juju
// blobContainer returns the URL to the named blob container.
func blobContainerURL(storageAccount *armstorage.Account, container string) string {
	return fmt.Sprintf(
		"%s%s/",
		to.String(storageAccount.Properties.PrimaryEndpoints.Blob),
		container,
	)
}
示例#25
0
文件: environ.go 项目: bac/juju
func (env *azureEnviron) deleteControllerManagedResourceGroups(controllerUUID string) error {
	filter := fmt.Sprintf(
		"tagname eq '%s' and tagvalue eq '%s'",
		tags.JujuController, controllerUUID,
	)
	client := resources.GroupsClient{env.resources}
	var result resources.ResourceGroupListResult
	if err := env.callAPI(func() (autorest.Response, error) {
		var err error
		result, err = client.List(filter, nil)
		return result.Response, err
	}); err != nil {
		return errors.Annotate(err, "listing resource groups")
	}
	if result.Value == nil {
		return nil
	}

	// Deleting groups can take a long time, so make sure they are
	// deleted in parallel.
	var wg sync.WaitGroup
	errs := make([]error, len(*result.Value))
	for i, group := range *result.Value {
		groupName := to.String(group.Name)
		logger.Debugf("  - deleting resource group %q", groupName)
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			if err := env.deleteResourceGroup(groupName); err != nil {
				errs[i] = errors.Annotatef(
					err, "deleting resource group %q", groupName,
				)
			}
		}(i)
	}
	wg.Wait()

	// If there is just one error, return it. If there are multiple,
	// then combine their messages.
	var nonNilErrs []error
	for _, err := range errs {
		if err != nil {
			nonNilErrs = append(nonNilErrs, err)
		}
	}
	switch len(nonNilErrs) {
	case 0:
		return nil
	case 1:
		return nonNilErrs[0]
	}
	combined := make([]string, len(nonNilErrs))
	for i, err := range nonNilErrs {
		combined[i] = err.Error()
	}
	return errors.New(strings.Join(combined, "; "))
}
示例#26
0
// powerStateFromInstanceView reads the instance view response and extracts the
// power state status (if exists) from there. If no status is found or an
// unknown status has occured, returns Unknown.
func powerStateFromInstanceView(instanceView *compute.VirtualMachineInstanceView) VMPowerState {
	if instanceView == nil {
		log.Debug("Retrieved nil instance view.")
		return Unknown
	} else if instanceView.Statuses == nil || len(*instanceView.Statuses) == 0 {
		log.Debug("Retrieved nil or empty instanceView.statuses.")
		return Unknown
	}
	statuses := *instanceView.Statuses

	// Filter statuses whose "code" starts with "PowerState/"
	var s *compute.InstanceViewStatus
	for _, v := range statuses {
		log.Debugf("Matching pattern for code=%q", to.String(v.Code))
		if strings.HasPrefix(to.String(v.Code), powerStateCodePrefix) {
			log.Debug("Power state found.")
			s = &v
			break
		}
	}
	if s == nil {
		log.Debug("No PowerState found in the instance view statuses.")
		return Unknown
	}
	code := strings.TrimPrefix(to.String(s.Code), powerStateCodePrefix)
	switch code {
	case "stopped":
		return Stopped
	case "stopping":
		return Stopping
	case "starting":
		return Starting
	case "running":
		return Running
	case "deallocated":
		return Deallocated
	case "deallocating":
		return Deallocating
	default:
		log.Warn("Encountered unknown PowerState for virtual machine: %q", code)
		return Unknown
	}
}
示例#27
0
文件: environ.go 项目: bac/juju
func isControllerDeployment(deployment resources.DeploymentExtended) bool {
	for _, d := range *deployment.Properties.Dependencies {
		if d.DependsOn == nil {
			continue
		}
		if to.String(d.ResourceType) != "Microsoft.Compute/virtualMachines" {
			continue
		}
		for _, on := range *d.DependsOn {
			if to.String(on.ResourceType) != "Microsoft.Compute/availabilitySets" {
				continue
			}
			if to.String(on.ResourceName) == controllerAvailabilitySet {
				return true
			}
		}
	}
	return false
}
示例#28
0
func (a AzureClient) CreateAvailabilitySetIfNotExists(ctx *DeploymentContext, resourceGroup, name, location string) error {
	f := logutil.Fields{"name": name}
	log.Info("Configuring availability set.", f)
	as, err := a.availabilitySetsClient().CreateOrUpdate(resourceGroup, name,
		compute.AvailabilitySet{
			Location: to.StringPtr(location),
		})
	ctx.AvailabilitySetID = to.String(as.ID)
	return err
}
示例#29
0
// GetPublicIPAddress attempts to get public IP address from the Public IP
// resource. If IP address is not allocated yet, returns empty string. If
// useFqdn is set to true, the a FQDN hostname will be returned.
func (a AzureClient) GetPublicIPAddress(resourceGroup, name string, useFqdn bool) (string, error) {
	f := logutil.Fields{"name": name}
	log.Debug("Querying public IP address.", f)
	ip, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
	if err != nil {
		return "", err
	}
	if ip.Properties == nil {
		log.Debug("publicIP.Properties is nil. Could not determine IP address", f)
		return "", nil
	}

	if useFqdn { // return FQDN value on public IP
		log.Debug("Will attempt to return FQDN.", f)
		if ip.Properties.DNSSettings == nil || ip.Properties.DNSSettings.Fqdn == nil {
			return "", errors.New("FQDN not found on public IP address")
		}
		return to.String(ip.Properties.DNSSettings.Fqdn), nil
	}
	return to.String(ip.Properties.IPAddress), nil
}
示例#30
0
// GetPublicIPAddress attempts to get public IP address from the Public IP
// resource. If IP address is not allocated yet, returns empty string.
func (a AzureClient) GetPublicIPAddress(resourceGroup, name string) (string, error) {
	f := logutil.Fields{"name": name}
	log.Debug("Querying public IP address.", f)
	ip, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
	if err != nil {
		return "", err
	}
	if ip.Properties == nil {
		log.Debug("publicIP.Properties is nil. Could not determine IP address", f)
		return "", nil
	}
	return to.String(ip.Properties.IPAddress), nil
}