github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/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/aws/session"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/aws/aws-sdk-go/service/s3"
    13  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    14  	awscommon "github.com/mitchellh/packer/builder/amazon/common"
    15  	"github.com/mitchellh/packer/common"
    16  	"github.com/mitchellh/packer/helper/config"
    17  	"github.com/mitchellh/packer/packer"
    18  	"github.com/mitchellh/packer/template/interpolate"
    19  )
    20  
    21  const BuilderId = "packer.post-processor.amazon-import"
    22  
    23  // Configuration of this post processor
    24  type Config struct {
    25  	common.PackerConfig    `mapstructure:",squash"`
    26  	awscommon.AccessConfig `mapstructure:",squash"`
    27  
    28  	// Variables specific to this post processor
    29  	S3Bucket  string            `mapstructure:"s3_bucket_name"`
    30  	S3Key     string            `mapstructure:"s3_key_name"`
    31  	SkipClean bool              `mapstructure:"skip_clean"`
    32  	Tags      map[string]string `mapstructure:"tags"`
    33  
    34  	ctx interpolate.Context
    35  }
    36  
    37  type PostProcessor struct {
    38  	config Config
    39  }
    40  
    41  // Entry point for configuration parisng when we've defined
    42  func (p *PostProcessor) Configure(raws ...interface{}) error {
    43  	p.config.ctx.Funcs = awscommon.TemplateFuncs
    44  	err := config.Decode(&p.config, &config.DecodeOpts{
    45  		Interpolate:        true,
    46  		InterpolateContext: &p.config.ctx,
    47  		InterpolateFilter: &interpolate.RenderFilter{
    48  			Exclude: []string{
    49  				"s3_key_name",
    50  			},
    51  		},
    52  	}, raws...)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	// Set defaults
    58  	if p.config.S3Key == "" {
    59  		p.config.S3Key = "packer-import-{{timestamp}}.ova"
    60  	}
    61  
    62  	errs := new(packer.MultiError)
    63  
    64  	// Check and render s3_key_name
    65  	if err = interpolate.Validate(p.config.S3Key, &p.config.ctx); err != nil {
    66  		errs = packer.MultiErrorAppend(
    67  			errs, fmt.Errorf("Error parsing s3_key_name template: %s", err))
    68  	}
    69  
    70  	// Check we have AWS access variables defined somewhere
    71  	errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...)
    72  
    73  	// define all our required paramaters
    74  	templates := map[string]*string{
    75  		"s3_bucket_name": &p.config.S3Bucket,
    76  	}
    77  	// Check out required params are defined
    78  	for key, ptr := range templates {
    79  		if *ptr == "" {
    80  			errs = packer.MultiErrorAppend(
    81  				errs, fmt.Errorf("%s must be set", key))
    82  		}
    83  	}
    84  
    85  	// Anything which flagged return back up the stack
    86  	if len(errs.Errors) > 0 {
    87  		return errs
    88  	}
    89  
    90  	log.Println(common.ScrubConfig(p.config, p.config.AccessKey, p.config.SecretKey))
    91  	return nil
    92  }
    93  
    94  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
    95  	var err error
    96  
    97  	config, err := p.config.Config()
    98  	if err != nil {
    99  		return nil, false, err
   100  	}
   101  
   102  	// Render this key since we didn't in the configure phase
   103  	p.config.S3Key, err = interpolate.Render(p.config.S3Key, &p.config.ctx)
   104  	if err != nil {
   105  		return nil, false, fmt.Errorf("Error rendering s3_key_name template: %s", err)
   106  	}
   107  	log.Printf("Rendered s3_key_name as %s", p.config.S3Key)
   108  
   109  	log.Println("Looking for OVA in artifact")
   110  	// Locate the files output from the builder
   111  	source := ""
   112  	for _, path := range artifact.Files() {
   113  		if strings.HasSuffix(path, ".ova") {
   114  			source = path
   115  			break
   116  		}
   117  	}
   118  
   119  	// Hope we found something useful
   120  	if source == "" {
   121  		return nil, false, fmt.Errorf("No OVA file found in artifact from builder")
   122  	}
   123  
   124  	// Set up the AWS session
   125  	log.Println("Creating AWS session")
   126  	session := session.New(config)
   127  
   128  	// open the source file
   129  	log.Printf("Opening file %s to upload", source)
   130  	file, err := os.Open(source)
   131  	if err != nil {
   132  		return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
   133  	}
   134  
   135  	ui.Message(fmt.Sprintf("Uploading %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   136  
   137  	// Copy the OVA file into the S3 bucket specified
   138  	uploader := s3manager.NewUploader(session)
   139  	_, err = uploader.Upload(&s3manager.UploadInput{
   140  		Body:   file,
   141  		Bucket: &p.config.S3Bucket,
   142  		Key:    &p.config.S3Key,
   143  	})
   144  	if err != nil {
   145  		return nil, false, fmt.Errorf("Failed to upload %s: %s", source, err)
   146  	}
   147  
   148  	// May as well stop holding this open now
   149  	file.Close()
   150  
   151  	ui.Message(fmt.Sprintf("Completed upload of %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   152  
   153  	// Call EC2 image import process
   154  	log.Printf("Calling EC2 to import from s3://%s/%s", p.config.S3Bucket, p.config.S3Key)
   155  
   156  	ec2conn := ec2.New(session)
   157  	import_start, err := ec2conn.ImportImage(&ec2.ImportImageInput{
   158  		DiskContainers: []*ec2.ImageDiskContainer{
   159  			{
   160  				UserBucket: &ec2.UserBucket{
   161  					S3Bucket: &p.config.S3Bucket,
   162  					S3Key:    &p.config.S3Key,
   163  				},
   164  			},
   165  		},
   166  	})
   167  
   168  	if err != nil {
   169  		return nil, false, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   170  	}
   171  
   172  	ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *import_start.ImportTaskId))
   173  
   174  	// Wait for import process to complete, this takess a while
   175  	ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId))
   176  
   177  	stateChange := awscommon.StateChangeConf{
   178  		Pending: []string{"pending", "active"},
   179  		Refresh: awscommon.ImportImageRefreshFunc(ec2conn, *import_start.ImportTaskId),
   180  		Target:  "completed",
   181  	}
   182  
   183  	// Actually do the wait for state change
   184  	// We ignore errors out of this and check job state in AWS API
   185  	awscommon.WaitForState(&stateChange)
   186  
   187  	// Retrieve what the outcome was for the import task
   188  	import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
   189  		ImportTaskIds: []*string{
   190  			import_start.ImportTaskId,
   191  		},
   192  	})
   193  
   194  	if err != nil {
   195  		return nil, false, fmt.Errorf("Failed to find import task %s: %s", *import_start.ImportTaskId, err)
   196  	}
   197  
   198  	// Check it was actually completed
   199  	if *import_result.ImportImageTasks[0].Status != "completed" {
   200  		// The most useful error message is from the job itself
   201  		return nil, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage)
   202  	}
   203  
   204  	ui.Message(fmt.Sprintf("Import task %s complete", *import_start.ImportTaskId))
   205  
   206  	// Pull AMI ID out of the completed job
   207  	createdami := *import_result.ImportImageTasks[0].ImageId
   208  
   209  	// If we have tags, then apply them now to both the AMI and snaps
   210  	// created by the import
   211  	if len(p.config.Tags) > 0 {
   212  		var ec2Tags []*ec2.Tag
   213  
   214  		log.Printf("Repacking tags into AWS format")
   215  
   216  		for key, value := range p.config.Tags {
   217  			ui.Message(fmt.Sprintf("Adding tag \"%s\": \"%s\"", key, value))
   218  			ec2Tags = append(ec2Tags, &ec2.Tag{
   219  				Key:   aws.String(key),
   220  				Value: aws.String(value),
   221  			})
   222  		}
   223  
   224  		resourceIds := []*string{&createdami}
   225  
   226  		log.Printf("Getting details of %s", createdami)
   227  
   228  		imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
   229  			ImageIds: resourceIds,
   230  		})
   231  
   232  		if err != nil {
   233  			return nil, false, fmt.Errorf("Failed to retrieve details for AMI %s: %s", createdami, err)
   234  		}
   235  
   236  		if len(imageResp.Images) == 0 {
   237  			return nil, false, fmt.Errorf("AMI %s has no images", createdami)
   238  		}
   239  
   240  		image := imageResp.Images[0]
   241  
   242  		log.Printf("Walking block device mappings for %s to find snapshots", createdami)
   243  
   244  		for _, device := range image.BlockDeviceMappings {
   245  			if device.Ebs != nil && device.Ebs.SnapshotId != nil {
   246  				ui.Message(fmt.Sprintf("Tagging snapshot %s", *device.Ebs.SnapshotId))
   247  				resourceIds = append(resourceIds, device.Ebs.SnapshotId)
   248  			}
   249  		}
   250  
   251  		ui.Message(fmt.Sprintf("Tagging AMI %s", createdami))
   252  
   253  		_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
   254  			Resources: resourceIds,
   255  			Tags:      ec2Tags,
   256  		})
   257  
   258  		if err != nil {
   259  			return nil, false, fmt.Errorf("Failed to add tags to resources %#v: %s", resourceIds, err)
   260  		}
   261  
   262  	}
   263  
   264  	// Add the reported AMI ID to the artifact list
   265  	log.Printf("Adding created AMI ID %s in region %s to output artifacts", createdami, *config.Region)
   266  	artifact = &awscommon.Artifact{
   267  		Amis: map[string]string{
   268  			*config.Region: createdami,
   269  		},
   270  		BuilderIdValue: BuilderId,
   271  		Conn:           ec2conn,
   272  	}
   273  
   274  	if !p.config.SkipClean {
   275  		ui.Message(fmt.Sprintf("Deleting import source s3://%s/%s", p.config.S3Bucket, p.config.S3Key))
   276  		s3conn := s3.New(session)
   277  		_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
   278  			Bucket: &p.config.S3Bucket,
   279  			Key:    &p.config.S3Key,
   280  		})
   281  		if err != nil {
   282  			return nil, false, fmt.Errorf("Failed to delete s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   283  		}
   284  	}
   285  
   286  	return artifact, false, nil
   287  }