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