github.com/raghuse92/packer@v1.3.2/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  	packer.LogSecretFilter.Set(p.config.AccessKey, p.config.SecretKey, p.config.Token)
    96  	log.Println(p.config)
    97  	return nil
    98  }
    99  
   100  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
   101  	var err error
   102  
   103  	session, err := p.config.Session()
   104  	if err != nil {
   105  		return nil, false, err
   106  	}
   107  	config := session.Config
   108  
   109  	// Render this key since we didn't in the configure phase
   110  	p.config.S3Key, err = interpolate.Render(p.config.S3Key, &p.config.ctx)
   111  	if err != nil {
   112  		return nil, false, fmt.Errorf("Error rendering s3_key_name template: %s", err)
   113  	}
   114  	log.Printf("Rendered s3_key_name as %s", p.config.S3Key)
   115  
   116  	log.Println("Looking for OVA in artifact")
   117  	// Locate the files output from the builder
   118  	source := ""
   119  	for _, path := range artifact.Files() {
   120  		if strings.HasSuffix(path, ".ova") {
   121  			source = path
   122  			break
   123  		}
   124  	}
   125  
   126  	// Hope we found something useful
   127  	if source == "" {
   128  		return nil, false, fmt.Errorf("No OVA file found in artifact from builder")
   129  	}
   130  
   131  	// open the source file
   132  	log.Printf("Opening file %s to upload", source)
   133  	file, err := os.Open(source)
   134  	if err != nil {
   135  		return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
   136  	}
   137  
   138  	ui.Message(fmt.Sprintf("Uploading %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   139  
   140  	// Copy the OVA file into the S3 bucket specified
   141  	uploader := s3manager.NewUploader(session)
   142  	_, err = uploader.Upload(&s3manager.UploadInput{
   143  		Body:   file,
   144  		Bucket: &p.config.S3Bucket,
   145  		Key:    &p.config.S3Key,
   146  	})
   147  	if err != nil {
   148  		return nil, false, fmt.Errorf("Failed to upload %s: %s", source, err)
   149  	}
   150  
   151  	// May as well stop holding this open now
   152  	file.Close()
   153  
   154  	ui.Message(fmt.Sprintf("Completed upload of %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   155  
   156  	// Call EC2 image import process
   157  	log.Printf("Calling EC2 to import from s3://%s/%s", p.config.S3Bucket, p.config.S3Key)
   158  
   159  	ec2conn := ec2.New(session)
   160  	params := &ec2.ImportImageInput{
   161  		DiskContainers: []*ec2.ImageDiskContainer{
   162  			{
   163  				UserBucket: &ec2.UserBucket{
   164  					S3Bucket: &p.config.S3Bucket,
   165  					S3Key:    &p.config.S3Key,
   166  				},
   167  			},
   168  		},
   169  	}
   170  
   171  	if p.config.RoleName != "" {
   172  		params.SetRoleName(p.config.RoleName)
   173  	}
   174  
   175  	if p.config.LicenseType != "" {
   176  		ui.Message(fmt.Sprintf("Setting license type to '%s'", p.config.LicenseType))
   177  		params.LicenseType = &p.config.LicenseType
   178  	}
   179  
   180  	import_start, err := ec2conn.ImportImage(params)
   181  
   182  	if err != nil {
   183  		return nil, false, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   184  	}
   185  
   186  	ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *import_start.ImportTaskId))
   187  
   188  	// Wait for import process to complete, this takes a while
   189  	ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId))
   190  	err = awscommon.WaitUntilImageImported(aws.BackgroundContext(), ec2conn, *import_start.ImportTaskId)
   191  	if err != nil {
   192  		return nil, false, fmt.Errorf("Import task %s failed with error: %s", *import_start.ImportTaskId, err)
   193  	}
   194  
   195  	// Retrieve what the outcome was for the import task
   196  	import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
   197  		ImportTaskIds: []*string{
   198  			import_start.ImportTaskId,
   199  		},
   200  	})
   201  
   202  	if err != nil {
   203  		return nil, false, fmt.Errorf("Failed to find import task %s: %s", *import_start.ImportTaskId, err)
   204  	}
   205  
   206  	// Check it was actually completed
   207  	if *import_result.ImportImageTasks[0].Status != "completed" {
   208  		// The most useful error message is from the job itself
   209  		return nil, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage)
   210  	}
   211  
   212  	ui.Message(fmt.Sprintf("Import task %s complete", *import_start.ImportTaskId))
   213  
   214  	// Pull AMI ID out of the completed job
   215  	createdami := *import_result.ImportImageTasks[0].ImageId
   216  
   217  	if p.config.Name != "" {
   218  
   219  		ui.Message(fmt.Sprintf("Starting rename of AMI (%s)", createdami))
   220  
   221  		resp, err := ec2conn.CopyImage(&ec2.CopyImageInput{
   222  			Name:          &p.config.Name,
   223  			SourceImageId: &createdami,
   224  			SourceRegion:  config.Region,
   225  		})
   226  
   227  		if err != nil {
   228  			return nil, false, fmt.Errorf("Error Copying AMI (%s): %s", createdami, err)
   229  		}
   230  
   231  		ui.Message(fmt.Sprintf("Waiting for AMI rename to complete (may take a while)"))
   232  
   233  		if err := awscommon.WaitUntilAMIAvailable(aws.BackgroundContext(), ec2conn, *resp.ImageId); err != nil {
   234  			return nil, false, fmt.Errorf("Error waiting for AMI (%s): %s", *resp.ImageId, err)
   235  		}
   236  
   237  		_, err = ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
   238  			ImageId: &createdami,
   239  		})
   240  
   241  		if err != nil {
   242  			return nil, false, fmt.Errorf("Error deregistering existing AMI: %s", err)
   243  		}
   244  
   245  		ui.Message(fmt.Sprintf("AMI rename completed"))
   246  
   247  		createdami = *resp.ImageId
   248  	}
   249  
   250  	// If we have tags, then apply them now to both the AMI and snaps
   251  	// created by the import
   252  	if len(p.config.Tags) > 0 {
   253  		var ec2Tags []*ec2.Tag
   254  
   255  		log.Printf("Repacking tags into AWS format")
   256  
   257  		for key, value := range p.config.Tags {
   258  			ui.Message(fmt.Sprintf("Adding tag \"%s\": \"%s\"", key, value))
   259  			ec2Tags = append(ec2Tags, &ec2.Tag{
   260  				Key:   aws.String(key),
   261  				Value: aws.String(value),
   262  			})
   263  		}
   264  
   265  		resourceIds := []*string{&createdami}
   266  
   267  		log.Printf("Getting details of %s", createdami)
   268  
   269  		imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
   270  			ImageIds: resourceIds,
   271  		})
   272  
   273  		if err != nil {
   274  			return nil, false, fmt.Errorf("Failed to retrieve details for AMI %s: %s", createdami, err)
   275  		}
   276  
   277  		if len(imageResp.Images) == 0 {
   278  			return nil, false, fmt.Errorf("AMI %s has no images", createdami)
   279  		}
   280  
   281  		image := imageResp.Images[0]
   282  
   283  		log.Printf("Walking block device mappings for %s to find snapshots", createdami)
   284  
   285  		for _, device := range image.BlockDeviceMappings {
   286  			if device.Ebs != nil && device.Ebs.SnapshotId != nil {
   287  				ui.Message(fmt.Sprintf("Tagging snapshot %s", *device.Ebs.SnapshotId))
   288  				resourceIds = append(resourceIds, device.Ebs.SnapshotId)
   289  			}
   290  		}
   291  
   292  		ui.Message(fmt.Sprintf("Tagging AMI %s", createdami))
   293  
   294  		_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
   295  			Resources: resourceIds,
   296  			Tags:      ec2Tags,
   297  		})
   298  
   299  		if err != nil {
   300  			return nil, false, fmt.Errorf("Failed to add tags to resources %#v: %s", resourceIds, err)
   301  		}
   302  
   303  	}
   304  
   305  	// Apply attributes for AMI specified in config
   306  	// (duped from builder/amazon/common/step_modify_ami_attributes.go)
   307  	options := make(map[string]*ec2.ModifyImageAttributeInput)
   308  	if p.config.Description != "" {
   309  		options["description"] = &ec2.ModifyImageAttributeInput{
   310  			Description: &ec2.AttributeValue{Value: &p.config.Description},
   311  		}
   312  	}
   313  
   314  	if len(p.config.Groups) > 0 {
   315  		groups := make([]*string, len(p.config.Groups))
   316  		adds := make([]*ec2.LaunchPermission, len(p.config.Groups))
   317  		addGroups := &ec2.ModifyImageAttributeInput{
   318  			LaunchPermission: &ec2.LaunchPermissionModifications{},
   319  		}
   320  
   321  		for i, g := range p.config.Groups {
   322  			groups[i] = aws.String(g)
   323  			adds[i] = &ec2.LaunchPermission{
   324  				Group: aws.String(g),
   325  			}
   326  		}
   327  		addGroups.UserGroups = groups
   328  		addGroups.LaunchPermission.Add = adds
   329  
   330  		options["groups"] = addGroups
   331  	}
   332  
   333  	if len(p.config.Users) > 0 {
   334  		users := make([]*string, len(p.config.Users))
   335  		adds := make([]*ec2.LaunchPermission, len(p.config.Users))
   336  		for i, u := range p.config.Users {
   337  			users[i] = aws.String(u)
   338  			adds[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
   339  		}
   340  		options["users"] = &ec2.ModifyImageAttributeInput{
   341  			UserIds: users,
   342  			LaunchPermission: &ec2.LaunchPermissionModifications{
   343  				Add: adds,
   344  			},
   345  		}
   346  	}
   347  
   348  	if len(options) > 0 {
   349  		for name, input := range options {
   350  			ui.Message(fmt.Sprintf("Modifying: %s", name))
   351  			input.ImageId = &createdami
   352  			_, err := ec2conn.ModifyImageAttribute(input)
   353  			if err != nil {
   354  				return nil, false, fmt.Errorf("Error modifying AMI attributes: %s", err)
   355  			}
   356  		}
   357  	}
   358  
   359  	// Add the reported AMI ID to the artifact list
   360  	log.Printf("Adding created AMI ID %s in region %s to output artifacts", createdami, *config.Region)
   361  	artifact = &awscommon.Artifact{
   362  		Amis: map[string]string{
   363  			*config.Region: createdami,
   364  		},
   365  		BuilderIdValue: BuilderId,
   366  		Session:        session,
   367  	}
   368  
   369  	if !p.config.SkipClean {
   370  		ui.Message(fmt.Sprintf("Deleting import source s3://%s/%s", p.config.S3Bucket, p.config.S3Key))
   371  		s3conn := s3.New(session)
   372  		_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
   373  			Bucket: &p.config.S3Bucket,
   374  			Key:    &p.config.S3Key,
   375  		})
   376  		if err != nil {
   377  			return nil, false, fmt.Errorf("Failed to delete s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   378  		}
   379  	}
   380  
   381  	return artifact, false, nil
   382  }