github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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: lin.SSHConfig(b.config.UserName),
   178  			},
   179  			&packerCommon.StepProvision{},
   180  			NewStepGetOSDisk(azureClient, ui),
   181  			NewStepGetAdditionalDisks(azureClient, ui),
   182  			NewStepPowerOffCompute(azureClient, ui),
   183  			NewStepCaptureImage(azureClient, ui),
   184  			NewStepDeleteResourceGroup(azureClient, ui),
   185  			NewStepDeleteOSDisk(azureClient, ui),
   186  			NewStepDeleteAdditionalDisks(azureClient, ui),
   187  		}
   188  	} else if b.config.OSType == constants.Target_Windows {
   189  		keyVaultDeploymentName := b.stateBag.Get(constants.ArmKeyVaultDeploymentName).(string)
   190  		steps = []multistep.Step{
   191  			NewStepCreateResourceGroup(azureClient, ui),
   192  			NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment),
   193  			NewStepDeployTemplate(azureClient, ui, b.config, keyVaultDeploymentName, GetKeyVaultDeployment),
   194  			NewStepGetCertificate(azureClient, ui),
   195  			NewStepSetCertificate(b.config, ui),
   196  			NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment),
   197  			NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment),
   198  			NewStepGetIPAddress(azureClient, ui, endpointConnectType),
   199  			&StepSaveWinRMPassword{
   200  				Password:  b.config.tmpAdminPassword,
   201  				BuildName: b.config.PackerBuildName,
   202  			},
   203  			&communicator.StepConnectWinRM{
   204  				Config: &b.config.Comm,
   205  				Host: func(stateBag multistep.StateBag) (string, error) {
   206  					return stateBag.Get(constants.SSHHost).(string), nil
   207  				},
   208  				WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
   209  					return &communicator.WinRMConfig{
   210  						Username: b.config.UserName,
   211  						Password: b.config.tmpAdminPassword,
   212  					}, nil
   213  				},
   214  			},
   215  			&packerCommon.StepProvision{},
   216  			NewStepGetOSDisk(azureClient, ui),
   217  			NewStepGetAdditionalDisks(azureClient, ui),
   218  			NewStepPowerOffCompute(azureClient, ui),
   219  			NewStepCaptureImage(azureClient, ui),
   220  			NewStepDeleteResourceGroup(azureClient, ui),
   221  			NewStepDeleteOSDisk(azureClient, ui),
   222  			NewStepDeleteAdditionalDisks(azureClient, ui),
   223  		}
   224  	} else {
   225  		return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType)
   226  	}
   227  
   228  	if b.config.PackerDebug {
   229  		ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName))
   230  		ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password))
   231  
   232  		if b.config.sshPrivateKey != "" {
   233  			debugKeyPath := fmt.Sprintf("%s-%s.pem", b.config.PackerBuildName, b.config.tmpComputeName)
   234  			ui.Message(fmt.Sprintf("temp ssh key: %s", debugKeyPath))
   235  
   236  			b.writeSSHPrivateKey(ui, debugKeyPath)
   237  		}
   238  	}
   239  
   240  	b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
   241  	b.runner.Run(b.stateBag)
   242  
   243  	// Report any errors.
   244  	if rawErr, ok := b.stateBag.GetOk(constants.Error); ok {
   245  		return nil, rawErr.(error)
   246  	}
   247  
   248  	// If we were interrupted or cancelled, then just exit.
   249  	if _, ok := b.stateBag.GetOk(multistep.StateCancelled); ok {
   250  		return nil, errors.New("Build was cancelled.")
   251  	}
   252  
   253  	if _, ok := b.stateBag.GetOk(multistep.StateHalted); ok {
   254  		return nil, errors.New("Build was halted.")
   255  	}
   256  
   257  	if b.config.isManagedImage() {
   258  		return NewManagedImageArtifact(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation)
   259  	} else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok {
   260  		return NewArtifact(
   261  			template.(*CaptureTemplate),
   262  			func(name string) string {
   263  				blob := azureClient.BlobStorageClient.GetContainerReference(DefaultSasBlobContainer).GetBlobReference(name)
   264  				options := storage.BlobSASOptions{}
   265  				options.BlobServiceSASPermissions.Read = true
   266  				options.Expiry = time.Now().AddDate(0, 1, 0).UTC() // one month
   267  				sasUrl, _ := blob.GetSASURI(options)
   268  				return sasUrl
   269  			})
   270  	}
   271  
   272  	return &Artifact{}, nil
   273  }
   274  
   275  func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) {
   276  	f, err := os.Create(debugKeyPath)
   277  	if err != nil {
   278  		ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
   279  	}
   280  	defer f.Close()
   281  
   282  	// Write the key out
   283  	if _, err := f.Write([]byte(b.config.sshPrivateKey)); err != nil {
   284  		ui.Say(fmt.Sprintf("Error saving debug key: %s", err))
   285  		return
   286  	}
   287  
   288  	// Chmod it so that it is SSH ready
   289  	if runtime.GOOS != "windows" {
   290  		if err := f.Chmod(0600); err != nil {
   291  			ui.Say(fmt.Sprintf("Error setting permissions of debug key: %s", err))
   292  		}
   293  	}
   294  }
   295  
   296  func (b *Builder) isPublicPrivateNetworkCommunication() bool {
   297  	return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp
   298  }
   299  
   300  func (b *Builder) isPrivateNetworkCommunication() bool {
   301  	return b.config.VirtualNetworkName != ""
   302  }
   303  
   304  func (b *Builder) Cancel() {
   305  	if b.ctxCancel != nil {
   306  		log.Printf("Cancelling Azure builder...")
   307  		b.ctxCancel()
   308  	}
   309  	if b.runner != nil {
   310  		log.Println("Cancelling the step runner...")
   311  		b.runner.Cancel()
   312  	}
   313  }
   314  
   315  func equalLocation(location1, location2 string) bool {
   316  	return strings.EqualFold(canonicalizeLocation(location1), canonicalizeLocation(location2))
   317  }
   318  
   319  func canonicalizeLocation(location string) string {
   320  	return strings.Replace(location, " ", "", -1)
   321  }
   322  
   323  func (b *Builder) getBlobAccount(ctx context.Context, client *AzureClient, resourceGroupName string, storageAccountName string) (*armstorage.Account, error) {
   324  	account, err := client.AccountsClient.GetProperties(ctx, resourceGroupName, storageAccountName)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	return &account, err
   330  }
   331  
   332  func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
   333  	stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey)
   334  	stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey)
   335  
   336  	stateBag.Put(constants.ArmTags, b.config.AzureTags)
   337  	stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName)
   338  	stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName)
   339  	if b.config.OSType == constants.Target_Windows {
   340  		stateBag.Put(constants.ArmKeyVaultDeploymentName, fmt.Sprintf("kv%s", b.config.tmpDeploymentName))
   341  	}
   342  	stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName)
   343  	stateBag.Put(constants.ArmNicName, b.config.tmpNicName)
   344  	stateBag.Put(constants.ArmPublicIPAddressName, b.config.tmpPublicIPAddressName)
   345  	if b.config.TempResourceGroupName != "" && b.config.BuildResourceGroupName != "" {
   346  		stateBag.Put(constants.ArmDoubleResourceGroupNameSet, true)
   347  	}
   348  	if b.config.tmpResourceGroupName != "" {
   349  		stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName)
   350  		stateBag.Put(constants.ArmIsExistingResourceGroup, false)
   351  	} else {
   352  		stateBag.Put(constants.ArmResourceGroupName, b.config.BuildResourceGroupName)
   353  		stateBag.Put(constants.ArmIsExistingResourceGroup, true)
   354  	}
   355  	stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount)
   356  
   357  	stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage())
   358  	stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName)
   359  	stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName)
   360  	stateBag.Put(constants.ArmAsyncResourceGroupDelete, b.config.AsyncResourceGroupDelete)
   361  }
   362  
   363  // Parameters that are only known at runtime after querying Azure.
   364  func (b *Builder) setRuntimeParameters(stateBag multistep.StateBag) {
   365  	stateBag.Put(constants.ArmLocation, b.config.Location)
   366  	stateBag.Put(constants.ArmManagedImageLocation, b.config.manageImageLocation)
   367  }
   368  
   369  func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) {
   370  	stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters())
   371  }
   372  
   373  func (b *Builder) setImageParameters(stateBag multistep.StateBag) {
   374  	stateBag.Put(constants.ArmImageParameters, b.config.toImageParameters())
   375  }
   376  
   377  func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) {
   378  	var servicePrincipalToken *adal.ServicePrincipalToken
   379  	var servicePrincipalTokenVault *adal.ServicePrincipalToken
   380  
   381  	var err error
   382  
   383  	if b.config.useDeviceLogin {
   384  		say("Getting auth token for Service management endpoint")
   385  		servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, b.config.cloudEnvironment.ServiceManagementEndpoint)
   386  		if err != nil {
   387  			return nil, nil, err
   388  		}
   389  		say("Getting token for Vault resource")
   390  		servicePrincipalTokenVault, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say, strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
   391  		if err != nil {
   392  			return nil, nil, err
   393  		}
   394  
   395  	} else {
   396  		auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID)
   397  
   398  		servicePrincipalToken, err = auth.getServicePrincipalToken()
   399  		if err != nil {
   400  			return nil, nil, err
   401  		}
   402  
   403  		servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
   404  			strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/"))
   405  		if err != nil {
   406  			return nil, nil, err
   407  		}
   408  
   409  	}
   410  
   411  	err = servicePrincipalToken.EnsureFresh()
   412  
   413  	if err != nil {
   414  		return nil, nil, err
   415  	}
   416  
   417  	err = servicePrincipalTokenVault.EnsureFresh()
   418  
   419  	if err != nil {
   420  		return nil, nil, err
   421  	}
   422  
   423  	return servicePrincipalToken, servicePrincipalTokenVault, nil
   424  }
   425  
   426  func getObjectIdFromToken(ui packer.Ui, token *adal.ServicePrincipalToken) string {
   427  	claims := jwt.MapClaims{}
   428  	var p jwt.Parser
   429  
   430  	var err error
   431  
   432  	_, _, err = p.ParseUnverified(token.OAuthToken(), claims)
   433  
   434  	if err != nil {
   435  		ui.Error(fmt.Sprintf("Failed to parse the token,Error: %s", err.Error()))
   436  		return ""
   437  	}
   438  	return claims["oid"].(string)
   439  
   440  }