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