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  }