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  }