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 }