github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/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/packer" 16 "github.com/hashicorp/packer/template/interpolate" 17 "github.com/mitchellh/multistep" 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 map[string]string `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 "tags", 52 }, 53 }, 54 }, raws...) 55 if err != nil { 56 return nil, err 57 } 58 59 if b.config.PackerConfig.PackerForce { 60 b.config.AMIForceDeregister = true 61 } 62 63 // Accumulate any errors 64 var errs *packer.MultiError 65 errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) 66 errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 67 errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...) 68 errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...) 69 errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...) 70 71 if b.config.AMIVirtType == "" { 72 errs = packer.MultiErrorAppend(errs, errors.New("ami_virtualization_type is required.")) 73 } 74 75 foundRootVolume := false 76 for _, launchDevice := range b.config.BlockDevices.LaunchMappings { 77 if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName { 78 foundRootVolume = true 79 } 80 } 81 82 if !foundRootVolume { 83 errs = packer.MultiErrorAppend(errs, fmt.Errorf("no volume with name '%s' is found", b.config.RootDevice.SourceDeviceName)) 84 } 85 86 if errs != nil && len(errs.Errors) > 0 { 87 return nil, errs 88 } 89 90 log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey)) 91 return nil, nil 92 } 93 94 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 95 session, err := b.config.Session() 96 if err != nil { 97 return nil, err 98 } 99 ec2conn := ec2.New(session) 100 101 // If the subnet is specified but not the VpcId or AZ, try to determine them automatically 102 if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") { 103 log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId) 104 resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}}) 105 if err != nil { 106 return nil, err 107 } 108 if b.config.AvailabilityZone == "" { 109 b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone 110 log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone) 111 } 112 if b.config.VpcId == "" { 113 b.config.VpcId = *resp.Subnets[0].VpcId 114 log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId) 115 } 116 } 117 118 // Setup the state bag and initial state for the steps 119 state := new(multistep.BasicStateBag) 120 state.Put("config", &b.config) 121 state.Put("ec2", ec2conn) 122 state.Put("hook", hook) 123 state.Put("ui", ui) 124 125 // Build the steps 126 steps := []multistep.Step{ 127 &awscommon.StepPreValidate{ 128 DestAmiName: b.config.AMIName, 129 ForceDeregister: b.config.AMIForceDeregister, 130 }, 131 &awscommon.StepSourceAMIInfo{ 132 SourceAmi: b.config.SourceAmi, 133 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 134 EnableAMIENASupport: b.config.AMIENASupport, 135 AmiFilters: b.config.SourceAmiFilter, 136 }, 137 &awscommon.StepKeyPair{ 138 Debug: b.config.PackerDebug, 139 SSHAgentAuth: b.config.Comm.SSHAgentAuth, 140 DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), 141 KeyPairName: b.config.SSHKeyPairName, 142 TemporaryKeyPairName: b.config.TemporaryKeyPairName, 143 PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, 144 }, 145 &awscommon.StepSecurityGroup{ 146 SecurityGroupIds: b.config.SecurityGroupIds, 147 CommConfig: &b.config.RunConfig.Comm, 148 VpcId: b.config.VpcId, 149 }, 150 &awscommon.StepRunSourceInstance{ 151 Debug: b.config.PackerDebug, 152 ExpectedRootDevice: "ebs", 153 SpotPrice: b.config.SpotPrice, 154 SpotPriceProduct: b.config.SpotPriceAutoProduct, 155 InstanceType: b.config.InstanceType, 156 UserData: b.config.UserData, 157 UserDataFile: b.config.UserDataFile, 158 SourceAMI: b.config.SourceAmi, 159 IamInstanceProfile: b.config.IamInstanceProfile, 160 SubnetId: b.config.SubnetId, 161 AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 162 EbsOptimized: b.config.EbsOptimized, 163 AvailabilityZone: b.config.AvailabilityZone, 164 BlockDevices: b.config.BlockDevices, 165 Tags: b.config.RunTags, 166 InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, 167 }, 168 &awscommon.StepTagEBSVolumes{ 169 VolumeRunTags: b.config.VolumeRunTags, 170 Ctx: b.config.ctx, 171 }, 172 &awscommon.StepGetPassword{ 173 Debug: b.config.PackerDebug, 174 Comm: &b.config.RunConfig.Comm, 175 Timeout: b.config.WindowsPasswordTimeout, 176 }, 177 &communicator.StepConnect{ 178 Config: &b.config.RunConfig.Comm, 179 Host: awscommon.SSHHost( 180 ec2conn, 181 b.config.SSHPrivateIp), 182 SSHConfig: awscommon.SSHConfig( 183 b.config.RunConfig.Comm.SSHAgentAuth, 184 b.config.RunConfig.Comm.SSHUsername, 185 b.config.RunConfig.Comm.SSHPassword), 186 }, 187 &common.StepProvision{}, 188 &awscommon.StepStopEBSBackedInstance{ 189 SpotPrice: b.config.SpotPrice, 190 DisableStopInstance: b.config.DisableStopInstance, 191 }, 192 &awscommon.StepModifyEBSBackedInstance{ 193 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 194 EnableAMIENASupport: b.config.AMIENASupport, 195 }, 196 &StepSnapshotNewRootVolume{ 197 NewRootMountPoint: b.config.RootDevice.SourceDeviceName, 198 }, 199 &awscommon.StepDeregisterAMI{ 200 AccessConfig: &b.config.AccessConfig, 201 ForceDeregister: b.config.AMIForceDeregister, 202 ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot, 203 AMIName: b.config.AMIName, 204 Regions: b.config.AMIRegions, 205 }, 206 &StepRegisterAMI{ 207 RootDevice: b.config.RootDevice, 208 BlockDevices: b.config.BlockDevices.BuildAMIDevices(), 209 EnableAMISriovNetSupport: b.config.AMISriovNetSupport, 210 EnableAMIENASupport: b.config.AMIENASupport, 211 }, 212 &awscommon.StepCreateEncryptedAMICopy{ 213 KeyID: b.config.AMIKmsKeyId, 214 EncryptBootVolume: b.config.AMIEncryptBootVolume, 215 Name: b.config.AMIName, 216 }, 217 &awscommon.StepAMIRegionCopy{ 218 AccessConfig: &b.config.AccessConfig, 219 Regions: b.config.AMIRegions, 220 RegionKeyIds: b.config.AMIRegionKMSKeyIDs, 221 EncryptBootVolume: b.config.AMIEncryptBootVolume, 222 Name: b.config.AMIName, 223 }, 224 &awscommon.StepModifyAMIAttributes{ 225 Description: b.config.AMIDescription, 226 Users: b.config.AMIUsers, 227 Groups: b.config.AMIGroups, 228 ProductCodes: b.config.AMIProductCodes, 229 SnapshotUsers: b.config.SnapshotUsers, 230 SnapshotGroups: b.config.SnapshotGroups, 231 Ctx: b.config.ctx, 232 }, 233 &awscommon.StepCreateTags{ 234 Tags: b.config.AMITags, 235 SnapshotTags: b.config.SnapshotTags, 236 Ctx: b.config.ctx, 237 }, 238 } 239 240 // Run! 241 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 242 b.runner.Run(state) 243 244 // If there was an error, return that 245 if rawErr, ok := state.GetOk("error"); ok { 246 return nil, rawErr.(error) 247 } 248 249 if amis, ok := state.GetOk("amis"); ok { 250 // Build the artifact and return it 251 artifact := &awscommon.Artifact{ 252 Amis: amis.(map[string]string), 253 BuilderIdValue: BuilderId, 254 Conn: ec2conn, 255 } 256 257 return artifact, nil 258 } 259 260 return nil, nil 261 } 262 263 func (b *Builder) Cancel() { 264 if b.runner != nil { 265 log.Println("Cancelling the step runner...") 266 b.runner.Cancel() 267 } 268 }