func createDeployment( callAPI callAPIFunc, client resources.DeploymentsClient, resourceGroup string, deploymentName string, t armtemplates.Template, ) error { templateMap, err := t.Map() if err != nil { return errors.Trace(err) } deployment := resources.Deployment{ &resources.DeploymentProperties{ Template: &templateMap, Mode: resources.Incremental, }, } if err := callAPI(func() (autorest.Response, error) { return client.CreateOrUpdate( resourceGroup, deploymentName, deployment, nil, // abort channel ) }); err != nil { return errors.Annotatef(err, "creating deployment %q", deploymentName) } return nil }
// cancelDeployment cancels a template deployment. func (env *azureEnviron) cancelDeployment(name string) error { deploymentsClient := resources.DeploymentsClient{env.resources} logger.Debugf("- canceling deployment %q", name) var cancelResult autorest.Response if err := env.callAPI(func() (autorest.Response, error) { var err error cancelResult, err = deploymentsClient.Cancel(env.resourceGroup, name) return cancelResult, err }); err != nil { if cancelResult.Response != nil { switch cancelResult.StatusCode { case http.StatusNotFound: return errors.NewNotFound(err, fmt.Sprintf("deployment %q not found", name)) case http.StatusConflict: if err, ok := errorutils.ServiceError(err); ok { if err.Code == serviceErrorCodeDeploymentCannotBeCancelled { // Deployments can only canceled while they're running. return nil } } } } return errors.Annotatef(err, "canceling deployment %q", name) } return nil }
// 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 }
// createVirtualMachine creates a virtual machine and related resources. // // All resources created are tagged with the specified "vmTags", so if // this function fails then all resources can be deleted by tag. func (env *azureEnviron) createVirtualMachine( vmName string, vmTags, envTags map[string]string, instanceSpec *instances.InstanceSpec, instanceConfig *instancecfg.InstanceConfig, storageAccountType string, ) error { deploymentsClient := resources.DeploymentsClient{env.resources} var apiPort int if instanceConfig.Controller != nil { apiPortValue := instanceConfig.Controller.Config.APIPort() apiPort = apiPortValue } else { apiPorts := instanceConfig.APIInfo.Ports() if len(apiPorts) != 1 { return errors.Errorf("expected one API port, found %v", apiPorts) } apiPort = apiPorts[0] } resources := networkTemplateResources(env.location, envTags, apiPort) resources = append(resources, storageAccountTemplateResource( env.location, envTags, env.storageAccountName, storageAccountType, )) osProfile, seriesOS, err := newOSProfile( vmName, instanceConfig, env.provider.config.RandomWindowsAdminPassword, ) if err != nil { return errors.Annotate(err, "creating OS profile") } storageProfile, err := newStorageProfile(vmName, env.storageAccountName, instanceSpec) if err != nil { return errors.Annotate(err, "creating storage profile") } var vmDependsOn []string var availabilitySetSubResource *compute.SubResource availabilitySetName, err := availabilitySetName( vmName, vmTags, instanceConfig.Controller != nil, ) if err != nil { return errors.Annotate(err, "getting availability set name") } if availabilitySetName != "" { availabilitySetId := fmt.Sprintf( `[resourceId('Microsoft.Compute/availabilitySets','%s')]`, availabilitySetName, ) resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/availabilitySets", Name: availabilitySetName, Location: env.location, Tags: envTags, }) availabilitySetSubResource = &compute.SubResource{ ID: to.StringPtr(availabilitySetId), } vmDependsOn = append(vmDependsOn, availabilitySetId) } publicIPAddressName := vmName + "-public-ip" publicIPAddressId := fmt.Sprintf(`[resourceId('Microsoft.Network/publicIPAddresses', '%s')]`, publicIPAddressName) resources = append(resources, armtemplates.Resource{ APIVersion: network.APIVersion, Type: "Microsoft.Network/publicIPAddresses", Name: publicIPAddressName, Location: env.location, Tags: vmTags, Properties: &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Dynamic, }, }) // Controller and non-controller machines are assigned to separate // subnets. This enables us to create controller-specific NSG rules // just by targeting the controller subnet. subnetName := internalSubnetName subnetPrefix := internalSubnetPrefix if instanceConfig.Controller != nil { subnetName = controllerSubnetName subnetPrefix = controllerSubnetPrefix } subnetId := fmt.Sprintf( `[concat(resourceId('Microsoft.Network/virtualNetworks', '%s'), '/subnets/%s')]`, internalNetworkName, subnetName, ) privateIP, err := machineSubnetIP(subnetPrefix, instanceConfig.MachineId) if err != nil { return errors.Annotatef(err, "computing private IP address") } nicName := vmName + "-primary" nicId := fmt.Sprintf(`[resourceId('Microsoft.Network/networkInterfaces', '%s')]`, nicName) ipConfigurations := []network.InterfaceIPConfiguration{{ Name: to.StringPtr("primary"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ Primary: to.BoolPtr(true), PrivateIPAddress: to.StringPtr(privateIP.String()), PrivateIPAllocationMethod: network.Static, Subnet: &network.Subnet{ID: to.StringPtr(subnetId)}, PublicIPAddress: &network.PublicIPAddress{ ID: to.StringPtr(publicIPAddressId), }, }, }} resources = append(resources, armtemplates.Resource{ APIVersion: network.APIVersion, Type: "Microsoft.Network/networkInterfaces", Name: nicName, Location: env.location, Tags: vmTags, Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &ipConfigurations, }, DependsOn: []string{ publicIPAddressId, fmt.Sprintf( `[resourceId('Microsoft.Network/virtualNetworks', '%s')]`, internalNetworkName, ), }, }) nics := []compute.NetworkInterfaceReference{{ ID: to.StringPtr(nicId), Properties: &compute.NetworkInterfaceReferenceProperties{ Primary: to.BoolPtr(true), }, }} vmDependsOn = append(vmDependsOn, nicId) vmDependsOn = append(vmDependsOn, fmt.Sprintf( `[resourceId('Microsoft.Storage/storageAccounts', '%s')]`, env.storageAccountName, )) resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines", Name: vmName, Location: env.location, Tags: vmTags, Properties: &compute.VirtualMachineProperties{ HardwareProfile: &compute.HardwareProfile{ VMSize: compute.VirtualMachineSizeTypes( instanceSpec.InstanceType.Name, ), }, StorageProfile: storageProfile, OsProfile: osProfile, NetworkProfile: &compute.NetworkProfile{ &nics, }, AvailabilitySet: availabilitySetSubResource, }, DependsOn: vmDependsOn, }) // On Windows and CentOS, we must add the CustomScript VM // extension to run the CustomData script. switch seriesOS { case os.Windows, os.CentOS: properties, err := vmExtensionProperties(seriesOS) if err != nil { return errors.Annotate( err, "creating virtual machine extension", ) } resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines/extensions", Name: vmName + "/" + extensionName, Location: env.location, Tags: vmTags, Properties: properties, DependsOn: []string{"Microsoft.Compute/virtualMachines/" + vmName}, }) } logger.Debugf("- creating virtual machine deployment") template := armtemplates.Template{Resources: resources} // NOTE(axw) VMs take a long time to go to "Succeeded", so we do not // block waiting for them to be fully provisioned. This means we won't // return an error from StartInstance if the VM fails provisioning; // we will instead report the error via the instance's status. deploymentsClient.ResponseInspector = asyncCreationRespondDecorator( deploymentsClient.ResponseInspector, ) if err := createDeployment( env.callAPI, deploymentsClient, env.resourceGroup, vmName, // deployment name template, ); err != nil { return errors.Trace(err) } return nil }