github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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 awscommon "github.com/hashicorp/packer/builder/amazon/common" 14 "github.com/hashicorp/packer/common" 15 "github.com/hashicorp/packer/helper/communicator" 16 "github.com/hashicorp/packer/helper/config" 17 "github.com/hashicorp/packer/helper/multistep" 18 "github.com/hashicorp/packer/packer" 19 "github.com/hashicorp/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 configs := make([]interface{}, len(raws)+1) 54 configs[0] = map[string]interface{}{ 55 "bundle_prefix": "image-{{timestamp}}", 56 } 57 copy(configs[1:], raws) 58 59 b.config.ctx.Funcs = awscommon.TemplateFuncs 60 err := config.Decode(&b.config, &config.DecodeOpts{ 61 Interpolate: true, 62 InterpolateContext: &b.config.ctx, 63 InterpolateFilter: &interpolate.RenderFilter{ 64 Exclude: []string{ 65 "ami_description", 66 "bundle_upload_command", 67 "bundle_vol_command", 68 "run_tags", 69 "run_volume_tags", 70 "snapshot_tags", 71 "tags", 72 "spot_tags", 73 }, 74 }, 75 }, configs...) 76 if err != nil { 77 return nil, err 78 } 79 80 if b.config.PackerConfig.PackerForce { 81 b.config.AMIForceDeregister = true 82 } 83 84 if b.config.BundleDestination == "" { 85 b.config.BundleDestination = "/tmp" 86 } 87 88 if b.config.BundleUploadCommand == "" { 89 if b.config.IamInstanceProfile != "" { 90 b.config.BundleUploadCommand = "sudo -i -n ec2-upload-bundle " + 91 "-b {{.BucketName}} " + 92 "-m {{.ManifestPath}} " + 93 "-d {{.BundleDirectory}} " + 94 "--batch " + 95 "--region {{.Region}} " + 96 "--retry" 97 } else { 98 b.config.BundleUploadCommand = "sudo -i -n ec2-upload-bundle " + 99 "-b {{.BucketName}} " + 100 "-m {{.ManifestPath}} " + 101 "-a {{.AccessKey}} " + 102 "-s {{.SecretKey}} " + 103 "-d {{.BundleDirectory}} " + 104 "--batch " + 105 "--region {{.Region}} " + 106 "--retry" 107 } 108 } 109 110 if b.config.BundleVolCommand == "" { 111 b.config.BundleVolCommand = "sudo -i -n ec2-bundle-vol " + 112 "-k {{.KeyPath}} " + 113 "-u {{.AccountId}} " + 114 "-c {{.CertPath}} " + 115 "-r {{.Architecture}} " + 116 "-e {{.PrivatePath}}/* " + 117 "-d {{.Destination}} " + 118 "-p {{.Prefix}} " + 119 "--batch " + 120 "--no-filter" 121 } 122 123 if b.config.X509UploadPath == "" { 124 b.config.X509UploadPath = "/tmp" 125 } 126 127 // Accumulate any errors 128 var errs *packer.MultiError 129 errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) 130 errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...) 131 errs = packer.MultiErrorAppend(errs, 132 b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...) 133 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 134 135 if b.config.AccountId == "" { 136 errs = packer.MultiErrorAppend(errs, errors.New("account_id is required")) 137 } else { 138 b.config.AccountId = strings.Replace(b.config.AccountId, "-", "", -1) 139 } 140 141 if b.config.S3Bucket == "" { 142 errs = packer.MultiErrorAppend(errs, errors.New("s3_bucket is required")) 143 } 144 145 if b.config.X509CertPath == "" { 146 errs = packer.MultiErrorAppend(errs, errors.New("x509_cert_path is required")) 147 } else if _, err := os.Stat(b.config.X509CertPath); err != nil { 148 errs = packer.MultiErrorAppend( 149 errs, fmt.Errorf("x509_cert_path points to bad file: %s", err)) 150 } 151 152 if b.config.X509KeyPath == "" { 153 errs = packer.MultiErrorAppend(errs, errors.New("x509_key_path is required")) 154 } else if _, err := os.Stat(b.config.X509KeyPath); err != nil { 155 errs = packer.MultiErrorAppend( 156 errs, fmt.Errorf("x509_key_path points to bad file: %s", err)) 157 } 158 159 if b.config.IsSpotInstance() && (b.config.AMIENASupport || b.config.AMISriovNetSupport) { 160 errs = packer.MultiErrorAppend(errs, 161 fmt.Errorf("Spot instances do not support modification, which is required "+ 162 "when either `ena_support` or `sriov_support` are set. Please ensure "+ 163 "you use an AMI that already has either SR-IOV or ENA enabled.")) 164 } 165 166 if errs != nil && len(errs.Errors) > 0 { 167 return nil, errs 168 } 169 170 log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token)) 171 return nil, nil 172 } 173 174 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 175 session, err := b.config.Session() 176 if err != nil { 177 return nil, err 178 } 179 ec2conn := ec2.New(session) 180 181 // If the subnet is specified but not the VpcId or AZ, try to determine them automatically 182 if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") { 183 log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId) 184 resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}}) 185 if err != nil { 186 return nil, err 187 } 188 if b.config.AvailabilityZone == "" { 189 b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone 190 log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone) 191 } 192 if b.config.VpcId == "" { 193 b.config.VpcId = *resp.Subnets[0].VpcId 194 log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId) 195 } 196 } 197 198 // Setup the state bag and initial state for the steps 199 state := new(multistep.BasicStateBag) 200 state.Put("config", &b.config) 201 state.Put("ec2", ec2conn) 202 state.Put("awsSession", session) 203 state.Put("hook", hook) 204 state.Put("ui", ui) 205 206 var instanceStep multistep.Step 207 208 if b.config.IsSpotInstance() { 209 instanceStep = &awscommon.StepRunSpotInstance{ 210 AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 211 AvailabilityZone: b.config.AvailabilityZone, 212 BlockDevices: b.config.BlockDevices, 213 Ctx: b.config.ctx, 214 Debug: b.config.PackerDebug, 215 EbsOptimized: b.config.EbsOptimized, 216 IamInstanceProfile: b.config.IamInstanceProfile, 217 InstanceType: b.config.InstanceType, 218 SourceAMI: b.config.SourceAmi, 219 SpotPrice: b.config.SpotPrice, 220 SpotPriceProduct: b.config.SpotPriceAutoProduct, 221 SubnetId: b.config.SubnetId, 222 Tags: b.config.RunTags, 223 SpotTags: b.config.SpotTags, 224 UserData: b.config.UserData, 225 UserDataFile: b.config.UserDataFile, 226 } 227 } else { 228 instanceStep = &awscommon.StepRunSourceInstance{ 229 AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 230 AvailabilityZone: b.config.AvailabilityZone, 231 BlockDevices: b.config.BlockDevices, 232 Ctx: b.config.ctx, 233 Debug: b.config.PackerDebug, 234 EbsOptimized: b.config.EbsOptimized, 235 EnableT2Unlimited: b.config.EnableT2Unlimited, 236 IamInstanceProfile: b.config.IamInstanceProfile, 237 InstanceType: b.config.InstanceType, 238 IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), 239 SourceAMI: b.config.SourceAmi, 240 SubnetId: b.config.SubnetId, 241 Tags: b.config.RunTags, 242 UserData: b.config.UserData, 243 UserDataFile: b.config.UserDataFile, 244 } 245 } 246 247 // Build the steps 248 steps := []multistep.Step{ 249 &awscommon.StepPreValidate{ 250 DestAmiName: b.config.AMIName, 251 ForceDeregister: b.config.AMIForceDeregister, 252 }, 253 &awscommon.StepSourceAMIInfo{ 254 SourceAmi: b.config.SourceAmi, 255 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 256 EnableAMIENASupport: b.config.AMIENASupport, 257 AmiFilters: b.config.SourceAmiFilter, 258 }, 259 &awscommon.StepKeyPair{ 260 Debug: b.config.PackerDebug, 261 SSHAgentAuth: b.config.Comm.SSHAgentAuth, 262 DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), 263 KeyPairName: b.config.SSHKeyPairName, 264 PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, 265 TemporaryKeyPairName: b.config.TemporaryKeyPairName, 266 }, 267 &awscommon.StepSecurityGroup{ 268 CommConfig: &b.config.RunConfig.Comm, 269 SecurityGroupIds: b.config.SecurityGroupIds, 270 VpcId: b.config.VpcId, 271 TemporarySGSourceCidr: b.config.TemporarySGSourceCidr, 272 }, 273 instanceStep, 274 &awscommon.StepGetPassword{ 275 Debug: b.config.PackerDebug, 276 Comm: &b.config.RunConfig.Comm, 277 Timeout: b.config.WindowsPasswordTimeout, 278 BuildName: b.config.PackerBuildName, 279 }, 280 &communicator.StepConnect{ 281 Config: &b.config.RunConfig.Comm, 282 Host: awscommon.SSHHost( 283 ec2conn, 284 b.config.SSHInterface), 285 SSHConfig: awscommon.SSHConfig( 286 b.config.RunConfig.Comm.SSHAgentAuth, 287 b.config.RunConfig.Comm.SSHUsername, 288 b.config.RunConfig.Comm.SSHPassword), 289 }, 290 &common.StepProvision{}, 291 &StepUploadX509Cert{}, 292 &StepBundleVolume{ 293 Debug: b.config.PackerDebug, 294 }, 295 &StepUploadBundle{ 296 Debug: b.config.PackerDebug, 297 }, 298 &awscommon.StepDeregisterAMI{ 299 AccessConfig: &b.config.AccessConfig, 300 ForceDeregister: b.config.AMIForceDeregister, 301 ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, 302 AMIName: b.config.AMIName, 303 Regions: b.config.AMIRegions, 304 }, 305 &StepRegisterAMI{ 306 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 307 EnableAMIENASupport: b.config.AMIENASupport, 308 }, 309 &awscommon.StepAMIRegionCopy{ 310 AccessConfig: &b.config.AccessConfig, 311 Regions: b.config.AMIRegions, 312 RegionKeyIds: b.config.AMIRegionKMSKeyIDs, 313 EncryptBootVolume: b.config.AMIEncryptBootVolume, 314 Name: b.config.AMIName, 315 }, 316 &awscommon.StepModifyAMIAttributes{ 317 Description: b.config.AMIDescription, 318 Users: b.config.AMIUsers, 319 Groups: b.config.AMIGroups, 320 ProductCodes: b.config.AMIProductCodes, 321 SnapshotUsers: b.config.SnapshotUsers, 322 SnapshotGroups: b.config.SnapshotGroups, 323 Ctx: b.config.ctx, 324 }, 325 &awscommon.StepCreateTags{ 326 Tags: b.config.AMITags, 327 SnapshotTags: b.config.SnapshotTags, 328 Ctx: b.config.ctx, 329 }, 330 } 331 332 // Run! 333 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 334 b.runner.Run(state) 335 336 // If there was an error, return that 337 if rawErr, ok := state.GetOk("error"); ok { 338 return nil, rawErr.(error) 339 } 340 341 // If there are no AMIs, then just return 342 if _, ok := state.GetOk("amis"); !ok { 343 return nil, nil 344 } 345 346 // Build the artifact and return it 347 artifact := &awscommon.Artifact{ 348 Amis: state.Get("amis").(map[string]string), 349 BuilderIdValue: BuilderId, 350 Session: session, 351 } 352 353 return artifact, nil 354 } 355 356 func (b *Builder) Cancel() { 357 if b.runner != nil { 358 log.Println("Cancelling the step runner...") 359 b.runner.Cancel() 360 } 361 }