github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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/multistep" 16 "github.com/hashicorp/packer/packer" 17 "github.com/hashicorp/packer/template/interpolate" 18 ) 19 20 type StepRunSourceInstance struct { 21 AssociatePublicIpAddress bool 22 AvailabilityZone string 23 BlockDevices BlockDevices 24 Ctx interpolate.Context 25 Debug bool 26 EbsOptimized bool 27 EnableT2Unlimited bool 28 ExpectedRootDevice string 29 IamInstanceProfile string 30 InstanceInitiatedShutdownBehavior string 31 InstanceType string 32 IsRestricted bool 33 SourceAMI string 34 SubnetId 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 var keyName string 46 if name, ok := state.GetOk("keyPair"); ok { 47 keyName = name.(string) 48 } 49 securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string)) 50 ui := state.Get("ui").(packer.Ui) 51 52 userData := s.UserData 53 if s.UserDataFile != "" { 54 contents, err := ioutil.ReadFile(s.UserDataFile) 55 if err != nil { 56 state.Put("error", fmt.Errorf("Problem reading user data file: %s", err)) 57 return multistep.ActionHalt 58 } 59 60 userData = string(contents) 61 } 62 63 // Test if it is encoded already, and if not, encode it 64 if _, err := base64.StdEncoding.DecodeString(userData); err != nil { 65 log.Printf("[DEBUG] base64 encoding user data...") 66 userData = base64.StdEncoding.EncodeToString([]byte(userData)) 67 } 68 69 ui.Say("Launching a source AWS instance...") 70 image, ok := state.Get("source_image").(*ec2.Image) 71 if !ok { 72 state.Put("error", fmt.Errorf("source_image type assertion failed")) 73 return multistep.ActionHalt 74 } 75 s.SourceAMI = *image.ImageId 76 77 if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice { 78 state.Put("error", fmt.Errorf( 79 "The provided source AMI has an invalid root device type.\n"+ 80 "Expected '%s', got '%s'.", 81 s.ExpectedRootDevice, *image.RootDeviceType)) 82 return multistep.ActionHalt 83 } 84 85 var instanceId string 86 87 ui.Say("Adding tags to source instance") 88 if _, exists := s.Tags["Name"]; !exists { 89 s.Tags["Name"] = "Packer Builder" 90 } 91 92 ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state) 93 if err != nil { 94 err := fmt.Errorf("Error tagging source instance: %s", err) 95 state.Put("error", err) 96 ui.Error(err.Error()) 97 return multistep.ActionHalt 98 } 99 100 volTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state) 101 if err != nil { 102 err := fmt.Errorf("Error tagging volumes: %s", err) 103 state.Put("error", err) 104 ui.Error(err.Error()) 105 return multistep.ActionHalt 106 } 107 108 runOpts := &ec2.RunInstancesInput{ 109 ImageId: &s.SourceAMI, 110 InstanceType: &s.InstanceType, 111 UserData: &userData, 112 MaxCount: aws.Int64(1), 113 MinCount: aws.Int64(1), 114 IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, 115 BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), 116 Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone}, 117 EbsOptimized: &s.EbsOptimized, 118 } 119 120 if s.EnableT2Unlimited { 121 creditOption := "unlimited" 122 runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption} 123 } 124 125 // Collect tags for tagging on resource creation 126 var tagSpecs []*ec2.TagSpecification 127 128 if len(ec2Tags) > 0 { 129 runTags := &ec2.TagSpecification{ 130 ResourceType: aws.String("instance"), 131 Tags: ec2Tags, 132 } 133 134 tagSpecs = append(tagSpecs, runTags) 135 } 136 137 if len(volTags) > 0 { 138 runVolTags := &ec2.TagSpecification{ 139 ResourceType: aws.String("volume"), 140 Tags: volTags, 141 } 142 143 tagSpecs = append(tagSpecs, runVolTags) 144 } 145 146 // If our region supports it, set tag specifications 147 if len(tagSpecs) > 0 && !s.IsRestricted { 148 runOpts.SetTagSpecifications(tagSpecs) 149 ec2Tags.Report(ui) 150 volTags.Report(ui) 151 } 152 153 if keyName != "" { 154 runOpts.KeyName = &keyName 155 } 156 157 if s.SubnetId != "" && s.AssociatePublicIpAddress { 158 runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ 159 { 160 DeviceIndex: aws.Int64(0), 161 AssociatePublicIpAddress: &s.AssociatePublicIpAddress, 162 SubnetId: &s.SubnetId, 163 Groups: securityGroupIds, 164 DeleteOnTermination: aws.Bool(true), 165 }, 166 } 167 } else { 168 runOpts.SubnetId = &s.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 }