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 }