github.phpd.cn/hashicorp/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 }