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  }