github.com/mitchellh/packer@v1.3.2/builder/azure/arm/builder.go (about)

     1  package arm
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	armstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
    14  	"github.com/Azure/azure-sdk-for-go/storage"
    15  	"github.com/Azure/go-autorest/autorest/adal"
    16  	"github.com/dgrijalva/jwt-go"
    17  	packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
    18  	"github.com/hashicorp/packer/builder/azure/common/constants"
    19  	"github.com/hashicorp/packer/builder/azure/common/lin"
    20  	packerCommon "github.com/hashicorp/packer/common"
    21  	"github.com/hashicorp/packer/helper/communicator"
    22  	"github.com/hashicorp/packer/helper/multistep"
    23  	"github.com/hashicorp/packer/packer"
    24  )
    25  
    26  type Builder struct {
    27  	config    *Config
    28  	stateBag  multistep.StateBag
    29  	runner    multistep.Runner
    30  	ctxCancel context.CancelFunc
    31  }
    32  
    33  const (
    34  	DefaultSasBlobContainer = "system/Microsoft.Compute"
    35  	DefaultSecretName       = "packerKeyVaultSecret"
    36  )
    37  
    38  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    39  	c, warnings, errs := newConfig(raws...)
    40  	if errs != nil {
    41  		return warnings, errs
    42  	}
    43  
    44  	b.config = c
    45  
    46  	b.stateBag = new(multistep.BasicStateBag)
    47  	b.configureStateBag(b.stateBag)
    48  	b.setTemplateParameters(b.stateBag)
    49  	b.setImageParameters(b.stateBag)
    50  
    51  	return warnings, errs
    52  }
    53  
    54  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
    55  
    56  	ui.Say("Running builder ...")
    57  
    58  	ctx, cancel := context.WithCancel(context.Background())
    59  	b.ctxCancel = cancel
    60  	defer cancel()
    61  
    62  	if err := newConfigRetriever().FillParameters(b.config); err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	log.Print(":: Configuration")
    67  	packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) })
    68  
    69  	b.stateBag.Put("hook", hook)
    70  	b.stateBag.Put(constants.Ui, ui)
    71  
    72  	spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	ui.Message("Creating Azure Resource Manager (ARM) client ...")
    78  	azureClient, err := NewAzureClient(
    79  		b.config.SubscriptionID,
    80  		b.config.ResourceGroupName,
    81  		b.config.StorageAccount,
    82  		b.config.cloudEnvironment,
    83  		spnCloud,
    84  		spnKeyVault)
    85  
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	resolver := newResourceResolver(azureClient)
    91  	if err := resolver.Resolve(b.config); err != nil {
    92  		return nil, err
    93  	}
    94  	if b.config.ObjectID == "" {
    95  		b.config.ObjectID = getObjectIdFromToken(ui, spnCloud)
    96  	} else {
    97  		ui.Message("You have provided Object_ID which is no longer needed, azure packer builder determines this dynamically from the authentication token")
    98  	}
    99  
   100  	if b.config.ObjectID == "" && b.config.OSType != constants.Target_Linux {
   101  		return nil, fmt.Errorf("could not determine the ObjectID for the user, which is required for Windows builds")
   102  	}
   103  
   104  	if b.config.isManagedImage() {
   105  		group, err := azureClient.GroupsClient.Get(ctx, b.config.ManagedImageResourceGroupName)
   106  		if err != nil {
   107  			return nil, fmt.Errorf("Cannot locate the managed image resource group %s.", b.config.ManagedImageResourceGroupName)
   108  		}
   109  
   110  		b.config.manageImageLocation = *group.Location
   111  
   112  		// If a managed image already exists it cannot be overwritten.
   113  		_, err = azureClient.ImagesClient.Get(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, "")
   114  		if err == nil {
   115  			if b.config.PackerForce {
   116  				ui.Say(fmt.Sprintf("the managed image named %s already exists, but deleting it due to -force flag", b.config.ManagedImageName))
   117  				f, err := azureClient.ImagesClient.Delete(ctx, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
   118  				if err == nil {
   119  					err = f.WaitForCompletion(ctx, azureClient.ImagesClient.Client)
   120  				}
   121  				if err != nil {
   122  					return nil, fmt.Errorf("failed to delete the managed image named %s : %s", b.config.ManagedImageName, azureClient.LastError.Error())
   123  				}
   124  			} else {
   125  				return nil, fmt.Errorf("the managed image named %s already exists in the resource group %s, use the -force option to automatically delete it.", b.config.ManagedImageName, b.config.ManagedImageResourceGroupName)
   126  			}
   127  		}
   128  	} else {
   129  		// User is not using Managed Images to build, warning message here that this path is being deprecated
   130  		ui.Error("Warning: You are using Azure Packer Builder to create VHDs which is being deprecated, consider using Managed Images. Learn more http://aka.ms/packermanagedimage")
   131  	}
   132  
   133  	if b.config.BuildResourceGroupName != "" {
   134  		group, err := azureClient.GroupsClient.Get(ctx, b.config.BuildResourceGroupName)
   135  		if err != nil {
   136  			return nil, fmt.Errorf("Cannot locate the existing build resource resource group %s.", b.config.BuildResourceGroupName)
   137  		}
   138  
   139  		b.config.Location = *group.Location
   140  	}
   141  
   142  	if b.config.StorageAccount != "" {
   143  		account, err := b.getBlobAccount(ctx, azureClient, b.config.ResourceGroupName, b.config.StorageAccount)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		b.config.storageAccountBlobEndpoint = *account.AccountProperties.PrimaryEndpoints.Blob
   148  
   149  		if !equalLocation(*account.Location, b.config.Location) {
   150  			return nil, fmt.Errorf("The storage account is located in %s, but the build will take place in %s. The locations must be identical", *account.Location, b.config.Location)
   151  		}
   152  	}
   153  
   154  	endpointConnectType := PublicEndpoint
   155  	if b.isPublicPrivateNetworkCommunication() && b.isPrivateNetworkCommunication() {
   156  		endpointConnectType = PublicEndpointInPrivateNetwork
   157  	} else if b.isPrivateNetworkCommunication() {
   158  		endpointConnectType = PrivateEndpoint
   159  	}
   160  
   161  	b.setRuntimeParameters(b.stateBag)
   162  	b.setTemplateParameters(b.stateBag)
   163  	b.setImageParameters(b.stateBag)
   164  	var steps []multistep.Step
   165  
   166  	deploymentName := b.stateBag.Get(constants.ArmDeploymentName).(string)
   167  
   168  	if b.config.OSType == constants.Target_Linux {
   169  		steps = []multistep.Step{
   170  			NewStepCreateResourceGroup(azureClient, ui),
   171  			NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
   172  			NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
   173  			NewStepGetIPAddress(azureClient, ui, endpointConnectType),
   174  			&communicator.StepConnectSSH{
   175  				Config:    &b.config.Comm,
   176  				Host:      lin.SSHHost,
   177  				SSHConfig: b.config.Comm.SSHConfigFunc(),
   178  			},
   179  			&packerCommon.StepProvision{},
   180  			&packerCommon.StepCleanupTempKeys{
   181  				Comm: &b.config.Comm,
   182  			},
   183  			NewStepGetOSDisk(azureClient, ui),
   184  			NewStepGetAdditionalDisks(azureClient, ui),
   185  			NewStepPowerOffCompute(azureClient, ui),
   186  			NewStepCaptureImage(azureClient, ui),
   187  			NewStepDeleteResourceGroup(azureClient, ui),
   188  			NewStepDeleteOSDisk(azureClient, ui),
   189  			NewStepDeleteAdditionalDisks(azureClient, ui),
   190  		}
   191  	} else if b.config.OSType == constants.Target_Windows {
   192  		keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string)
   193  		steps = []multistep.Step{
   194  			NewStepCreateResourceGroup(azureClient, ui),
   195  			NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
   196  			NewStepDeployTemplate(azureClient, ui, b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
   197  			NewStepGetCertificate(azureClient, ui),
   198  			NewStepSetCertificate(b.config, ui),
   199  			NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
   200  			NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
   201  			NewStepGetIPAddress(azureClient, ui, endpointConnectType),
   202  			&StepSaveWinRMPassword{
   203  				Password:  b.config.tmpAdminPassword,
   204  				BuildName: b.config.PackerBuildName,
   205  			},
   206  			&communicator.StepConnectWinRM{
   207  				Config: &b.config.Comm,
   208  				Host: func(stateBag multistep.StateBag) (string, error) {
   209  					return stateBag.Get(constants.SSHHost).(string), nil
   210  				},
   211  				WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
   212  					return &communicator.WinRMConfig{
   213  						Username: b.config.UserName,
   214  						Password: b.config.tmpAdminPassword,
   215  					}, nil
   216  				},
   217  			},
   218  			&packerCommon.StepProvision{},
   219  			NewStepGetOSDisk(azureClient, ui),
   220  			NewStepGetAdditionalDisks(azureClient, ui),
   221  			NewStepPowerOffCompute(azureClient, ui),
   222  			NewStepCaptureImage(azureClient, ui),
   223  			NewStepDeleteResourceGroup(azureClient, ui),
   224  			NewStepDeleteOSDisk(azureClient, ui),
   225  			NewStepDeleteAdditionalDisks(azureClient, ui),
   226  		}
   227  	} else {
   228  		return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
   229  	}
   230  
   231  	if b.config.PackerDebug {
   232  		ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName))
   233  		ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
   234  
   235  		if len(b.config.Comm.SSHPrivateKey) != 0 {
   236  			debugKeyPath := fmt.Sprintf("%s-%s.pem", b.config.PackerBuildName, b.config.tmpComputeName)
   237  			ui.Message(fmt.Sprintf("temp ssh key: %s", debugKeyPath))
   238  
   239  			b.writeSSHPrivateKey(ui, debugKeyPath)
   240  		}
   241  	}
   242  
   243  	b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
   244  	b.runner.Run(b.stateBag)
   245  
   246  	// Report any errors.
   247  	if rawErr, ok := b.stateBag.GetOk(constants.Error); ok {
   248  		return nil, rawErr.(error)
   249  	}
   250  
   251  	// If we were interrupted or cancelled, then just exit.
   252  	if _, ok := b.stateBag.GetOk(multistep.StateCancelled); ok {
   253  		return nil, errors.New("Build was cancelled.")
   254  	}
   255  
   256  	if _, ok := b.stateBag.GetOk(multistep.StateHalted); ok {
   257  		return nil, errors.New("Build was halted.")
   258  	}
   259  
   260  	if b.config.isManagedImage() {
   261  		managedImageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/images/%s", b.config.SubscriptionID, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName)
   262  		return NewManagedImageArtifact(b.config.OSType, b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation, managedImageID)
   263  	} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
   264  		return NewArtifact(
   265  			template.(*CaptureTemplate),
   266  			func(name string) string {
   267  				blob := azureClient.BlobStorageClient.GetContainerReference(DefaultSasBlobContainer).GetBlobReference(name)
   268  				options := storage.BlobSASOptions{}
   269  				options.BlobServiceSASPermissions.Read = true
   270  				options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month
   271  				sasUrl, _ := blob.GetSASURI(options)
   272  				return sasUrl
   273  			},
   274  			b.config.OSType)
   275  	}
   276  
   277  	return &Artifact{}, nil
   278  }
   279  
   280  func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) {
   281  	f, err := os.Create(debugKeyPath)
   282  	if err != nil {
   283  		ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
   284  	}
   285  	defer f.Close()
   286  
   287  	// Write the key out
   288  	if _, err := f.Write(b.config.Comm.SSHPrivateKey); err != nil {
   289  		ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
   290  		return
   291  	}
   292  
   293  	// Chmod it so that it is SSH ready
   294  	if runtime.GOOS != "windows" {
   295  		if err := f.Chmod(0600); err != nil {
   296  			ui.Say(fmt.Sprintf("Error setting permissions of debug key: %s", err))
   297  		}
   298  	}
   299  }
   300  
   301  func (b *Builder) isPublicPrivateNetworkCommunication() bool {
   302  	return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp
   303  }
   304  
   305  func (b *Builder) isPrivateNetworkCommunication() bool {
   306  	return b.config.VirtualNetworkName != ""
   307  }
   308  
   309  func (b *Builder) Cancel() {
   310  	if b.ctxCancel != nil {
   311  		log.Printf("Cancelling Azure builder...")
   312  		b.ctxCancel()
   313  	}
   314  	if b.runner != nil {
   315  		log.Println("Cancelling the step runner...")
   316  		b.runner.Cancel()
   317  	}
   318  }
   319  
   320  func equalLocation(location1, location2 string) bool {
   321  	return strings.EqualFold(canonicalizeLocation(location1), canonicalizeLocation(location2))
   322  }
   323  
   324  func canonicalizeLocation(location string) string {
   325  	return strings.Replace(location, " ", "", -1)
   326  }
   327  
   328  func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resourceGroupName string, storageAccountName string) (*armstorage.Account, error) {
   329  	account, err := client.AccountsClient.GetProperties(ctx, resourceGroupName, storageAccountName)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	return &account, err
   335  }
   336  
   337  func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
   338  	stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
   339  
   340  	stateBag.Put(constants.ArmTags, b.config.AzureTags)
   341  	stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
   342  	stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
   343  	if b.config.OSType == constants.Target_Windows {
   344  		stateBag.Put(constants.ArmKeyVaultDeploymentName, fmt.Sprintf("kv%s", b.config.tmpDeploymentName))
   345  	}
   346  	stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
   347  	stateBag.Put(constants.ArmNicName, b.config.tmpNicName)
   348  	stateBag.Put(constants.ArmPublicIPAddressName, b.config.tmpPublicIPAddressName)
   349  	if b.config.TempResourceGroupName != "" && b.config.BuildResourceGroupName != "" {
   350  		stateBag.Put(constants.ArmDoubleResourceGroupNameSet, true)
   351  	}
   352  	if b.config.tmpResourceGroupName != "" {
   353  		stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
   354  		stateBag.Put(constants.ArmIsExistingResourceGroup, false)
   355  	} else {
   356  		stateBag.Put(constants.ArmResourceGroupName, b.config.BuildResourceGroupName)
   357  		stateBag.Put(constants.ArmIsExistingResourceGroup, true)
   358  	}
   359  	stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
   360  
   361  	stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage())
   362  	stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName)
   363  	stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName)
   364  	stateBag.Put(constants.ArmAsyncResourceGroupDelete, b.config.AsyncResourceGroupDelete)
   365  }
   366  
   367  // Parameters that are only known at runtime after querying Azure.
   368  func (b *Builder) setRuntimeParameters(stateBag multistep.StateBag) {
   369  	stateBag.Put(constants.ArmLocation, b.config.Location)
   370  	stateBag.Put(constants.ArmManagedImageLocation, b.config.manageImageLocation)
   371  }
   372  
   373  func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
   374  	stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
   375  }
   376  
   377  func (b *Builder) setImageParameters(stateBag multistep.StateBag) {
   378  	stateBag.Put(constants.ArmImageParameters, b.config.toImageParameters())
   379  }
   380  
   381  func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) {
   382  	var servicePrincipalToken *adal.ServicePrincipalToken
   383  	var servicePrincipalTokenVault *adal.ServicePrincipalToken
   384  
   385  	var err error
   386  
   387  	if b.config.useDeviceLogin {
   388  		say("Getting auth token for Service management endpoint")
   389  		servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, b.config.cloudEnvironment.ServiceManagementEndpoint)
   390  		if err != nil {
   391  			return nil, nil, err
   392  		}
   393  		say("Getting token for Vault resource")
   394  		servicePrincipalTokenVault, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
   395  		if err != nil {
   396  			return nil, nil, err
   397  		}
   398  
   399  	} else {
   400  		auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID)
   401  
   402  		servicePrincipalToken, err = auth.getServicePrincipalToken()
   403  		if err != nil {
   404  			return nil, nil, err
   405  		}
   406  
   407  		servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
   408  			strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
   409  		if err != nil {
   410  			return nil, nil, err
   411  		}
   412  
   413  	}
   414  
   415  	err = servicePrincipalToken.EnsureFresh()
   416  
   417  	if err != nil {
   418  		return nil, nil, err
   419  	}
   420  
   421  	err = servicePrincipalTokenVault.EnsureFresh()
   422  
   423  	if err != nil {
   424  		return nil, nil, err
   425  	}
   426  
   427  	return servicePrincipalToken, servicePrincipalTokenVault, nil
   428  }
   429  
   430  func getObjectIdFromToken(ui packer.Ui, token *adal.ServicePrincipalToken) string {
   431  	claims := jwt.MapClaims{}
   432  	var p jwt.Parser
   433  
   434  	var err error
   435  
   436  	_, _, err = p.ParseUnverified(token.OAuthToken(), claims)
   437  
   438  	if err != nil {
   439  		ui.Error(fmt.Sprintf("Failed to parse the token,Error: %s", err.Error()))
   440  		return ""
   441  	}
   442  	return claims["oid"].(string)
   443  
   444  }