github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/builder/amazon/instance/builder.go (about)

     1  // The instance package contains a packer.Builder implementation that builds
     2  // AMIs for Amazon EC2 backed by instance storage, as opposed to EBS storage.
     3  package instance
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/aws/aws-sdk-go/service/ec2"
    13  	"github.com/mitchellh/multistep"
    14  	awscommon "github.com/mitchellh/packer/builder/amazon/common"
    15  	"github.com/mitchellh/packer/common"
    16  	"github.com/mitchellh/packer/helper/communicator"
    17  	"github.com/mitchellh/packer/helper/config"
    18  	"github.com/mitchellh/packer/packer"
    19  	"github.com/mitchellh/packer/template/interpolate"
    20  )
    21  
    22  // The unique ID for this builder
    23  const BuilderId = "mitchellh.amazon.instance"
    24  
    25  // Config is the configuration that is chained through the steps and
    26  // settable from the template.
    27  type Config struct {
    28  	common.PackerConfig    `mapstructure:",squash"`
    29  	awscommon.AccessConfig `mapstructure:",squash"`
    30  	awscommon.AMIConfig    `mapstructure:",squash"`
    31  	awscommon.BlockDevices `mapstructure:",squash"`
    32  	awscommon.RunConfig    `mapstructure:",squash"`
    33  
    34  	AccountId           string `mapstructure:"account_id"`
    35  	BundleDestination   string `mapstructure:"bundle_destination"`
    36  	BundlePrefix        string `mapstructure:"bundle_prefix"`
    37  	BundleUploadCommand string `mapstructure:"bundle_upload_command"`
    38  	BundleVolCommand    string `mapstructure:"bundle_vol_command"`
    39  	S3Bucket            string `mapstructure:"s3_bucket"`
    40  	X509CertPath        string `mapstructure:"x509_cert_path"`
    41  	X509KeyPath         string `mapstructure:"x509_key_path"`
    42  	X509UploadPath      string `mapstructure:"x509_upload_path"`
    43  
    44  	ctx interpolate.Context
    45  }
    46  
    47  type Builder struct {
    48  	config Config
    49  	runner multistep.Runner
    50  }
    51  
    52  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    53  	b.config.ctx.Funcs = awscommon.TemplateFuncs
    54  	err := config.Decode(&b.config, &config.DecodeOpts{
    55  		Interpolate:        true,
    56  		InterpolateContext: &b.config.ctx,
    57  		InterpolateFilter: &interpolate.RenderFilter{
    58  			Exclude: []string{
    59  				"bundle_upload_command",
    60  				"bundle_vol_command",
    61  			},
    62  		},
    63  	}, raws...)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	if b.config.BundleDestination == "" {
    69  		b.config.BundleDestination = "/tmp"
    70  	}
    71  
    72  	if b.config.BundlePrefix == "" {
    73  		b.config.BundlePrefix = "image-{{timestamp}}"
    74  	}
    75  
    76  	if b.config.BundleUploadCommand == "" {
    77  		if b.config.IamInstanceProfile != "" {
    78  			b.config.BundleUploadCommand = "sudo -i -n ec2-upload-bundle " +
    79  				"-b {{.BucketName}} " +
    80  				"-m {{.ManifestPath}} " +
    81  				"-d {{.BundleDirectory}} " +
    82  				"--batch " +
    83  				"--region {{.Region}} " +
    84  				"--retry"
    85  		} else {
    86  			b.config.BundleUploadCommand = "sudo -i -n ec2-upload-bundle " +
    87  				"-b {{.BucketName}} " +
    88  				"-m {{.ManifestPath}} " +
    89  				"-a {{.AccessKey}} " +
    90  				"-s {{.SecretKey}} " +
    91  				"-d {{.BundleDirectory}} " +
    92  				"--batch " +
    93  				"--region {{.Region}} " +
    94  				"--retry"
    95  		}
    96  	}
    97  
    98  	if b.config.BundleVolCommand == "" {
    99  		b.config.BundleVolCommand = "sudo -i -n ec2-bundle-vol " +
   100  			"-k {{.KeyPath}} " +
   101  			"-u {{.AccountId}} " +
   102  			"-c {{.CertPath}} " +
   103  			"-r {{.Architecture}} " +
   104  			"-e {{.PrivatePath}}/* " +
   105  			"-d {{.Destination}} " +
   106  			"-p {{.Prefix}} " +
   107  			"--batch " +
   108  			"--no-filter"
   109  	}
   110  
   111  	if b.config.X509UploadPath == "" {
   112  		b.config.X509UploadPath = "/tmp"
   113  	}
   114  
   115  	// Accumulate any errors
   116  	var errs *packer.MultiError
   117  	errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
   118  	errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
   119  	errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
   120  	errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
   121  
   122  	if b.config.AccountId == "" {
   123  		errs = packer.MultiErrorAppend(errs, errors.New("account_id is required"))
   124  	} else {
   125  		b.config.AccountId = strings.Replace(b.config.AccountId, "-", "", -1)
   126  	}
   127  
   128  	if b.config.S3Bucket == "" {
   129  		errs = packer.MultiErrorAppend(errs, errors.New("s3_bucket is required"))
   130  	}
   131  
   132  	if b.config.X509CertPath == "" {
   133  		errs = packer.MultiErrorAppend(errs, errors.New("x509_cert_path is required"))
   134  	} else if _, err := os.Stat(b.config.X509CertPath); err != nil {
   135  		errs = packer.MultiErrorAppend(
   136  			errs, fmt.Errorf("x509_cert_path points to bad file: %s", err))
   137  	}
   138  
   139  	if b.config.X509KeyPath == "" {
   140  		errs = packer.MultiErrorAppend(errs, errors.New("x509_key_path is required"))
   141  	} else if _, err := os.Stat(b.config.X509KeyPath); err != nil {
   142  		errs = packer.MultiErrorAppend(
   143  			errs, fmt.Errorf("x509_key_path points to bad file: %s", err))
   144  	}
   145  
   146  	if errs != nil && len(errs.Errors) > 0 {
   147  		return nil, errs
   148  	}
   149  
   150  	log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
   151  	return nil, nil
   152  }
   153  
   154  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   155  	config, err := b.config.Config()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	ec2conn := ec2.New(config)
   161  
   162  	// Setup the state bag and initial state for the steps
   163  	state := new(multistep.BasicStateBag)
   164  	state.Put("config", &b.config)
   165  	state.Put("ec2", ec2conn)
   166  	state.Put("hook", hook)
   167  	state.Put("ui", ui)
   168  
   169  	// Build the steps
   170  	steps := []multistep.Step{
   171  		&awscommon.StepPreValidate{
   172  			DestAmiName:     b.config.AMIName,
   173  			ForceDeregister: b.config.AMIForceDeregister,
   174  		},
   175  		&awscommon.StepSourceAMIInfo{
   176  			SourceAmi:          b.config.SourceAmi,
   177  			EnhancedNetworking: b.config.AMIEnhancedNetworking,
   178  		},
   179  		&awscommon.StepKeyPair{
   180  			Debug:                b.config.PackerDebug,
   181  			DebugKeyPath:         fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
   182  			KeyPairName:          b.config.SSHKeyPairName,
   183  			PrivateKeyFile:       b.config.RunConfig.Comm.SSHPrivateKey,
   184  			TemporaryKeyPairName: b.config.TemporaryKeyPairName,
   185  		},
   186  		&awscommon.StepSecurityGroup{
   187  			CommConfig:       &b.config.RunConfig.Comm,
   188  			SecurityGroupIds: b.config.SecurityGroupIds,
   189  			VpcId:            b.config.VpcId,
   190  		},
   191  		&awscommon.StepRunSourceInstance{
   192  			Debug:                    b.config.PackerDebug,
   193  			SpotPrice:                b.config.SpotPrice,
   194  			SpotPriceProduct:         b.config.SpotPriceAutoProduct,
   195  			InstanceType:             b.config.InstanceType,
   196  			IamInstanceProfile:       b.config.IamInstanceProfile,
   197  			UserData:                 b.config.UserData,
   198  			UserDataFile:             b.config.UserDataFile,
   199  			SourceAMI:                b.config.SourceAmi,
   200  			SubnetId:                 b.config.SubnetId,
   201  			AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
   202  			AvailabilityZone:         b.config.AvailabilityZone,
   203  			BlockDevices:             b.config.BlockDevices,
   204  			Tags:                     b.config.RunTags,
   205  		},
   206  		&awscommon.StepGetPassword{
   207  			Comm:    &b.config.RunConfig.Comm,
   208  			Timeout: b.config.WindowsPasswordTimeout,
   209  		},
   210  		&communicator.StepConnect{
   211  			Config: &b.config.RunConfig.Comm,
   212  			Host: awscommon.SSHHost(
   213  				ec2conn,
   214  				b.config.SSHPrivateIp),
   215  			SSHConfig: awscommon.SSHConfig(
   216  				b.config.RunConfig.Comm.SSHUsername),
   217  		},
   218  		&common.StepProvision{},
   219  		&StepUploadX509Cert{},
   220  		&StepBundleVolume{
   221  			Debug: b.config.PackerDebug,
   222  		},
   223  		&StepUploadBundle{
   224  			Debug: b.config.PackerDebug,
   225  		},
   226  		&awscommon.StepDeregisterAMI{
   227  			ForceDeregister: b.config.AMIForceDeregister,
   228  			AMIName:         b.config.AMIName,
   229  		},
   230  		&StepRegisterAMI{},
   231  		&awscommon.StepAMIRegionCopy{
   232  			AccessConfig: &b.config.AccessConfig,
   233  			Regions:      b.config.AMIRegions,
   234  			Name:         b.config.AMIName,
   235  		},
   236  		&awscommon.StepModifyAMIAttributes{
   237  			Description:  b.config.AMIDescription,
   238  			Users:        b.config.AMIUsers,
   239  			Groups:       b.config.AMIGroups,
   240  			ProductCodes: b.config.AMIProductCodes,
   241  		},
   242  		&awscommon.StepCreateTags{
   243  			Tags: b.config.AMITags,
   244  		},
   245  	}
   246  
   247  	// Run!
   248  	if b.config.PackerDebug {
   249  		b.runner = &multistep.DebugRunner{
   250  			Steps:   steps,
   251  			PauseFn: common.MultistepDebugFn(ui),
   252  		}
   253  	} else {
   254  		b.runner = &multistep.BasicRunner{Steps: steps}
   255  	}
   256  
   257  	b.runner.Run(state)
   258  
   259  	// If there was an error, return that
   260  	if rawErr, ok := state.GetOk("error"); ok {
   261  		return nil, rawErr.(error)
   262  	}
   263  
   264  	// If there are no AMIs, then just return
   265  	if _, ok := state.GetOk("amis"); !ok {
   266  		return nil, nil
   267  	}
   268  
   269  	// Build the artifact and return it
   270  	artifact := &awscommon.Artifact{
   271  		Amis:           state.Get("amis").(map[string]string),
   272  		BuilderIdValue: BuilderId,
   273  		Conn:           ec2conn,
   274  	}
   275  
   276  	return artifact, nil
   277  }
   278  
   279  func (b *Builder) Cancel() {
   280  	if b.runner != nil {
   281  		log.Println("Cancelling the step runner...")
   282  		b.runner.Cancel()
   283  	}
   284  }