github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/azure/arm/builder.go (about) 1 package arm 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "runtime" 9 "strings" 10 "time" 11 12 packerAzureCommon "github.com/hashicorp/packer/builder/azure/common" 13 14 "github.com/hashicorp/packer/builder/azure/common/constants" 15 "github.com/hashicorp/packer/builder/azure/common/lin" 16 17 "github.com/Azure/azure-sdk-for-go/arm/storage" 18 "github.com/Azure/go-autorest/autorest/adal" 19 packerCommon "github.com/hashicorp/packer/common" 20 "github.com/hashicorp/packer/helper/communicator" 21 "github.com/hashicorp/packer/packer" 22 "github.com/mitchellh/multistep" 23 ) 24 25 type Builder struct { 26 config *Config 27 stateBag multistep.StateBag 28 runner multistep.Runner 29 } 30 31 const ( 32 DefaultNicName = "packerNic" 33 DefaultPublicIPAddressName = "packerPublicIP" 34 DefaultSasBlobContainer = "system/Microsoft.Compute" 35 DefaultSasBlobPermission = "r" 36 DefaultSecretName = "packerKeyVaultSecret" 37 ) 38 39 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 40 c, warnings, errs := newConfig(raws...) 41 if errs != nil { 42 return warnings, errs 43 } 44 45 b.config = c 46 47 b.stateBag = new(multistep.BasicStateBag) 48 b.configureStateBag(b.stateBag) 49 b.setTemplateParameters(b.stateBag) 50 b.setImageParameters(b.stateBag) 51 52 return warnings, errs 53 } 54 55 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 56 ui.Say("Running builder ...") 57 58 if err := newConfigRetriever().FillParameters(b.config); err != nil { 59 return nil, err 60 } 61 62 log.Print(":: Configuration") 63 packerAzureCommon.DumpConfig(b.config, func(s string) { log.Print(s) }) 64 65 b.stateBag.Put("hook", hook) 66 b.stateBag.Put(constants.Ui, ui) 67 68 spnCloud, spnKeyVault, err := b.getServicePrincipalTokens(ui.Say) 69 if err != nil { 70 return nil, err 71 } 72 73 ui.Message("Creating Azure Resource Manager (ARM) client ...") 74 azureClient, err := NewAzureClient( 75 b.config.SubscriptionID, 76 b.config.ResourceGroupName, 77 b.config.StorageAccount, 78 b.config.cloudEnvironment, 79 spnCloud, 80 spnKeyVault) 81 82 if err != nil { 83 return nil, err 84 } 85 86 resolver := newResourceResolver(azureClient) 87 if err := resolver.Resolve(b.config); err != nil { 88 return nil, err 89 } 90 91 if b.config.isManagedImage() { 92 group, err := azureClient.GroupsClient.Get(b.config.ManagedImageResourceGroupName) 93 if err != nil { 94 return nil, fmt.Errorf("Cannot locate the managed image resource group %s.", b.config.ManagedImageResourceGroupName) 95 } 96 97 b.config.manageImageLocation = *group.Location 98 99 // If a managed image already exists it cannot be overwritten. 100 _, err = azureClient.ImagesClient.Get(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, "") 101 if err == nil { 102 return nil, fmt.Errorf("A managed image named %s already exists in the resource group %s.", b.config.ManagedImageName, b.config.ManagedImageResourceGroupName) 103 } 104 } 105 106 if b.config.StorageAccount != "" { 107 account, err := b.getBlobAccount(azureClient, b.config.ResourceGroupName, b.config.StorageAccount) 108 if err != nil { 109 return nil, err 110 } 111 b.config.storageAccountBlobEndpoint = *account.AccountProperties.PrimaryEndpoints.Blob 112 113 if !equalLocation(*account.Location, b.config.Location) { 114 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) 115 } 116 } 117 118 endpointConnectType := PublicEndpoint 119 if b.isPublicPrivateNetworkCommunication() && b.isPrivateNetworkCommunication() { 120 endpointConnectType = PublicEndpointInPrivateNetwork 121 } else if b.isPrivateNetworkCommunication() { 122 endpointConnectType = PrivateEndpoint 123 } 124 125 b.setRuntimeParameters(b.stateBag) 126 b.setTemplateParameters(b.stateBag) 127 b.setImageParameters(b.stateBag) 128 var steps []multistep.Step 129 130 if b.config.OSType == constants.Target_Linux { 131 steps = []multistep.Step{ 132 NewStepCreateResourceGroup(azureClient, ui), 133 NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), 134 NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), 135 NewStepGetIPAddress(azureClient, ui, endpointConnectType), 136 &communicator.StepConnectSSH{ 137 Config: &b.config.Comm, 138 Host: lin.SSHHost, 139 SSHConfig: lin.SSHConfig(b.config.UserName), 140 }, 141 &packerCommon.StepProvision{}, 142 NewStepGetOSDisk(azureClient, ui), 143 NewStepPowerOffCompute(azureClient, ui), 144 NewStepCaptureImage(azureClient, ui), 145 NewStepDeleteResourceGroup(azureClient, ui), 146 NewStepDeleteOSDisk(azureClient, ui), 147 } 148 } else if b.config.OSType == constants.Target_Windows { 149 steps = []multistep.Step{ 150 NewStepCreateResourceGroup(azureClient, ui), 151 NewStepValidateTemplate(azureClient, ui, b.config, GetKeyVaultDeployment), 152 NewStepDeployTemplate(azureClient, ui, b.config, GetKeyVaultDeployment), 153 NewStepGetCertificate(azureClient, ui), 154 NewStepSetCertificate(b.config, ui), 155 NewStepValidateTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), 156 NewStepDeployTemplate(azureClient, ui, b.config, GetVirtualMachineDeployment), 157 NewStepGetIPAddress(azureClient, ui, endpointConnectType), 158 &communicator.StepConnectWinRM{ 159 Config: &b.config.Comm, 160 Host: func(stateBag multistep.StateBag) (string, error) { 161 return stateBag.Get(constants.SSHHost).(string), nil 162 }, 163 WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) { 164 return &communicator.WinRMConfig{ 165 Username: b.config.UserName, 166 Password: b.config.tmpAdminPassword, 167 }, nil 168 }, 169 }, 170 &packerCommon.StepProvision{}, 171 NewStepGetOSDisk(azureClient, ui), 172 NewStepPowerOffCompute(azureClient, ui), 173 NewStepCaptureImage(azureClient, ui), 174 NewStepDeleteResourceGroup(azureClient, ui), 175 NewStepDeleteOSDisk(azureClient, ui), 176 } 177 } else { 178 return nil, fmt.Errorf("Builder does not support the os_type '%s'", b.config.OSType) 179 } 180 181 if b.config.PackerDebug { 182 ui.Message(fmt.Sprintf("temp admin user: '%s'", b.config.UserName)) 183 ui.Message(fmt.Sprintf("temp admin password: '%s'", b.config.Password)) 184 185 if b.config.sshPrivateKey != "" { 186 debugKeyPath := fmt.Sprintf("%s-%s.pem", b.config.PackerBuildName, b.config.tmpComputeName) 187 ui.Message(fmt.Sprintf("temp ssh key: %s", debugKeyPath)) 188 189 b.writeSSHPrivateKey(ui, debugKeyPath) 190 } 191 } 192 193 b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui) 194 b.runner.Run(b.stateBag) 195 196 // Report any errors. 197 if rawErr, ok := b.stateBag.GetOk(constants.Error); ok { 198 return nil, rawErr.(error) 199 } 200 201 // If we were interrupted or cancelled, then just exit. 202 if _, ok := b.stateBag.GetOk(multistep.StateCancelled); ok { 203 return nil, errors.New("Build was cancelled.") 204 } 205 206 if _, ok := b.stateBag.GetOk(multistep.StateHalted); ok { 207 return nil, errors.New("Build was halted.") 208 } 209 210 if b.config.isManagedImage() { 211 return NewManagedImageArtifact(b.config.ManagedImageResourceGroupName, b.config.ManagedImageName, b.config.manageImageLocation) 212 } else if template, ok := b.stateBag.GetOk(constants.ArmCaptureTemplate); ok { 213 return NewArtifact( 214 template.(*CaptureTemplate), 215 func(name string) string { 216 month := time.Now().AddDate(0, 1, 0).UTC() 217 blob := azureClient.BlobStorageClient.GetContainerReference(DefaultSasBlobContainer).GetBlobReference(name) 218 sasUrl, _ := blob.GetSASURI(month, DefaultSasBlobPermission) 219 return sasUrl 220 }) 221 } 222 223 return &Artifact{}, nil 224 } 225 226 func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) { 227 f, err := os.Create(debugKeyPath) 228 if err != nil { 229 ui.Say(fmt.Sprintf("Error saving debug key: %s", err)) 230 } 231 defer f.Close() 232 233 // Write the key out 234 if _, err := f.Write([]byte(b.config.sshPrivateKey)); err != nil { 235 ui.Say(fmt.Sprintf("Error saving debug key: %s", err)) 236 return 237 } 238 239 // Chmod it so that it is SSH ready 240 if runtime.GOOS != "windows" { 241 if err := f.Chmod(0600); err != nil { 242 ui.Say(fmt.Sprintf("Error setting permissions of debug key: %s", err)) 243 } 244 } 245 } 246 247 func (b *Builder) isPublicPrivateNetworkCommunication() bool { 248 return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp 249 } 250 251 func (b *Builder) isPrivateNetworkCommunication() bool { 252 return b.config.VirtualNetworkName != "" 253 } 254 255 func (b *Builder) Cancel() { 256 if b.runner != nil { 257 log.Println("Cancelling the step runner...") 258 b.runner.Cancel() 259 } 260 } 261 262 func equalLocation(location1, location2 string) bool { 263 return strings.EqualFold(canonicalizeLocation(location1), canonicalizeLocation(location2)) 264 } 265 266 func canonicalizeLocation(location string) string { 267 return strings.Replace(location, " ", "", -1) 268 } 269 270 func (b *Builder) getBlobAccount(client *AzureClient, resourceGroupName string, storageAccountName string) (*storage.Account, error) { 271 account, err := client.AccountsClient.GetProperties(resourceGroupName, storageAccountName) 272 if err != nil { 273 return nil, err 274 } 275 276 return &account, err 277 } 278 279 func (b *Builder) configureStateBag(stateBag multistep.StateBag) { 280 stateBag.Put(constants.AuthorizedKey, b.config.sshAuthorizedKey) 281 stateBag.Put(constants.PrivateKey, b.config.sshPrivateKey) 282 283 stateBag.Put(constants.ArmTags, &b.config.AzureTags) 284 stateBag.Put(constants.ArmComputeName, b.config.tmpComputeName) 285 stateBag.Put(constants.ArmDeploymentName, b.config.tmpDeploymentName) 286 stateBag.Put(constants.ArmKeyVaultName, b.config.tmpKeyVaultName) 287 stateBag.Put(constants.ArmLocation, b.config.Location) 288 stateBag.Put(constants.ArmNicName, DefaultNicName) 289 stateBag.Put(constants.ArmPublicIPAddressName, DefaultPublicIPAddressName) 290 stateBag.Put(constants.ArmResourceGroupName, b.config.tmpResourceGroupName) 291 stateBag.Put(constants.ArmStorageAccountName, b.config.StorageAccount) 292 293 stateBag.Put(constants.ArmIsManagedImage, b.config.isManagedImage()) 294 stateBag.Put(constants.ArmManagedImageResourceGroupName, b.config.ManagedImageResourceGroupName) 295 stateBag.Put(constants.ArmManagedImageName, b.config.ManagedImageName) 296 } 297 298 // Parameters that are only known at runtime after querying Azure. 299 func (b *Builder) setRuntimeParameters(stateBag multistep.StateBag) { 300 stateBag.Put(constants.ArmManagedImageLocation, b.config.manageImageLocation) 301 } 302 303 func (b *Builder) setTemplateParameters(stateBag multistep.StateBag) { 304 stateBag.Put(constants.ArmVirtualMachineCaptureParameters, b.config.toVirtualMachineCaptureParameters()) 305 } 306 307 func (b *Builder) setImageParameters(stateBag multistep.StateBag) { 308 stateBag.Put(constants.ArmImageParameters, b.config.toImageParameters()) 309 } 310 311 func (b *Builder) getServicePrincipalTokens(say func(string)) (*adal.ServicePrincipalToken, *adal.ServicePrincipalToken, error) { 312 var servicePrincipalToken *adal.ServicePrincipalToken 313 var servicePrincipalTokenVault *adal.ServicePrincipalToken 314 315 var err error 316 317 if b.config.useDeviceLogin { 318 servicePrincipalToken, err = packerAzureCommon.Authenticate(*b.config.cloudEnvironment, b.config.TenantID, say) 319 if err != nil { 320 return nil, nil, err 321 } 322 } else { 323 auth := NewAuthenticate(*b.config.cloudEnvironment, b.config.ClientID, b.config.ClientSecret, b.config.TenantID) 324 325 servicePrincipalToken, err = auth.getServicePrincipalToken() 326 if err != nil { 327 return nil, nil, err 328 } 329 330 servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource( 331 strings.TrimRight(b.config.cloudEnvironment.KeyVaultEndpoint, "/")) 332 333 if err != nil { 334 return nil, nil, err 335 } 336 } 337 338 return servicePrincipalToken, servicePrincipalTokenVault, nil 339 }