github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/builder/amazon/ebs/builder.go (about) 1 // The amazonebs package contains a packer.Builder implementation that 2 // builds AMIs for Amazon EC2. 3 // 4 // In general, there are two types of AMIs that can be created: ebs-backed or 5 // instance-store. This builder _only_ builds ebs-backed images. 6 package ebs 7 8 import ( 9 "fmt" 10 "log" 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.amazonebs" 24 25 type Config struct { 26 common.PackerConfig `mapstructure:",squash"` 27 awscommon.AccessConfig `mapstructure:",squash"` 28 awscommon.AMIConfig `mapstructure:",squash"` 29 awscommon.BlockDevices `mapstructure:",squash"` 30 awscommon.RunConfig `mapstructure:",squash"` 31 VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"` 32 33 ctx interpolate.Context 34 } 35 36 type Builder struct { 37 config Config 38 runner multistep.Runner 39 } 40 41 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 42 b.config.ctx.Funcs = awscommon.TemplateFuncs 43 err := config.Decode(&b.config, &config.DecodeOpts{ 44 Interpolate: true, 45 InterpolateContext: &b.config.ctx, 46 InterpolateFilter: &interpolate.RenderFilter{ 47 Exclude: []string{ 48 "ami_description", 49 "run_tags", 50 "run_volume_tags", 51 "spot_tags", 52 "snapshot_tags", 53 "tags", 54 }, 55 }, 56 }, raws...) 57 if err != nil { 58 return nil, err 59 } 60 61 if b.config.PackerConfig.PackerForce { 62 b.config.AMIForceDeregister = true 63 } 64 65 // Accumulate any errors 66 var errs *packer.MultiError 67 errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) 68 errs = packer.MultiErrorAppend(errs, 69 b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...) 70 errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...) 71 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 72 73 if b.config.IsSpotInstance() && (b.config.AMIENASupport || b.config.AMISriovNetSupport) { 74 errs = packer.MultiErrorAppend(errs, 75 fmt.Errorf("Spot instances do not support modification, which is required "+ 76 "when either `ena_support` or `sriov_support` are set. Please ensure "+ 77 "you use an AMI that already has either SR-IOV or ENA enabled.")) 78 } 79 80 if errs != nil && len(errs.Errors) > 0 { 81 return nil, errs 82 } 83 84 log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey, b.config.Token)) 85 return nil, nil 86 } 87 88 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 89 90 session, err := b.config.Session() 91 if err != nil { 92 return nil, err 93 } 94 ec2conn := ec2.New(session) 95 96 // If the subnet is specified but not the VpcId or AZ, try to determine them automatically 97 if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") { 98 log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId) 99 resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}}) 100 if err != nil { 101 return nil, err 102 } 103 if b.config.AvailabilityZone == "" { 104 b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone 105 log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone) 106 } 107 if b.config.VpcId == "" { 108 b.config.VpcId = *resp.Subnets[0].VpcId 109 log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId) 110 } 111 } 112 113 // Setup the state bag and initial state for the steps 114 state := new(multistep.BasicStateBag) 115 state.Put("config", b.config) 116 state.Put("ec2", ec2conn) 117 state.Put("awsSession", session) 118 state.Put("hook", hook) 119 state.Put("ui", ui) 120 121 var instanceStep multistep.Step 122 123 if b.config.IsSpotInstance() { 124 instanceStep = &awscommon.StepRunSpotInstance{ 125 AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 126 AvailabilityZone: b.config.AvailabilityZone, 127 BlockDevices: b.config.BlockDevices, 128 Ctx: b.config.ctx, 129 Debug: b.config.PackerDebug, 130 EbsOptimized: b.config.EbsOptimized, 131 ExpectedRootDevice: "ebs", 132 IamInstanceProfile: b.config.IamInstanceProfile, 133 InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, 134 InstanceType: b.config.InstanceType, 135 SourceAMI: b.config.SourceAmi, 136 SpotPrice: b.config.SpotPrice, 137 SpotPriceProduct: b.config.SpotPriceAutoProduct, 138 SpotTags: b.config.SpotTags, 139 SubnetId: b.config.SubnetId, 140 Tags: b.config.RunTags, 141 UserData: b.config.UserData, 142 UserDataFile: b.config.UserDataFile, 143 VolumeTags: b.config.VolumeRunTags, 144 } 145 } else { 146 instanceStep = &awscommon.StepRunSourceInstance{ 147 AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 148 AvailabilityZone: b.config.AvailabilityZone, 149 BlockDevices: b.config.BlockDevices, 150 Ctx: b.config.ctx, 151 Debug: b.config.PackerDebug, 152 EbsOptimized: b.config.EbsOptimized, 153 EnableT2Unlimited: b.config.EnableT2Unlimited, 154 ExpectedRootDevice: "ebs", 155 IamInstanceProfile: b.config.IamInstanceProfile, 156 InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, 157 InstanceType: b.config.InstanceType, 158 IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), 159 SourceAMI: b.config.SourceAmi, 160 SubnetId: b.config.SubnetId, 161 Tags: b.config.RunTags, 162 UserData: b.config.UserData, 163 UserDataFile: b.config.UserDataFile, 164 VolumeTags: b.config.VolumeRunTags, 165 } 166 } 167 168 // Build the steps 169 steps := []multistep.Step{ 170 &awscommon.StepPreValidate{ 171 DestAmiName: b.config.AMIName, 172 ForceDeregister: b.config.AMIForceDeregister, 173 }, 174 &awscommon.StepSourceAMIInfo{ 175 SourceAmi: b.config.SourceAmi, 176 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 177 EnableAMIENASupport: b.config.AMIENASupport, 178 AmiFilters: b.config.SourceAmiFilter, 179 }, 180 &awscommon.StepKeyPair{ 181 Debug: b.config.PackerDebug, 182 SSHAgentAuth: b.config.Comm.SSHAgentAuth, 183 DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), 184 KeyPairName: b.config.SSHKeyPairName, 185 TemporaryKeyPairName: b.config.TemporaryKeyPairName, 186 PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, 187 }, 188 &awscommon.StepSecurityGroup{ 189 SecurityGroupIds: b.config.SecurityGroupIds, 190 CommConfig: &b.config.RunConfig.Comm, 191 VpcId: b.config.VpcId, 192 TemporarySGSourceCidr: b.config.TemporarySGSourceCidr, 193 }, 194 &stepCleanupVolumes{ 195 BlockDevices: b.config.BlockDevices, 196 }, 197 instanceStep, 198 &awscommon.StepGetPassword{ 199 Debug: b.config.PackerDebug, 200 Comm: &b.config.RunConfig.Comm, 201 Timeout: b.config.WindowsPasswordTimeout, 202 BuildName: b.config.PackerBuildName, 203 }, 204 &communicator.StepConnect{ 205 Config: &b.config.RunConfig.Comm, 206 Host: awscommon.SSHHost( 207 ec2conn, 208 b.config.SSHInterface), 209 SSHConfig: awscommon.SSHConfig( 210 b.config.RunConfig.Comm.SSHAgentAuth, 211 b.config.RunConfig.Comm.SSHUsername, 212 b.config.RunConfig.Comm.SSHPassword), 213 }, 214 &common.StepProvision{}, 215 &awscommon.StepStopEBSBackedInstance{ 216 Skip: b.config.IsSpotInstance(), 217 DisableStopInstance: b.config.DisableStopInstance, 218 }, 219 &awscommon.StepModifyEBSBackedInstance{ 220 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 221 EnableAMIENASupport: b.config.AMIENASupport, 222 }, 223 &awscommon.StepDeregisterAMI{ 224 AccessConfig: &b.config.AccessConfig, 225 ForceDeregister: b.config.AMIForceDeregister, 226 ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, 227 AMIName: b.config.AMIName, 228 Regions: b.config.AMIRegions, 229 }, 230 &stepCreateAMI{}, 231 &awscommon.StepCreateEncryptedAMICopy{ 232 KeyID: b.config.AMIKmsKeyId, 233 EncryptBootVolume: b.config.AMIEncryptBootVolume, 234 Name: b.config.AMIName, 235 AMIMappings: b.config.AMIBlockDevices.AMIMappings, 236 }, 237 &awscommon.StepAMIRegionCopy{ 238 AccessConfig: &b.config.AccessConfig, 239 Regions: b.config.AMIRegions, 240 RegionKeyIds: b.config.AMIRegionKMSKeyIDs, 241 EncryptBootVolume: b.config.AMIEncryptBootVolume, 242 Name: b.config.AMIName, 243 }, 244 &awscommon.StepModifyAMIAttributes{ 245 Description: b.config.AMIDescription, 246 Users: b.config.AMIUsers, 247 Groups: b.config.AMIGroups, 248 ProductCodes: b.config.AMIProductCodes, 249 SnapshotUsers: b.config.SnapshotUsers, 250 SnapshotGroups: b.config.SnapshotGroups, 251 Ctx: b.config.ctx, 252 }, 253 &awscommon.StepCreateTags{ 254 Tags: b.config.AMITags, 255 SnapshotTags: b.config.SnapshotTags, 256 Ctx: b.config.ctx, 257 }, 258 } 259 260 // Run! 261 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 262 b.runner.Run(state) 263 264 // If there was an error, return that 265 if rawErr, ok := state.GetOk("error"); ok { 266 return nil, rawErr.(error) 267 } 268 269 // If there are no AMIs, then just return 270 if _, ok := state.GetOk("amis"); !ok { 271 return nil, nil 272 } 273 274 // Build the artifact and return it 275 artifact := &awscommon.Artifact{ 276 Amis: state.Get("amis").(map[string]string), 277 BuilderIdValue: BuilderId, 278 Session: session, 279 } 280 281 return artifact, nil 282 } 283 284 func (b *Builder) Cancel() { 285 if b.runner != nil { 286 log.Println("Cancelling the step runner...") 287 b.runner.Cancel() 288 } 289 }