github.phpd.cn/hashicorp/packer@v1.3.2/builder/amazon/common/step_run_source_instance.go (about) 1 package common 2 3 import ( 4 "context" 5 "encoding/base64" 6 "fmt" 7 "io/ioutil" 8 "log" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/ec2" 13 14 retry "github.com/hashicorp/packer/common" 15 "github.com/hashicorp/packer/helper/communicator" 16 "github.com/hashicorp/packer/helper/multistep" 17 "github.com/hashicorp/packer/packer" 18 "github.com/hashicorp/packer/template/interpolate" 19 ) 20 21 type StepRunSourceInstance struct { 22 AssociatePublicIpAddress bool 23 BlockDevices BlockDevices 24 Comm *communicator.Config 25 Ctx interpolate.Context 26 Debug bool 27 EbsOptimized bool 28 EnableT2Unlimited bool 29 ExpectedRootDevice string 30 IamInstanceProfile string 31 InstanceInitiatedShutdownBehavior string 32 InstanceType string 33 IsRestricted bool 34 SourceAMI string 35 Tags TagMap 36 UserData string 37 UserDataFile string 38 VolumeTags TagMap 39 40 instanceId string 41 } 42 43 func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 44 ec2conn := state.Get("ec2").(*ec2.EC2) 45 46 securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string)) 47 ui := state.Get("ui").(packer.Ui) 48 49 userData := s.UserData 50 if s.UserDataFile != "" { 51 contents, err := ioutil.ReadFile(s.UserDataFile) 52 if err != nil { 53 state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) 54 return multistep.ActionHalt 55 } 56 57 userData = string(contents) 58 } 59 60 // Test if it is encoded already, and if not, encode it 61 if _, err := base64.StdEncoding.DecodeString(userData); err != nil { 62 log.Printf("[DEBUG] base64 encoding user data...") 63 userData = base64.StdEncoding.EncodeToString([]byte(userData)) 64 } 65 66 ui.Say("Launching a source AWS instance...") 67 image, ok := state.Get("source_image").(*ec2.Image) 68 if !ok { 69 state.Put("error", fmt.Errorf("source_image type assertion failed")) 70 return multistep.ActionHalt 71 } 72 s.SourceAMI = *image.ImageId 73 74 if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice { 75 state.Put("error", fmt.Errorf( 76 "The provided source AMI has an invalid root device type.\n"+ 77 "Expected '%s', got '%s'.", 78 s.ExpectedRootDevice, *image.RootDeviceType)) 79 return multistep.ActionHalt 80 } 81 82 var instanceId string 83 84 ui.Say("Adding tags to source instance") 85 if _, exists := s.Tags["Name"]; !exists { 86 s.Tags["Name"] = "Packer Builder" 87 } 88 89 ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state) 90 if err != nil { 91 err := fmt.Errorf("Error tagging source instance: %s", err) 92 state.Put("error", err) 93 ui.Error(err.Error()) 94 return multistep.ActionHalt 95 } 96 97 volTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state) 98 if err != nil { 99 err := fmt.Errorf("Error tagging volumes: %s", err) 100 state.Put("error", err) 101 ui.Error(err.Error()) 102 return multistep.ActionHalt 103 } 104 105 az := state.Get("availability_zone").(string) 106 runOpts := &ec2.RunInstancesInput{ 107 ImageId: &s.SourceAMI, 108 InstanceType: &s.InstanceType, 109 UserData: &userData, 110 MaxCount: aws.Int64(1), 111 MinCount: aws.Int64(1), 112 IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 113 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 114 Placement: &ec2.Placement{AvailabilityZone: &az}, 115 EbsOptimized: &s.EbsOptimized, 116 } 117 118 if s.EnableT2Unlimited { 119 creditOption := "unlimited" 120 runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption} 121 } 122 123 // Collect tags for tagging on resource creation 124 var tagSpecs []*ec2.TagSpecification 125 126 if len(ec2Tags) > 0 { 127 runTags := &ec2.TagSpecification{ 128 ResourceType: aws.String("instance"), 129 Tags: ec2Tags, 130 } 131 132 tagSpecs = append(tagSpecs, runTags) 133 } 134 135 if len(volTags) > 0 { 136 runVolTags := &ec2.TagSpecification{ 137 ResourceType: aws.String("volume"), 138 Tags: volTags, 139 } 140 141 tagSpecs = append(tagSpecs, runVolTags) 142 } 143 144 // If our region supports it, set tag specifications 145 if len(tagSpecs) > 0 && !s.IsRestricted { 146 runOpts.SetTagSpecifications(tagSpecs) 147 ec2Tags.Report(ui) 148 volTags.Report(ui) 149 } 150 151 if s.Comm.SSHKeyPairName != "" { 152 runOpts.KeyName = &s.Comm.SSHKeyPairName 153 } 154 155 subnetId := state.Get("subnet_id").(string) 156 157 if subnetId != "" && s.AssociatePublicIpAddress { 158 runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ 159 { 160 DeviceIndex: aws.Int64(0), 161 AssociatePublicIpAddress: &s.AssociatePublicIpAddress, 162 SubnetId: aws.String(subnetId), 163 Groups: securityGroupIds, 164 DeleteOnTermination: aws.Bool(true), 165 }, 166 } 167 } else { 168 runOpts.SubnetId = aws.String(subnetId) 169 runOpts.SecurityGroupIds = securityGroupIds 170 } 171 172 if s.ExpectedRootDevice == "ebs" { 173 runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior 174 } 175 176 runResp, err := ec2conn.RunInstances(runOpts) 177 if err != nil { 178 err := fmt.Errorf("Error launching source instance: %s", err) 179 state.Put("error", err) 180 ui.Error(err.Error()) 181 return multistep.ActionHalt 182 } 183 instanceId = *runResp.Instances[0].InstanceId 184 185 // Set the instance ID so that the cleanup works properly 186 s.instanceId = instanceId 187 188 ui.Message(fmt.Sprintf("Instance ID: %s", instanceId)) 189 ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId)) 190 191 describeInstance := &ec2.DescribeInstancesInput{ 192 InstanceIds: []*string{aws.String(instanceId)}, 193 } 194 if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil { 195 err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) 196 state.Put("error", err) 197 ui.Error(err.Error()) 198 return multistep.ActionHalt 199 } 200 201 r, err := ec2conn.DescribeInstances(describeInstance) 202 203 if err != nil || len(r.Reservations) == 0 || len(r.Reservations[0].Instances) == 0 { 204 err := fmt.Errorf("Error finding source instance.") 205 state.Put("error", err) 206 ui.Error(err.Error()) 207 return multistep.ActionHalt 208 } 209 instance := r.Reservations[0].Instances[0] 210 211 if s.Debug { 212 if instance.PublicDnsName != nil && *instance.PublicDnsName != "" { 213 ui.Message(fmt.Sprintf("Public DNS: %s", *instance.PublicDnsName)) 214 } 215 216 if instance.PublicIpAddress != nil && *instance.PublicIpAddress != "" { 217 ui.Message(fmt.Sprintf("Public IP: %s", *instance.PublicIpAddress)) 218 } 219 220 if instance.PrivateIpAddress != nil && *instance.PrivateIpAddress != "" { 221 ui.Message(fmt.Sprintf("Private IP: %s", *instance.PrivateIpAddress)) 222 } 223 } 224 225 state.Put("instance", instance) 226 227 // If we're in a region that doesn't support tagging on instance creation, 228 // do that now. 229 230 if s.IsRestricted { 231 ec2Tags.Report(ui) 232 // Retry creating tags for about 2.5 minutes 233 err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) { 234 _, err := ec2conn.CreateTags(&ec2.CreateTagsInput{ 235 Tags: ec2Tags, 236 Resources: []*string{instance.InstanceId}, 237 }) 238 if err == nil { 239 return true, nil 240 } 241 if awsErr, ok := err.(awserr.Error); ok { 242 if awsErr.Code() == "InvalidInstanceID.NotFound" { 243 return false, nil 244 } 245 } 246 return true, err 247 }) 248 249 if err != nil { 250 err := fmt.Errorf("Error tagging source instance: %s", err) 251 state.Put("error", err) 252 ui.Error(err.Error()) 253 return multistep.ActionHalt 254 } 255 256 // Now tag volumes 257 258 volumeIds := make([]*string, 0) 259 for _, v := range instance.BlockDeviceMappings { 260 if ebs := v.Ebs; ebs != nil { 261 volumeIds = append(volumeIds, ebs.VolumeId) 262 } 263 } 264 265 if len(volumeIds) > 0 && s.VolumeTags.IsSet() { 266 ui.Say("Adding tags to source EBS Volumes") 267 268 volumeTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state) 269 if err != nil { 270 err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err) 271 state.Put("error", err) 272 ui.Error(err.Error()) 273 return multistep.ActionHalt 274 } 275 volumeTags.Report(ui) 276 277 _, err = ec2conn.CreateTags(&ec2.CreateTagsInput{ 278 Resources: volumeIds, 279 Tags: volumeTags, 280 }) 281 282 if err != nil { 283 err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err) 284 state.Put("error", err) 285 ui.Error(err.Error()) 286 return multistep.ActionHalt 287 } 288 } 289 } 290 291 return multistep.ActionContinue 292 } 293 294 func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) { 295 296 ec2conn := state.Get("ec2").(*ec2.EC2) 297 ui := state.Get("ui").(packer.Ui) 298 299 // Terminate the source instance if it exists 300 if s.instanceId != "" { 301 ui.Say("Terminating the source AWS instance...") 302 if _, err := ec2conn.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIds: []*string{&s.instanceId}}); err != nil { 303 ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err)) 304 return 305 } 306 307 if err := WaitUntilInstanceTerminated(aws.BackgroundContext(), ec2conn, s.instanceId); err != nil { 308 ui.Error(err.Error()) 309 } 310 } 311 }