github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/azure/instance.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	stdcontext "context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
    12  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
    13  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
    14  	"github.com/juju/collections/set"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/names/v5"
    17  
    18  	"github.com/juju/juju/core/instance"
    19  	corenetwork "github.com/juju/juju/core/network"
    20  	"github.com/juju/juju/core/network/firewall"
    21  	"github.com/juju/juju/core/status"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/provider/azure/internal/errorutils"
    24  )
    25  
    26  type azureInstance struct {
    27  	vmName            string
    28  	provisioningState armresources.ProvisioningState
    29  	provisioningError string
    30  	env               *azureEnviron
    31  	networkInterfaces []*armnetwork.Interface
    32  	publicIPAddresses []*armnetwork.PublicIPAddress
    33  }
    34  
    35  // Id is specified in the Instance interface.
    36  func (inst *azureInstance) Id() instance.Id {
    37  	// Note: we use Name and not Id, since all VM operations are in
    38  	// terms of the VM name (qualified by resource group). The ID is
    39  	// an internal detail.
    40  	return instance.Id(inst.vmName)
    41  }
    42  
    43  // Status is specified in the Instance interface.
    44  func (inst *azureInstance) Status(ctx context.ProviderCallContext) instance.Status {
    45  	var instanceStatus status.Status
    46  	message := string(inst.provisioningState)
    47  	switch inst.provisioningState {
    48  	case armresources.ProvisioningStateSucceeded:
    49  		// TODO(axw) once a VM has been started, we should
    50  		// start using its power state to show if it's
    51  		// really running or not. This is just a nice to
    52  		// have, since we should not expect a VM to ever
    53  		// be stopped.
    54  		instanceStatus = status.Running
    55  		message = ""
    56  	case armresources.ProvisioningStateDeleting, armresources.ProvisioningStateFailed:
    57  		instanceStatus = status.ProvisioningError
    58  		message = inst.provisioningError
    59  	case armresources.ProvisioningStateCreating:
    60  		message = ""
    61  		fallthrough
    62  	default:
    63  		instanceStatus = status.Provisioning
    64  	}
    65  	return instance.Status{
    66  		Status:  instanceStatus,
    67  		Message: message,
    68  	}
    69  }
    70  
    71  // setInstanceAddresses queries Azure for the NICs and public IPs associated
    72  // with the given set of instances. This assumes that the instances'
    73  // VirtualMachines are up-to-date, and that there are no concurrent accesses
    74  // to the instances.
    75  func (env *azureEnviron) setInstanceAddresses(
    76  	ctx context.ProviderCallContext,
    77  	resourceGroup string,
    78  	instances []*azureInstance,
    79  ) (err error) {
    80  	instanceNics, err := env.instanceNetworkInterfaces(ctx, resourceGroup)
    81  	if err != nil {
    82  		return errors.Annotate(err, "listing network interfaces")
    83  	}
    84  	instancePips, err := env.instancePublicIPAddresses(ctx, resourceGroup)
    85  	if err != nil {
    86  		return errors.Annotate(err, "listing public IP addresses")
    87  	}
    88  	for _, inst := range instances {
    89  		inst.networkInterfaces = instanceNics[inst.Id()]
    90  		inst.publicIPAddresses = instancePips[inst.Id()]
    91  	}
    92  	return nil
    93  }
    94  
    95  // instanceNetworkInterfaces lists all network interfaces in the resource
    96  // group, and returns a mapping from instance ID to the network interfaces
    97  // associated with that instance.
    98  func (env *azureEnviron) instanceNetworkInterfaces(
    99  	ctx context.ProviderCallContext,
   100  	resourceGroup string,
   101  ) (map[instance.Id][]*armnetwork.Interface, error) {
   102  	nicClient, err := env.interfacesClient()
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  	pager := nicClient.NewListPager(resourceGroup, nil)
   107  	instanceNics := make(map[instance.Id][]*armnetwork.Interface)
   108  	for pager.More() {
   109  		next, err := pager.NextPage(ctx)
   110  		if err != nil {
   111  			return nil, errorutils.HandleCredentialError(errors.Annotate(err, "listing network interfaces"), ctx)
   112  		}
   113  		for _, nic := range next.Value {
   114  			instanceId := instance.Id(toValue(nic.Tags[jujuMachineNameTag]))
   115  			instanceNics[instanceId] = append(instanceNics[instanceId], nic)
   116  		}
   117  	}
   118  	return instanceNics, nil
   119  }
   120  
   121  // interfacePublicIPAddresses lists all public IP addresses in the resource
   122  // group, and returns a mapping from instance ID to the public IP addresses
   123  // associated with that instance.
   124  func (env *azureEnviron) instancePublicIPAddresses(
   125  	ctx context.ProviderCallContext,
   126  	resourceGroup string,
   127  ) (map[instance.Id][]*armnetwork.PublicIPAddress, error) {
   128  	pipClient, err := env.publicAddressesClient()
   129  	if err != nil {
   130  		return nil, errors.Trace(err)
   131  	}
   132  	pager := pipClient.NewListPager(resourceGroup, nil)
   133  	instancePips := make(map[instance.Id][]*armnetwork.PublicIPAddress)
   134  	for pager.More() {
   135  		next, err := pager.NextPage(ctx)
   136  		if err != nil {
   137  			return nil, errorutils.HandleCredentialError(errors.Annotate(err, "listing public IP addresses"), ctx)
   138  		}
   139  		for _, pip := range next.Value {
   140  			instanceId := instance.Id(toValue(pip.Tags[jujuMachineNameTag]))
   141  			instancePips[instanceId] = append(instancePips[instanceId], pip)
   142  		}
   143  	}
   144  	return instancePips, nil
   145  }
   146  
   147  // Addresses is specified in the Instance interface.
   148  func (inst *azureInstance) Addresses(ctx context.ProviderCallContext) (corenetwork.ProviderAddresses, error) {
   149  	addresses := make([]corenetwork.ProviderAddress, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses))
   150  	for _, nic := range inst.networkInterfaces {
   151  		if nic.Properties == nil {
   152  			continue
   153  		}
   154  		for _, ipConfiguration := range nic.Properties.IPConfigurations {
   155  			if ipConfiguration.Properties == nil || ipConfiguration.Properties.PrivateIPAddress == nil {
   156  				continue
   157  			}
   158  			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   159  			addresses = append(addresses, corenetwork.NewMachineAddress(
   160  				toValue(privateIpAddress),
   161  				corenetwork.WithScope(corenetwork.ScopeCloudLocal),
   162  			).AsProviderAddress())
   163  		}
   164  	}
   165  	for _, pip := range inst.publicIPAddresses {
   166  		if pip.Properties == nil || pip.Properties.IPAddress == nil {
   167  			continue
   168  		}
   169  		addresses = append(addresses, corenetwork.NewMachineAddress(
   170  			toValue(pip.Properties.IPAddress),
   171  			corenetwork.WithScope(corenetwork.ScopePublic),
   172  		).AsProviderAddress())
   173  	}
   174  	return addresses, nil
   175  }
   176  
   177  type securityGroupInfo struct {
   178  	resourceGroup  string
   179  	securityGroup  *armnetwork.SecurityGroup
   180  	primaryAddress corenetwork.SpaceAddress
   181  }
   182  
   183  // primarySecurityGroupInfo returns info for the NIC's primary corenetwork.Address
   184  // for the internal virtual network, and any security group on the subnet.
   185  // The address is used to identify the machine in network security rules.
   186  func primarySecurityGroupInfo(ctx stdcontext.Context, env *azureEnviron, nic *armnetwork.Interface) (*securityGroupInfo, error) {
   187  	if nic == nil || nic.Properties == nil {
   188  		return nil, errors.NotFoundf("internal network address or security group")
   189  	}
   190  	subnets, err := env.subnetsClient()
   191  	if err != nil {
   192  		return nil, errors.Trace(err)
   193  	}
   194  	for _, ipConfiguration := range nic.Properties.IPConfigurations {
   195  		if ipConfiguration.Properties == nil {
   196  			continue
   197  		}
   198  		if !toValue(ipConfiguration.Properties.Primary) {
   199  			continue
   200  		}
   201  		privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   202  		if privateIpAddress == nil {
   203  			continue
   204  		}
   205  		securityGroup := nic.Properties.NetworkSecurityGroup
   206  		if securityGroup == nil && ipConfiguration.Properties.Subnet != nil {
   207  			idParts := strings.Split(toValue(ipConfiguration.Properties.Subnet.ID), "/")
   208  			lenParts := len(idParts)
   209  			subnet, err := subnets.Get(ctx, idParts[lenParts-7], idParts[lenParts-3], idParts[lenParts-1], &armnetwork.SubnetsClientGetOptions{
   210  				Expand: to.Ptr("networkSecurityGroup"),
   211  			})
   212  			if err != nil {
   213  				return nil, errors.Trace(err)
   214  			}
   215  			if subnet.Properties != nil {
   216  				securityGroup = subnet.Properties.NetworkSecurityGroup
   217  			}
   218  		}
   219  		if securityGroup == nil {
   220  			continue
   221  		}
   222  
   223  		idParts := strings.Split(toValue(securityGroup.ID), "/")
   224  		resourceGroup := idParts[len(idParts)-5]
   225  		return &securityGroupInfo{
   226  			resourceGroup: resourceGroup,
   227  			securityGroup: securityGroup,
   228  			primaryAddress: corenetwork.NewSpaceAddress(
   229  				toValue(privateIpAddress),
   230  				corenetwork.WithScope(corenetwork.ScopeCloudLocal),
   231  			),
   232  		}, nil
   233  	}
   234  	return nil, errors.NotFoundf("internal network address or security group")
   235  }
   236  
   237  // getSecurityGroupInfo gets the security group information for
   238  // each NIC on the instance.
   239  func (inst *azureInstance) getSecurityGroupInfo(ctx stdcontext.Context) ([]securityGroupInfo, error) {
   240  	return getSecurityGroupInfoForInterfaces(ctx, inst.env, inst.networkInterfaces)
   241  }
   242  
   243  func getSecurityGroupInfoForInterfaces(ctx stdcontext.Context, env *azureEnviron, networkInterfaces []*armnetwork.Interface) ([]securityGroupInfo, error) {
   244  	groupsByName := make(map[string]securityGroupInfo)
   245  	for _, nic := range networkInterfaces {
   246  		info, err := primarySecurityGroupInfo(ctx, env, nic)
   247  		if errors.IsNotFound(err) {
   248  			continue
   249  		}
   250  		if err != nil {
   251  			return nil, errors.Trace(err)
   252  		}
   253  		name := toValue(info.securityGroup.Name)
   254  		if _, ok := groupsByName[name]; ok {
   255  			continue
   256  		}
   257  		groupsByName[name] = *info
   258  	}
   259  	var result []securityGroupInfo
   260  	for _, sg := range groupsByName {
   261  		result = append(result, sg)
   262  	}
   263  	return result, nil
   264  }
   265  
   266  // OpenPorts is specified in the Instance interface.
   267  func (inst *azureInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error {
   268  	securityGroupInfos, err := inst.getSecurityGroupInfo(ctx)
   269  	if err != nil {
   270  		return errors.Trace(err)
   271  	}
   272  	for _, info := range securityGroupInfos {
   273  		if err := inst.openPortsOnGroup(ctx, machineId, info, rules); err != nil {
   274  			return errors.Annotatef(err,
   275  				"opening ports on security group %q on machine %q", toValue(info.securityGroup.Name), machineId)
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  func (inst *azureInstance) openPortsOnGroup(
   282  	ctx context.ProviderCallContext,
   283  	machineId string, nsgInfo securityGroupInfo, rules firewall.IngressRules,
   284  ) error {
   285  	nsg := nsgInfo.securityGroup
   286  	if nsg.Properties == nil {
   287  		nsg.Properties = &armnetwork.SecurityGroupPropertiesFormat{}
   288  	}
   289  
   290  	// Create rules one at a time; this is necessary to avoid trampling
   291  	// on changes made by the provisioner. We still record rules in the
   292  	// NSG in memory, so we can easily tell which priorities are available.
   293  	vmName := resourceName(names.NewMachineTag(machineId))
   294  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   295  
   296  	securityRules, err := inst.env.securityRulesClient()
   297  	if err != nil {
   298  		return errors.Trace(err)
   299  	}
   300  	singleSourceIngressRules := explodeIngressRules(rules)
   301  	for _, rule := range singleSourceIngressRules {
   302  		ruleName := securityRuleName(prefix, rule)
   303  
   304  		// Check if the rule already exists; OpenPorts must be idempotent.
   305  		var found bool
   306  		for _, rule := range nsg.Properties.SecurityRules {
   307  			if toValue(rule.Name) == ruleName {
   308  				found = true
   309  				break
   310  			}
   311  		}
   312  		if found {
   313  			logger.Debugf("security rule %q already exists", ruleName)
   314  			continue
   315  		}
   316  		logger.Debugf("creating security rule %q", ruleName)
   317  
   318  		priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax)
   319  		if err != nil {
   320  			return errors.Annotatef(err, "getting security rule priority for %q", rule)
   321  		}
   322  
   323  		var protocol armnetwork.SecurityRuleProtocol
   324  		switch rule.PortRange.Protocol {
   325  		case "tcp":
   326  			protocol = armnetwork.SecurityRuleProtocolTCP
   327  		case "udp":
   328  			protocol = armnetwork.SecurityRuleProtocolUDP
   329  		default:
   330  			return errors.Errorf("invalid protocol %q", rule.PortRange.Protocol)
   331  		}
   332  
   333  		var portRange string
   334  		if rule.PortRange.FromPort != rule.PortRange.ToPort {
   335  			portRange = fmt.Sprintf("%d-%d", rule.PortRange.FromPort, rule.PortRange.ToPort)
   336  		} else {
   337  			portRange = fmt.Sprint(rule.PortRange.FromPort)
   338  		}
   339  
   340  		// rule has a single source CIDR
   341  		from := rule.SourceCIDRs.SortedValues()[0]
   342  		securityRule := armnetwork.SecurityRule{
   343  			Properties: &armnetwork.SecurityRulePropertiesFormat{
   344  				Description:              to.Ptr(rule.String()),
   345  				Protocol:                 to.Ptr(protocol),
   346  				SourcePortRange:          to.Ptr("*"),
   347  				DestinationPortRange:     to.Ptr(portRange),
   348  				SourceAddressPrefix:      to.Ptr(from),
   349  				DestinationAddressPrefix: to.Ptr(nsgInfo.primaryAddress.Value),
   350  				Access:                   to.Ptr(armnetwork.SecurityRuleAccessAllow),
   351  				Priority:                 to.Ptr(priority),
   352  				Direction:                to.Ptr(armnetwork.SecurityRuleDirectionInbound),
   353  			},
   354  		}
   355  		poller, err := securityRules.BeginCreateOrUpdate(
   356  			ctx,
   357  			nsgInfo.resourceGroup, toValue(nsg.Name), ruleName, securityRule,
   358  			nil,
   359  		)
   360  		if err == nil {
   361  			_, err = poller.PollUntilDone(ctx, nil)
   362  		}
   363  		if err != nil {
   364  			return errorutils.HandleCredentialError(errors.Annotatef(err, "creating security rule for %q", ruleName), ctx)
   365  		}
   366  		nsg.Properties.SecurityRules = append(nsg.Properties.SecurityRules, to.Ptr(securityRule))
   367  	}
   368  	return nil
   369  }
   370  
   371  // ClosePorts is specified in the Instance interface.
   372  func (inst *azureInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error {
   373  	securityGroupInfos, err := inst.getSecurityGroupInfo(ctx)
   374  	if err != nil {
   375  		return errors.Trace(err)
   376  	}
   377  	for _, info := range securityGroupInfos {
   378  		if err := inst.closePortsOnGroup(ctx, machineId, info, rules); err != nil {
   379  			return errors.Annotatef(err,
   380  				"closing ports on security group %q on machine %q", toValue(info.securityGroup.Name), machineId)
   381  		}
   382  	}
   383  	return nil
   384  }
   385  
   386  func (inst *azureInstance) closePortsOnGroup(
   387  	ctx context.ProviderCallContext,
   388  	machineId string, nsgInfo securityGroupInfo, rules firewall.IngressRules,
   389  ) error {
   390  	// Delete rules one at a time; this is necessary to avoid trampling
   391  	// on changes made by the provisioner.
   392  	vmName := resourceName(names.NewMachineTag(machineId))
   393  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   394  
   395  	securityRules, err := inst.env.securityRulesClient()
   396  	if err != nil {
   397  		return errors.Trace(err)
   398  	}
   399  	singleSourceIngressRules := explodeIngressRules(rules)
   400  	for _, rule := range singleSourceIngressRules {
   401  		ruleName := securityRuleName(prefix, rule)
   402  		logger.Debugf("deleting security rule %q", ruleName)
   403  		poller, err := securityRules.BeginDelete(
   404  			ctx,
   405  			nsgInfo.resourceGroup, toValue(nsgInfo.securityGroup.Name), ruleName,
   406  			nil,
   407  		)
   408  		if err == nil {
   409  			_, err = poller.PollUntilDone(ctx, nil)
   410  		}
   411  		if err != nil && !errorutils.IsNotFoundError(err) {
   412  			return errorutils.HandleCredentialError(errors.Annotatef(err, "deleting security rule %q", ruleName), ctx)
   413  		}
   414  	}
   415  	return nil
   416  }
   417  
   418  // IngressRules is specified in the Instance interface.
   419  func (inst *azureInstance) IngressRules(ctx context.ProviderCallContext, machineId string) (firewall.IngressRules, error) {
   420  	// The rules to use will be those on the primary network interface.
   421  	var info *securityGroupInfo
   422  	for _, nic := range inst.networkInterfaces {
   423  		if nic.Properties == nil || !toValue(nic.Properties.Primary) {
   424  			continue
   425  		}
   426  		var err error
   427  		info, err = primarySecurityGroupInfo(ctx, inst.env, nic)
   428  		if errors.IsNotFound(err) {
   429  			continue
   430  		}
   431  		if err != nil {
   432  			return nil, errors.Trace(err)
   433  		}
   434  		break
   435  	}
   436  	if info == nil {
   437  		return nil, nil
   438  	}
   439  	rules, err := inst.ingressRulesForGroup(ctx, machineId, info)
   440  	if err != nil {
   441  		return rules, errors.Trace(err)
   442  	}
   443  	rules.Sort()
   444  	return rules, nil
   445  }
   446  
   447  func (inst *azureInstance) ingressRulesForGroup(ctx context.ProviderCallContext, machineId string, nsgInfo *securityGroupInfo) (rules firewall.IngressRules, err error) {
   448  	securityGroups, err := inst.env.securityGroupsClient()
   449  	if err != nil {
   450  		return nil, errors.Trace(err)
   451  	}
   452  	nsg, err := securityGroups.Get(ctx, nsgInfo.resourceGroup, toValue(nsgInfo.securityGroup.Name), nil)
   453  	if err != nil {
   454  		return nil, errorutils.HandleCredentialError(errors.Annotate(err, "querying network security group"), ctx)
   455  	}
   456  	if nsg.Properties == nil || len(nsg.Properties.SecurityRules) == 0 {
   457  		return nil, nil
   458  	}
   459  
   460  	vmName := resourceName(names.NewMachineTag(machineId))
   461  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   462  
   463  	// Keep track of all the SourceAddressPrefixes for each port range.
   464  	portSourceCIDRs := make(map[corenetwork.PortRange]*[]string)
   465  	for _, rule := range nsg.Properties.SecurityRules {
   466  		if rule.Properties == nil {
   467  			continue
   468  		}
   469  		if toValue(rule.Properties.Direction) != armnetwork.SecurityRuleDirectionInbound {
   470  			continue
   471  		}
   472  		if toValue(rule.Properties.Access) != armnetwork.SecurityRuleAccessAllow {
   473  			continue
   474  		}
   475  		if toValue(rule.Properties.Priority) <= securityRuleInternalMax {
   476  			continue
   477  		}
   478  		if !strings.HasPrefix(toValue(rule.Name), prefix) {
   479  			continue
   480  		}
   481  
   482  		var portRange corenetwork.PortRange
   483  		if toValue(rule.Properties.DestinationPortRange) == "*" {
   484  			portRange.FromPort = 1
   485  			portRange.ToPort = 65535
   486  		} else {
   487  			portRange, err = corenetwork.ParsePortRange(
   488  				toValue(rule.Properties.DestinationPortRange),
   489  			)
   490  			if err != nil {
   491  				return nil, errors.Annotatef(
   492  					err, "parsing port range for security rule %q",
   493  					toValue(rule.Name),
   494  				)
   495  			}
   496  		}
   497  
   498  		var protocols []string
   499  		switch toValue(rule.Properties.Protocol) {
   500  		case armnetwork.SecurityRuleProtocolTCP:
   501  			protocols = []string{"tcp"}
   502  		case armnetwork.SecurityRuleProtocolUDP:
   503  			protocols = []string{"udp"}
   504  		default:
   505  			protocols = []string{"tcp", "udp"}
   506  		}
   507  
   508  		// Record the SourceAddressPrefix for the port range.
   509  		remotePrefix := toValue(rule.Properties.SourceAddressPrefix)
   510  		if remotePrefix == "" || remotePrefix == "*" {
   511  			remotePrefix = "0.0.0.0/0"
   512  		}
   513  		for _, protocol := range protocols {
   514  			portRange.Protocol = protocol
   515  			sourceCIDRs, ok := portSourceCIDRs[portRange]
   516  			if !ok {
   517  				sourceCIDRs = &[]string{}
   518  				portSourceCIDRs[portRange] = sourceCIDRs
   519  			}
   520  			*sourceCIDRs = append(*sourceCIDRs, remotePrefix)
   521  		}
   522  	}
   523  	// Combine all the port ranges and remote prefixes.
   524  	for portRange, sourceCIDRs := range portSourceCIDRs {
   525  		rules = append(rules, firewall.NewIngressRule(portRange, *sourceCIDRs...))
   526  	}
   527  	if err := rules.Validate(); err != nil {
   528  		return nil, errors.Trace(err)
   529  	}
   530  	return rules, nil
   531  }
   532  
   533  // deleteInstanceNetworkSecurityRules deletes network security rules in the
   534  // internal network security group that correspond to the specified machine.
   535  //
   536  // This is expected to delete *all* security rules related to the instance,
   537  // i.e. both the ones opened by OpenPorts above, and the ones opened for API
   538  // access.
   539  func deleteInstanceNetworkSecurityRules(
   540  	ctx context.ProviderCallContext,
   541  	env *azureEnviron, id instance.Id,
   542  	networkInterfaces []*armnetwork.Interface,
   543  ) error {
   544  	securityGroupInfos, err := getSecurityGroupInfoForInterfaces(ctx, env, networkInterfaces)
   545  	if err != nil {
   546  		return errors.Trace(err)
   547  	}
   548  	securityRules, err := env.securityRulesClient()
   549  	if err != nil {
   550  		return errors.Trace(err)
   551  	}
   552  
   553  	for _, info := range securityGroupInfos {
   554  		if err := deleteSecurityRules(
   555  			ctx, id, info,
   556  			securityRules,
   557  		); err != nil {
   558  			return errors.Trace(err)
   559  		}
   560  	}
   561  	return nil
   562  }
   563  
   564  func deleteSecurityRules(
   565  	ctx context.ProviderCallContext,
   566  	id instance.Id,
   567  	nsgInfo securityGroupInfo,
   568  	securityRuleClient *armnetwork.SecurityRulesClient,
   569  ) error {
   570  	nsg := nsgInfo.securityGroup
   571  	if nsg.Properties == nil {
   572  		return nil
   573  	}
   574  	prefix := instanceNetworkSecurityRulePrefix(id)
   575  	for _, rule := range nsg.Properties.SecurityRules {
   576  		ruleName := toValue(rule.Name)
   577  		if !strings.HasPrefix(ruleName, prefix) {
   578  			continue
   579  		}
   580  		poller, err := securityRuleClient.BeginDelete(
   581  			ctx,
   582  			nsgInfo.resourceGroup,
   583  			*nsg.Name,
   584  			ruleName,
   585  			nil,
   586  		)
   587  		if err != nil {
   588  			return errors.Annotatef(err, "deleting security rule %q", ruleName)
   589  		}
   590  		_, err = poller.PollUntilDone(ctx, nil)
   591  		if err != nil && !errorutils.IsNotFoundError(err) {
   592  			return errorutils.HandleCredentialError(errors.Annotatef(err, "deleting security rule %q", ruleName), ctx)
   593  		}
   594  	}
   595  	return nil
   596  }
   597  
   598  // instanceNetworkSecurityRulePrefix returns the unique prefix for network
   599  // security rule names that relate to the instance with the given ID.
   600  func instanceNetworkSecurityRulePrefix(id instance.Id) string {
   601  	return string(id) + "-"
   602  }
   603  
   604  // securityRuleName returns the security rule name for the given ingress rule,
   605  // and prefix returned by instanceNetworkSecurityRulePrefix.
   606  func securityRuleName(prefix string, rule firewall.IngressRule) string {
   607  	ruleName := fmt.Sprintf("%s%s-%d", prefix, rule.PortRange.Protocol, rule.PortRange.FromPort)
   608  	if rule.PortRange.FromPort != rule.PortRange.ToPort {
   609  		ruleName += fmt.Sprintf("-%d", rule.PortRange.ToPort)
   610  	}
   611  	// The rule parameter must have a single source cidr.
   612  	// Ensure the rule name can be a valid URL path component.
   613  	var cidr string
   614  	if rule.SourceCIDRs.IsEmpty() {
   615  		cidr = firewall.AllNetworksIPV4CIDR
   616  	} else {
   617  		cidr = rule.SourceCIDRs.SortedValues()[0]
   618  	}
   619  	if cidr != firewall.AllNetworksIPV4CIDR && cidr != "*" {
   620  		cidr = strings.Replace(cidr, ".", "-", -1)
   621  		cidr = strings.Replace(cidr, "::", "-", -1)
   622  		cidr = strings.Replace(cidr, "/", "-", -1)
   623  		ruleName = fmt.Sprintf("%s-cidr-%s", ruleName, cidr)
   624  	}
   625  	return ruleName
   626  }
   627  
   628  // explodeIngressRules creates a slice of ingress rules, each rule in the
   629  // result having a single source CIDR. The results contain a copy of each
   630  // specified rule with each copy having one of the source CIDR values,
   631  func explodeIngressRules(inRules firewall.IngressRules) firewall.IngressRules {
   632  	// If any rule has an empty source CIDR slice, a default
   633  	// source value of "*" is used.
   634  	var singleSourceIngressRules firewall.IngressRules
   635  	for _, rule := range inRules {
   636  		sourceCIDRs := rule.SourceCIDRs
   637  		if len(sourceCIDRs) == 0 {
   638  			sourceCIDRs = set.NewStrings("*")
   639  		}
   640  		for _, sr := range sourceCIDRs.SortedValues() {
   641  			singleSourceIngressRules = append(singleSourceIngressRules, firewall.NewIngressRule(rule.PortRange, sr))
   642  		}
   643  	}
   644  	return singleSourceIngressRules
   645  }