2016-03-04 05:14:55 -05:00
package arm
import (
"errors"
"fmt"
"log"
2017-06-27 16:19:21 -04:00
"os"
"runtime"
2016-06-30 19:51:52 -04:00
"strings"
2016-04-21 19:50:03 -04:00
"time"
2016-03-04 05:14:55 -05:00
2017-04-04 16:39:01 -04:00
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
2016-03-04 05:14:55 -05:00
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/builder/azure/common/lin"
2016-04-21 19:50:03 -04:00
2017-05-29 00:06:09 -04:00
"github.com/Azure/azure-sdk-for-go/arm/storage"
"github.com/Azure/go-autorest/autorest/adal"
2017-04-04 16:39:01 -04:00
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
2018-01-19 19:18:44 -05:00
"github.com/hashicorp/packer/helper/multistep"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/packer"
2016-03-04 05:14:55 -05:00
)
type Builder struct {
config * Config
stateBag multistep . StateBag
runner multistep . Runner
}
const (
2016-06-30 19:51:52 -04:00
DefaultNicName = "packerNic"
2016-03-04 05:14:55 -05:00
DefaultPublicIPAddressName = "packerPublicIP"
2016-04-21 19:50:03 -04:00
DefaultSasBlobContainer = "system/Microsoft.Compute"
DefaultSasBlobPermission = "r"
DefaultSecretName = "packerKeyVaultSecret"
2016-03-04 05:14:55 -05:00
)
func ( b * Builder ) Prepare ( raws ... interface { } ) ( [ ] string , error ) {
c , warnings , errs := newConfig ( raws ... )
if errs != nil {
return warnings , errs
}
b . config = c
b . stateBag = new ( multistep . BasicStateBag )
2016-04-21 19:50:03 -04:00
b . configureStateBag ( b . stateBag )
b . setTemplateParameters ( b . stateBag )
2017-05-29 00:06:09 -04:00
b . setImageParameters ( b . stateBag )
2016-03-04 05:14:55 -05:00
return warnings , errs
}
func ( b * Builder ) Run ( ui packer . Ui , hook packer . Hook , cache packer . Cache ) ( packer . Artifact , error ) {
2016-06-22 19:04:13 -04:00
ui . Say ( "Running builder ..." )
if err := newConfigRetriever ( ) . FillParameters ( b . config ) ; err != nil {
return nil , err
}
2016-03-04 05:14:55 -05:00
2016-04-29 18:02:28 -04:00
log . Print ( ":: Configuration" )
packerAzureCommon . DumpConfig ( b . config , func ( s string ) { log . Print ( s ) } )
2016-03-04 05:14:55 -05:00
b . stateBag . Put ( "hook" , hook )
b . stateBag . Put ( constants . Ui , ui )
2016-04-21 19:50:03 -04:00
spnCloud , spnKeyVault , err := b . getServicePrincipalTokens ( ui . Say )
2016-03-04 05:14:55 -05:00
if err != nil {
return nil , err
}
ui . Message ( "Creating Azure Resource Manager (ARM) client ..." )
2016-04-21 19:50:03 -04:00
azureClient , err := NewAzureClient (
b . config . SubscriptionID ,
b . config . ResourceGroupName ,
b . config . StorageAccount ,
2016-07-07 20:28:47 -04:00
b . config . cloudEnvironment ,
2016-04-21 19:50:03 -04:00
spnCloud ,
spnKeyVault )
2016-03-04 05:14:55 -05:00
if err != nil {
return nil , err
}
2016-06-30 19:51:52 -04:00
resolver := newResourceResolver ( azureClient )
if err := resolver . Resolve ( b . config ) ; err != nil {
return nil , err
}
2017-05-29 00:06:09 -04:00
if b . config . isManagedImage ( ) {
2017-05-30 14:25:46 -04:00
group , err := azureClient . GroupsClient . Get ( b . config . ManagedImageResourceGroupName )
2017-05-29 00:06:09 -04:00
if err != nil {
2017-05-30 14:25:46 -04:00
return nil , fmt . Errorf ( "Cannot locate the managed image resource group %s." , b . config . ManagedImageResourceGroupName )
2017-05-29 00:06:09 -04:00
}
2017-05-30 14:25:46 -04:00
b . config . manageImageLocation = * group . Location
2017-05-29 00:06:09 -04:00
// If a managed image already exists it cannot be overwritten.
2017-05-30 14:25:46 -04:00
_ , err = azureClient . ImagesClient . Get ( b . config . ManagedImageResourceGroupName , b . config . ManagedImageName , "" )
2017-05-29 00:06:09 -04:00
if err == nil {
2017-05-30 14:25:46 -04:00
return nil , fmt . Errorf ( "A managed image named %s already exists in the resource group %s." , b . config . ManagedImageName , b . config . ManagedImageResourceGroupName )
2017-05-29 00:06:09 -04:00
}
}
2017-11-30 19:43:35 -05:00
if b . config . BuildResourceGroupName != "" {
group , err := azureClient . GroupsClient . Get ( b . config . BuildResourceGroupName )
if err != nil {
return nil , fmt . Errorf ( "Cannot locate the existing build resource resource group %s." , b . config . BuildResourceGroupName )
}
b . config . Location = * group . Location
}
2017-06-07 18:01:10 -04:00
if b . config . StorageAccount != "" {
account , err := b . getBlobAccount ( azureClient , b . config . ResourceGroupName , b . config . StorageAccount )
if err != nil {
return nil , err
}
b . config . storageAccountBlobEndpoint = * account . AccountProperties . PrimaryEndpoints . Blob
2017-05-29 00:06:09 -04:00
2017-06-07 18:01:10 -04:00
if ! equalLocation ( * account . Location , b . config . Location ) {
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 )
}
2017-05-29 00:06:09 -04:00
}
2016-04-21 19:50:03 -04:00
2016-06-30 19:51:52 -04:00
endpointConnectType := PublicEndpoint
2017-08-06 18:32:44 -04:00
if b . isPublicPrivateNetworkCommunication ( ) && b . isPrivateNetworkCommunication ( ) {
endpointConnectType = PublicEndpointInPrivateNetwork
} else if b . isPrivateNetworkCommunication ( ) {
2016-06-30 19:51:52 -04:00
endpointConnectType = PrivateEndpoint
}
2017-05-29 00:06:09 -04:00
b . setRuntimeParameters ( b . stateBag )
2016-04-21 19:50:03 -04:00
b . setTemplateParameters ( b . stateBag )
2017-05-29 00:06:09 -04:00
b . setImageParameters ( b . stateBag )
2016-04-21 19:50:03 -04:00
var steps [ ] multistep . Step
2017-11-30 03:11:17 -05:00
deploymentName := b . stateBag . Get ( constants . ArmDeploymentName ) . ( string )
2016-10-12 20:54:59 -04:00
if b . config . OSType == constants . Target_Linux {
2016-04-21 19:50:03 -04:00
steps = [ ] multistep . Step {
NewStepCreateResourceGroup ( azureClient , ui ) ,
2016-05-21 02:01:16 -04:00
NewStepValidateTemplate ( azureClient , ui , b . config , GetVirtualMachineDeployment ) ,
2017-11-30 03:11:17 -05:00
NewStepDeployTemplate ( azureClient , ui , b . config , deploymentName , GetVirtualMachineDeployment ) ,
2016-06-30 19:51:52 -04:00
NewStepGetIPAddress ( azureClient , ui , endpointConnectType ) ,
2016-04-21 19:50:03 -04:00
& communicator . StepConnectSSH {
Config : & b . config . Comm ,
Host : lin . SSHHost ,
SSHConfig : lin . SSHConfig ( b . config . UserName ) ,
} ,
2016-04-29 18:02:28 -04:00
& packerCommon . StepProvision { } ,
2016-04-21 19:50:03 -04:00
NewStepGetOSDisk ( azureClient , ui ) ,
NewStepPowerOffCompute ( azureClient , ui ) ,
NewStepCaptureImage ( azureClient , ui ) ,
NewStepDeleteResourceGroup ( azureClient , ui ) ,
NewStepDeleteOSDisk ( azureClient , ui ) ,
}
2016-10-12 20:54:59 -04:00
} else if b . config . OSType == constants . Target_Windows {
2017-11-30 03:11:17 -05:00
keyVaultDeploymentName := b . stateBag . Get ( constants . ArmKeyVaultDeploymentName ) . ( string )
2016-04-21 19:50:03 -04:00
steps = [ ] multistep . Step {
NewStepCreateResourceGroup ( azureClient , ui ) ,
2016-05-21 02:01:16 -04:00
NewStepValidateTemplate ( azureClient , ui , b . config , GetKeyVaultDeployment ) ,
2017-11-30 03:11:17 -05:00
NewStepDeployTemplate ( azureClient , ui , b . config , keyVaultDeploymentName , GetKeyVaultDeployment ) ,
2016-04-21 19:50:03 -04:00
NewStepGetCertificate ( azureClient , ui ) ,
NewStepSetCertificate ( b . config , ui ) ,
2016-05-21 02:01:16 -04:00
NewStepValidateTemplate ( azureClient , ui , b . config , GetVirtualMachineDeployment ) ,
2017-11-30 03:11:17 -05:00
NewStepDeployTemplate ( azureClient , ui , b . config , deploymentName , GetVirtualMachineDeployment ) ,
2016-06-30 19:51:52 -04:00
NewStepGetIPAddress ( azureClient , ui , endpointConnectType ) ,
2016-04-21 19:50:03 -04:00
& communicator . StepConnectWinRM {
Config : & b . config . Comm ,
Host : func ( stateBag multistep . StateBag ) ( string , error ) {
return stateBag . Get ( constants . SSHHost ) . ( string ) , nil
} ,
WinRMConfig : func ( multistep . StateBag ) ( * communicator . WinRMConfig , error ) {
return & communicator . WinRMConfig {
Username : b . config . UserName ,
Password : b . config . tmpAdminPassword ,
} , nil
} ,
} ,
2016-04-29 18:02:28 -04:00
& packerCommon . StepProvision { } ,
2016-04-21 19:50:03 -04:00
NewStepGetOSDisk ( azureClient , ui ) ,
NewStepPowerOffCompute ( azureClient , ui ) ,
NewStepCaptureImage ( azureClient , ui ) ,
NewStepDeleteResourceGroup ( azureClient , ui ) ,
NewStepDeleteOSDisk ( azureClient , ui ) ,
}
} else {
return nil , fmt . Errorf ( "Builder does not support the os_type '%s'" , b . config . OSType )
2016-03-04 05:14:55 -05:00
}
if b . config . PackerDebug {
ui . Message ( fmt . Sprintf ( "temp admin user: '%s'" , b . config . UserName ) )
ui . Message ( fmt . Sprintf ( "temp admin password: '%s'" , b . config . Password ) )
2017-06-26 16:44:34 -04:00
if b . config . sshPrivateKey != "" {
2017-06-27 16:19:21 -04:00
debugKeyPath := fmt . Sprintf ( "%s-%s.pem" , b . config . PackerBuildName , b . config . tmpComputeName )
ui . Message ( fmt . Sprintf ( "temp ssh key: %s" , debugKeyPath ) )
b . writeSSHPrivateKey ( ui , debugKeyPath )
2017-06-26 16:44:34 -04:00
}
2016-03-04 05:14:55 -05:00
}
2016-09-13 20:04:18 -04:00
b . runner = packerCommon . NewRunner ( steps , b . config . PackerConfig , ui )
2016-03-04 05:14:55 -05:00
b . runner . Run ( b . stateBag )
// Report any errors.
if rawErr , ok := b . stateBag . GetOk ( constants . Error ) ; ok {
return nil , rawErr . ( error )
}
// If we were interrupted or cancelled, then just exit.
if _ , ok := b . stateBag . GetOk ( multistep . StateCancelled ) ; ok {
return nil , errors . New ( "Build was cancelled." )
}
if _ , ok := b . stateBag . GetOk ( multistep . StateHalted ) ; ok {
return nil , errors . New ( "Build was halted." )
}
2017-06-07 18:01:10 -04:00
if b . config . isManagedImage ( ) {
return NewManagedImageArtifact ( b . config . ManagedImageResourceGroupName , b . config . ManagedImageName , b . config . manageImageLocation )
} else if template , ok := b . stateBag . GetOk ( constants . ArmCaptureTemplate ) ; ok {
2016-04-21 19:50:03 -04:00
return NewArtifact (
template . ( * CaptureTemplate ) ,
func ( name string ) string {
month := time . Now ( ) . AddDate ( 0 , 1 , 0 ) . UTC ( )
2017-05-28 03:38:45 -04:00
blob := azureClient . BlobStorageClient . GetContainerReference ( DefaultSasBlobContainer ) . GetBlobReference ( name )
sasUrl , _ := blob . GetSASURI ( month , DefaultSasBlobPermission )
2016-04-21 19:50:03 -04:00
return sasUrl
} )
}
return & Artifact { } , nil
2016-03-04 05:14:55 -05:00
}
2017-06-27 16:19:21 -04:00
func ( b * Builder ) writeSSHPrivateKey ( ui packer . Ui , debugKeyPath string ) {
f , err := os . Create ( debugKeyPath )
if err != nil {
ui . Say ( fmt . Sprintf ( "Error saving debug key: %s" , err ) )
}
defer f . Close ( )
// Write the key out
if _ , err := f . Write ( [ ] byte ( b . config . sshPrivateKey ) ) ; err != nil {
ui . Say ( fmt . Sprintf ( "Error saving debug key: %s" , err ) )
return
}
// Chmod it so that it is SSH ready
if runtime . GOOS != "windows" {
if err := f . Chmod ( 0600 ) ; err != nil {
ui . Say ( fmt . Sprintf ( "Error setting permissions of debug key: %s" , err ) )
}
}
}
2017-08-06 18:32:44 -04:00
func ( b * Builder ) isPublicPrivateNetworkCommunication ( ) bool {
return DefaultPrivateVirtualNetworkWithPublicIp != b . config . PrivateVirtualNetworkWithPublicIp
}
2016-06-30 19:51:52 -04:00
func ( b * Builder ) isPrivateNetworkCommunication ( ) bool {
return b . config . VirtualNetworkName != ""
}
2016-03-04 05:14:55 -05:00
func ( b * Builder ) Cancel ( ) {
if b . runner != nil {
log . Println ( "Cancelling the step runner..." )
b . runner . Cancel ( )
}
}
2017-05-29 00:06:09 -04:00
func equalLocation ( location1 , location2 string ) bool {
return strings . EqualFold ( canonicalizeLocation ( location1 ) , canonicalizeLocation ( location2 ) )
}
func canonicalizeLocation ( location string ) string {
return strings . Replace ( location , " " , "" , - 1 )
}
func ( b * Builder ) getBlobAccount ( client * AzureClient , resourceGroupName string , storageAccountName string ) ( * storage . Account , error ) {
2016-04-21 19:50:03 -04:00
account , err := client . AccountsClient . GetProperties ( resourceGroupName , storageAccountName )
if err != nil {
2017-05-29 00:06:09 -04:00
return nil , err
2016-04-21 19:50:03 -04:00
}
2017-05-29 00:06:09 -04:00
return & account , err
2016-04-21 19:50:03 -04:00
}
func ( b * Builder ) configureStateBag ( stateBag multistep . StateBag ) {
2016-03-04 05:14:55 -05:00
stateBag . Put ( constants . AuthorizedKey , b . config . sshAuthorizedKey )
stateBag . Put ( constants . PrivateKey , b . config . sshPrivateKey )
2016-07-29 17:17:33 -04:00
stateBag . Put ( constants . ArmTags , & b . config . AzureTags )
2016-03-04 05:14:55 -05:00
stateBag . Put ( constants . ArmComputeName , b . config . tmpComputeName )
stateBag . Put ( constants . ArmDeploymentName , b . config . tmpDeploymentName )
2017-11-30 03:11:17 -05:00
if b . config . OSType == constants . Target_Windows {
stateBag . Put ( constants . ArmKeyVaultDeploymentName , fmt . Sprintf ( "kv%s" , b . config . tmpDeploymentName ) )
}
2016-04-21 19:50:03 -04:00
stateBag . Put ( constants . ArmKeyVaultName , b . config . tmpKeyVaultName )
2016-06-30 19:51:52 -04:00
stateBag . Put ( constants . ArmNicName , DefaultNicName )
2016-04-21 19:50:03 -04:00
stateBag . Put ( constants . ArmPublicIPAddressName , DefaultPublicIPAddressName )
2017-11-09 06:20:09 -05:00
if b . config . TempResourceGroupName != "" && b . config . BuildResourceGroupName != "" {
stateBag . Put ( constants . ArmDoubleResourceGroupNameSet , true )
}
if b . config . tmpResourceGroupName != "" {
stateBag . Put ( constants . ArmResourceGroupName , b . config . tmpResourceGroupName )
stateBag . Put ( constants . ArmIsExistingResourceGroup , false )
} else {
stateBag . Put ( constants . ArmResourceGroupName , b . config . BuildResourceGroupName )
stateBag . Put ( constants . ArmIsExistingResourceGroup , true )
}
2016-04-21 19:50:03 -04:00
stateBag . Put ( constants . ArmStorageAccountName , b . config . StorageAccount )
2017-05-29 00:06:09 -04:00
stateBag . Put ( constants . ArmIsManagedImage , b . config . isManagedImage ( ) )
2017-05-30 14:25:46 -04:00
stateBag . Put ( constants . ArmManagedImageResourceGroupName , b . config . ManagedImageResourceGroupName )
stateBag . Put ( constants . ArmManagedImageName , b . config . ManagedImageName )
2017-05-29 00:06:09 -04:00
}
// Parameters that are only known at runtime after querying Azure.
func ( b * Builder ) setRuntimeParameters ( stateBag multistep . StateBag ) {
2017-11-30 19:43:35 -05:00
stateBag . Put ( constants . ArmLocation , b . config . Location )
2017-05-30 14:25:46 -04:00
stateBag . Put ( constants . ArmManagedImageLocation , b . config . manageImageLocation )
2016-04-21 19:50:03 -04:00
}
func ( b * Builder ) setTemplateParameters ( stateBag multistep . StateBag ) {
2016-03-04 05:14:55 -05:00
stateBag . Put ( constants . ArmVirtualMachineCaptureParameters , b . config . toVirtualMachineCaptureParameters ( ) )
2016-04-21 19:50:03 -04:00
}
2016-03-04 05:14:55 -05:00
2017-05-29 00:06:09 -04:00
func ( b * Builder ) setImageParameters ( stateBag multistep . StateBag ) {
stateBag . Put ( constants . ArmImageParameters , b . config . toImageParameters ( ) )
}
2017-05-28 03:38:45 -04:00
func ( b * Builder ) getServicePrincipalTokens ( say func ( string ) ) ( * adal . ServicePrincipalToken , * adal . ServicePrincipalToken , error ) {
var servicePrincipalToken * adal . ServicePrincipalToken
var servicePrincipalTokenVault * adal . ServicePrincipalToken
2016-03-04 05:14:55 -05:00
2016-04-21 19:50:03 -04:00
var err error
2016-03-04 05:14:55 -05:00
2016-04-21 19:50:03 -04:00
if b . config . useDeviceLogin {
2016-07-16 01:23:53 -04:00
servicePrincipalToken , err = packerAzureCommon . Authenticate ( * b . config . cloudEnvironment , b . config . TenantID , say )
2016-04-21 19:50:03 -04:00
if err != nil {
return nil , nil , err
}
} else {
auth := NewAuthenticate ( * b . config . cloudEnvironment , b . config . ClientID , b . config . ClientSecret , b . config . TenantID )
2016-03-04 05:14:55 -05:00
2016-04-21 19:50:03 -04:00
servicePrincipalToken , err = auth . getServicePrincipalToken ( )
if err != nil {
return nil , nil , err
}
2016-07-07 20:28:47 -04:00
servicePrincipalTokenVault , err = auth . getServicePrincipalTokenWithResource (
strings . TrimRight ( b . config . cloudEnvironment . KeyVaultEndpoint , "/" ) )
2016-04-21 19:50:03 -04:00
if err != nil {
return nil , nil , err
}
}
2016-03-04 05:14:55 -05:00
2016-04-21 19:50:03 -04:00
return servicePrincipalToken , servicePrincipalTokenVault , nil
2016-03-04 05:14:55 -05:00
}