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