github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/post-processor/amazon-import/post-processor.go (about)

     1  package amazonimport
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/service/ec2"
    11  	"github.com/aws/aws-sdk-go/service/s3"
    12  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    13  	awscommon "github.com/hashicorp/packer/builder/amazon/common"
    14  	"github.com/hashicorp/packer/common"
    15  	"github.com/hashicorp/packer/helper/config"
    16  	"github.com/hashicorp/packer/packer"
    17  	"github.com/hashicorp/packer/template/interpolate"
    18  )
    19  
    20  const BuilderId = "packer.post-processor.amazon-import"
    21  
    22  // Configuration of this post processor
    23  type Config struct {
    24  	common.PackerConfig    `mapstructure:",squash"`
    25  	awscommon.AccessConfig `mapstructure:",squash"`
    26  
    27  	// Variables specific to this post processor
    28  	S3Bucket    string            `mapstructure:"s3_bucket_name"`
    29  	S3Key       string            `mapstructure:"s3_key_name"`
    30  	SkipClean   bool              `mapstructure:"skip_clean"`
    31  	Tags        map[string]string `mapstructure:"tags"`
    32  	Name        string            `mapstructure:"ami_name"`
    33  	Description string            `mapstructure:"ami_description"`
    34  	Users       []string          `mapstructure:"ami_users"`
    35  	Groups      []string          `mapstructure:"ami_groups"`
    36  	LicenseType string            `mapstructure:"license_type"`
    37  	RoleName    string            `mapstructure:"role_name"`
    38  
    39  	ctx interpolate.Context
    40  }
    41  
    42  type PostProcessor struct {
    43  	config Config
    44  }
    45  
    46  // Entry point for configuration parsing when we've defined
    47  func (p *PostProcessor) Configure(raws ...interface{}) error {
    48  	p.config.ctx.Funcs = awscommon.TemplateFuncs
    49  	err := config.Decode(&p.config, &config.DecodeOpts{
    50  		Interpolate:        true,
    51  		InterpolateContext: &p.config.ctx,
    52  		InterpolateFilter: &interpolate.RenderFilter{
    53  			Exclude: []string{
    54  				"s3_key_name",
    55  			},
    56  		},
    57  	}, raws...)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// Set defaults
    63  	if p.config.S3Key == "" {
    64  		p.config.S3Key = "packer-import-{{timestamp}}.ova"
    65  	}
    66  
    67  	errs := new(packer.MultiError)
    68  
    69  	// Check and render s3_key_name
    70  	if err = interpolate.Validate(p.config.S3Key, &p.config.ctx); err != nil {
    71  		errs = packer.MultiErrorAppend(
    72  			errs, fmt.Errorf("Error parsing s3_key_name template: %s", err))
    73  	}
    74  
    75  	// Check we have AWS access variables defined somewhere
    76  	errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...)
    77  
    78  	// define all our required parameters
    79  	templates := map[string]*string{
    80  		"s3_bucket_name": &p.config.S3Bucket,
    81  	}
    82  	// Check out required params are defined
    83  	for key, ptr := range templates {
    84  		if *ptr == "" {
    85  			errs = packer.MultiErrorAppend(
    86  				errs, fmt.Errorf("%s must be set", key))
    87  		}
    88  	}
    89  
    90  	// Anything which flagged return back up the stack
    91  	if len(errs.Errors) > 0 {
    92  		return errs
    93  	}
    94  
    95  	log.Println(common.ScrubConfig(p.config, p.config.AccessKey, p.config.SecretKey, p.config.Token))
    96  	return nil
    97  }
    98  
    99  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
   100  	var err error
   101  
   102  	session, err := p.config.Session()
   103  	if err != nil {
   104  		return nil, false, err
   105  	}
   106  	config := session.Config
   107  
   108  	// Render this key since we didn't in the configure phase
   109  	p.config.S3Key, err = interpolate.Render(p.config.S3Key, &p.config.ctx)
   110  	if err != nil {
   111  		return nil, false, fmt.Errorf("Error rendering s3_key_name template: %s", err)
   112  	}
   113  	log.Printf("Rendered s3_key_name as %s", p.config.S3Key)
   114  
   115  	log.Println("Looking for OVA in artifact")
   116  	// Locate the files output from the builder
   117  	source := ""
   118  	for _, path := range artifact.Files() {
   119  		if strings.HasSuffix(path, ".ova") {
   120  			source = path
   121  			break
   122  		}
   123  	}
   124  
   125  	// Hope we found something useful
   126  	if source == "" {
   127  		return nil, false, fmt.Errorf("No OVA file found in artifact from builder")
   128  	}
   129  
   130  	// open the source file
   131  	log.Printf("Opening file %s to upload", source)
   132  	file, err := os.Open(source)
   133  	if err != nil {
   134  		return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
   135  	}
   136  
   137  	ui.Message(fmt.Sprintf("Uploading %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   138  
   139  	// Copy the OVA file into the S3 bucket specified
   140  	uploader := s3manager.NewUploader(session)
   141  	_, err = uploader.Upload(&s3manager.UploadInput{
   142  		Body:   file,
   143  		Bucket: &p.config.S3Bucket,
   144  		Key:    &p.config.S3Key,
   145  	})
   146  	if err != nil {
   147  		return nil, false, fmt.Errorf("Failed to upload %s: %s", source, err)
   148  	}
   149  
   150  	// May as well stop holding this open now
   151  	file.Close()
   152  
   153  	ui.Message(fmt.Sprintf("Completed upload of %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   154  
   155  	// Call EC2 image import process
   156  	log.Printf("Calling EC2 to import from s3://%s/%s", p.config.S3Bucket, p.config.S3Key)
   157  
   158  	ec2conn := ec2.New(session)
   159  	params := &ec2.ImportImageInput{
   160  		DiskContainers: []*ec2.ImageDiskContainer{
   161  			{
   162  				UserBucket: &ec2.UserBucket{
   163  					S3Bucket: &p.config.S3Bucket,
   164  					S3Key:    &p.config.S3Key,
   165  				},
   166  			},
   167  		},
   168  	}
   169  
   170  	if p.config.RoleName != "" {
   171  		params.SetRoleName(p.config.RoleName)
   172  	}
   173  
   174  	if p.config.LicenseType != "" {
   175  		ui.Message(fmt.Sprintf("Setting license type to '%s'", p.config.LicenseType))
   176  		params.LicenseType = &p.config.LicenseType
   177  	}
   178  
   179  	import_start, err := ec2conn.ImportImage(params)
   180  
   181  	if err != nil {
   182  		return nil, false, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   183  	}
   184  
   185  	ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *import_start.ImportTaskId))
   186  
   187  	// Wait for import process to complete, this takes a while
   188  	ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId))
   189  	err = awscommon.WaitUntilImageImported(aws.BackgroundContext(), ec2conn, *import_start.ImportTaskId)
   190  	if err != nil {
   191  		return nil, false, fmt.Errorf("Import task %s failed with error: %s", *import_start.ImportTaskId, err)
   192  	}
   193  
   194  	// Retrieve what the outcome was for the import task
   195  	import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
   196  		ImportTaskIds: []*string{
   197  			import_start.ImportTaskId,
   198  		},
   199  	})
   200  
   201  	if err != nil {
   202  		return nil, false, fmt.Errorf("Failed to find import task %s: %s", *import_start.ImportTaskId, err)
   203  	}
   204  
   205  	// Check it was actually completed
   206  	if *import_result.ImportImageTasks[0].Status != "completed" {
   207  		// The most useful error message is from the job itself
   208  		return nil, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage)
   209  	}
   210  
   211  	ui.Message(fmt.Sprintf("Import task %s complete", *import_start.ImportTaskId))
   212  
   213  	// Pull AMI ID out of the completed job
   214  	createdami := *import_result.ImportImageTasks[0].ImageId
   215  
   216  	if p.config.Name != "" {
   217  
   218  		ui.Message(fmt.Sprintf("Starting rename of AMI (%s)", createdami))
   219  
   220  		resp, err := ec2conn.CopyImage(&ec2.CopyImageInput{
   221  			Name:          &p.config.Name,
   222  			SourceImageId: &createdami,
   223  			SourceRegion:  config.Region,
   224  		})
   225  
   226  		if err != nil {
   227  			return nil, false, fmt.Errorf("Error Copying AMI (%s): %s", createdami, err)
   228  		}
   229  
   230  		ui.Message(fmt.Sprintf("Waiting for AMI rename to complete (may take a while)"))
   231  
   232  		if err := awscommon.WaitUntilAMIAvailable(aws.BackgroundContext(), ec2conn, *resp.ImageId); err != nil {
   233  			return nil, false, fmt.Errorf("Error waiting for AMI (%s): %s", *resp.ImageId, err)
   234  		}
   235  
   236  		_, err = ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
   237  			ImageId: &createdami,
   238  		})
   239  
   240  		if err != nil {
   241  			return nil, false, fmt.Errorf("Error deregistering existing AMI: %s", err)
   242  		}
   243  
   244  		ui.Message(fmt.Sprintf("AMI rename completed"))
   245  
   246  		createdami = *resp.ImageId
   247  	}
   248  
   249  	// If we have tags, then apply them now to both the AMI and snaps
   250  	// created by the import
   251  	if len(p.config.Tags) > 0 {
   252  		var ec2Tags []*ec2.Tag
   253  
   254  		log.Printf("Repacking tags into AWS format")
   255  
   256  		for key, value := range p.config.Tags {
   257  			ui.Message(fmt.Sprintf("Adding tag \"%s\": \"%s\"", key, value))
   258  			ec2Tags = append(ec2Tags, &ec2.Tag{
   259  				Key:   aws.String(key),
   260  				Value: aws.String(value),
   261  			})
   262  		}
   263  
   264  		resourceIds := []*string{&createdami}
   265  
   266  		log.Printf("Getting details of %s", createdami)
   267  
   268  		imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
   269  			ImageIds: resourceIds,
   270  		})
   271  
   272  		if err != nil {
   273  			return nil, false, fmt.Errorf("Failed to retrieve details for AMI %s: %s", createdami, err)
   274  		}
   275  
   276  		if len(imageResp.Images) == 0 {
   277  			return nil, false, fmt.Errorf("AMI %s has no images", createdami)
   278  		}
   279  
   280  		image := imageResp.Images[0]
   281  
   282  		log.Printf("Walking block device mappings for %s to find snapshots", createdami)
   283  
   284  		for _, device := range image.BlockDeviceMappings {
   285  			if device.Ebs != nil && device.Ebs.SnapshotId != nil {
   286  				ui.Message(fmt.Sprintf("Tagging snapshot %s", *device.Ebs.SnapshotId))
   287  				resourceIds = append(resourceIds, device.Ebs.SnapshotId)
   288  			}
   289  		}
   290  
   291  		ui.Message(fmt.Sprintf("Tagging AMI %s", createdami))
   292  
   293  		_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
   294  			Resources: resourceIds,
   295  			Tags:      ec2Tags,
   296  		})
   297  
   298  		if err != nil {
   299  			return nil, false, fmt.Errorf("Failed to add tags to resources %#v: %s", resourceIds, err)
   300  		}
   301  
   302  	}
   303  
   304  	// Apply attributes for AMI specified in config
   305  	// (duped from builder/amazon/common/step_modify_ami_attributes.go)
   306  	options := make(map[string]*ec2.ModifyImageAttributeInput)
   307  	if p.config.Description != "" {
   308  		options["description"] = &ec2.ModifyImageAttributeInput{
   309  			Description: &ec2.AttributeValue{Value: &p.config.Description},
   310  		}
   311  	}
   312  
   313  	if len(p.config.Groups) > 0 {
   314  		groups := make([]*string, len(p.config.Groups))
   315  		adds := make([]*ec2.LaunchPermission, len(p.config.Groups))
   316  		addGroups := &ec2.ModifyImageAttributeInput{
   317  			LaunchPermission: &ec2.LaunchPermissionModifications{},
   318  		}
   319  
   320  		for i, g := range p.config.Groups {
   321  			groups[i] = aws.String(g)
   322  			adds[i] = &ec2.LaunchPermission{
   323  				Group: aws.String(g),
   324  			}
   325  		}
   326  		addGroups.UserGroups = groups
   327  		addGroups.LaunchPermission.Add = adds
   328  
   329  		options["groups"] = addGroups
   330  	}
   331  
   332  	if len(p.config.Users) > 0 {
   333  		users := make([]*string, len(p.config.Users))
   334  		adds := make([]*ec2.LaunchPermission, len(p.config.Users))
   335  		for i, u := range p.config.Users {
   336  			users[i] = aws.String(u)
   337  			adds[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
   338  		}
   339  		options["users"] = &ec2.ModifyImageAttributeInput{
   340  			UserIds: users,
   341  			LaunchPermission: &ec2.LaunchPermissionModifications{
   342  				Add: adds,
   343  			},
   344  		}
   345  	}
   346  
   347  	if len(options) > 0 {
   348  		for name, input := range options {
   349  			ui.Message(fmt.Sprintf("Modifying: %s", name))
   350  			input.ImageId = &createdami
   351  			_, err := ec2conn.ModifyImageAttribute(input)
   352  			if err != nil {
   353  				return nil, false, fmt.Errorf("Error modifying AMI attributes: %s", err)
   354  			}
   355  		}
   356  	}
   357  
   358  	// Add the reported AMI ID to the artifact list
   359  	log.Printf("Adding created AMI ID %s in region %s to output artifacts", createdami, *config.Region)
   360  	artifact = &awscommon.Artifact{
   361  		Amis: map[string]string{
   362  			*config.Region: createdami,
   363  		},
   364  		BuilderIdValue: BuilderId,
   365  		Session:        session,
   366  	}
   367  
   368  	if !p.config.SkipClean {
   369  		ui.Message(fmt.Sprintf("Deleting import source s3://%s/%s", p.config.S3Bucket, p.config.S3Key))
   370  		s3conn := s3.New(session)
   371  		_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
   372  			Bucket: &p.config.S3Bucket,
   373  			Key:    &p.config.S3Key,
   374  		})
   375  		if err != nil {
   376  			return nil, false, fmt.Errorf("Failed to delete s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   377  		}
   378  	}
   379  
   380  	return artifact, false, nil
   381  }