github.com/coreos/mantle@v0.13.0/cmd/plume/prerelease.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "html/template" 23 "net/http" 24 "net/url" 25 "os" 26 "path/filepath" 27 "regexp" 28 "sort" 29 "strings" 30 "time" 31 32 "github.com/Azure/azure-sdk-for-go/management/storageservice" 33 "github.com/Microsoft/azure-vhd-utils/vhdcore/validator" 34 "github.com/spf13/cobra" 35 "golang.org/x/net/context" 36 gs "google.golang.org/api/storage/v1" 37 38 "github.com/coreos/mantle/platform/api/aws" 39 "github.com/coreos/mantle/platform/api/azure" 40 "github.com/coreos/mantle/sdk" 41 "github.com/coreos/mantle/storage" 42 "github.com/coreos/mantle/util" 43 ) 44 45 var ( 46 cmdPreRelease = &cobra.Command{ 47 Use: "pre-release [options]", 48 Short: "Run pre-release steps for CoreOS", 49 Long: "Runs pre-release steps for CoreOS, such as image uploading and OS image creation, and replication across regions.", 50 RunE: runPreRelease, 51 } 52 53 platforms = map[string]platform{ 54 "aws": platform{ 55 displayName: "AWS", 56 handler: awsPreRelease, 57 }, 58 "azure": platform{ 59 displayName: "Azure", 60 handler: azurePreRelease, 61 }, 62 } 63 platformList []string 64 65 selectedPlatforms []string 66 selectedDistro string 67 azureProfile string 68 awsCredentialsFile string 69 verifyKeyFile string 70 imageInfoFile string 71 ) 72 73 type imageMetadataAbstract struct { 74 Env string 75 Version string 76 Timestamp string 77 Respin string 78 ImageType string 79 Arch string 80 } 81 82 type platform struct { 83 displayName string 84 handler func(context.Context, *http.Client, *storage.Bucket, *channelSpec, *imageInfo) error 85 } 86 87 type imageInfo struct { 88 AWS *amiList `json:"aws,omitempty"` 89 Azure *azureImageInfo `json:"azure,omitempty"` 90 } 91 92 func init() { 93 for k, _ := range platforms { 94 platformList = append(platformList, k) 95 } 96 sort.Sort(sort.StringSlice(platformList)) 97 98 cmdPreRelease.Flags().StringSliceVar(&selectedPlatforms, "platform", platformList, "platform to pre-release") 99 cmdPreRelease.Flags().StringVar(&selectedDistro, "system", "cl", "system to pre-release") 100 cmdPreRelease.Flags().StringVar(&azureProfile, "azure-profile", "", "Azure Profile json file") 101 cmdPreRelease.Flags().StringVar(&awsCredentialsFile, "aws-credentials", "", "AWS credentials file") 102 cmdPreRelease.Flags().StringVar(&verifyKeyFile, 103 "verify-key", "", "path to ASCII-armored PGP public key to be used in verifying download signatures. Defaults to CoreOS Buildbot (0412 7D0B FABE C887 1FFB 2CCE 50E0 8855 93D2 DCB4)") 104 cmdPreRelease.Flags().StringVar(&imageInfoFile, "write-image-list", "", "optional output file describing uploaded images") 105 106 AddSpecFlags(cmdPreRelease.Flags()) 107 AddFedoraSpecFlags(cmdPreRelease.Flags()) 108 root.AddCommand(cmdPreRelease) 109 } 110 111 func runPreRelease(cmd *cobra.Command, args []string) error { 112 if len(args) > 0 { 113 return errors.New("no args accepted") 114 } 115 116 for _, platformName := range selectedPlatforms { 117 if _, ok := platforms[platformName]; !ok { 118 return fmt.Errorf("Unknown platform %q", platformName) 119 } 120 } 121 122 switch selectedDistro { 123 case "cl": 124 if err := runCLPreRelease(cmd); err != nil { 125 return err 126 } 127 case "fedora": 128 if err := runFedoraPreRelease(cmd); err != nil { 129 return err 130 } 131 default: 132 return fmt.Errorf("Unknown distro %q", selectedDistro) 133 } 134 plog.Printf("Pre-release complete, run `plume release` to finish.") 135 136 return nil 137 } 138 139 func runFedoraPreRelease(cmd *cobra.Command) error { 140 spec, err := ChannelFedoraSpec() 141 if err != nil { 142 return err 143 } 144 ctx := context.Background() 145 client := http.Client{} 146 147 var imageInfo imageInfo 148 149 for _, platformName := range selectedPlatforms { 150 platform := platforms[platformName] 151 plog.Printf("Running %v pre-release...", platform.displayName) 152 if err := platform.handler(ctx, &client, nil, &spec, &imageInfo); err != nil { 153 return err 154 } 155 } 156 157 return nil 158 } 159 160 func runCLPreRelease(cmd *cobra.Command) error { 161 spec := ChannelSpec() 162 ctx := context.Background() 163 client, err := getGoogleClient() 164 if err != nil { 165 plog.Fatal(err) 166 } 167 168 src, err := storage.NewBucket(client, spec.SourceURL()) 169 if err != nil { 170 plog.Fatal(err) 171 } 172 173 if err := src.Fetch(ctx); err != nil { 174 plog.Fatal(err) 175 } 176 177 // Sanity check! 178 if vertxt := src.Object(src.Prefix() + "version.txt"); vertxt == nil { 179 verurl := src.URL().String() + "version.txt" 180 plog.Fatalf("File not found: %s", verurl) 181 } 182 183 var imageInfo imageInfo 184 for _, platformName := range selectedPlatforms { 185 platform := platforms[platformName] 186 plog.Printf("Running %v pre-release...", platform.displayName) 187 if err := platform.handler(ctx, client, src, &spec, &imageInfo); err != nil { 188 plog.Fatal(err) 189 } 190 } 191 192 if imageInfoFile != "" { 193 f, err := os.OpenFile(imageInfoFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) 194 if err != nil { 195 plog.Fatal(err) 196 } 197 defer f.Close() 198 199 encoder := json.NewEncoder(f) 200 encoder.SetIndent("", " ") 201 if err := encoder.Encode(imageInfo); err != nil { 202 plog.Fatalf("couldn't encode image list: %v", err) 203 } 204 } 205 206 return nil 207 } 208 209 // getImageFile downloads a bzipped CoreOS image, verifies its signature, 210 // decompresses it, and returns the decompressed path. 211 func getImageFile(client *http.Client, spec *channelSpec, src *storage.Bucket, fileName string) (string, error) { 212 switch selectedDistro { 213 case "cl": 214 return getCLImageFile(client, src, fileName) 215 case "fedora": 216 return getFedoraImageFile(client, spec, src, fileName) 217 default: 218 return "", fmt.Errorf("Invalid system: %v", selectedDistro) 219 } 220 } 221 222 func getImageTypeURI() string { 223 if specImageType == "Cloud-Base" { 224 return "Cloud" 225 } 226 return specImageType 227 } 228 229 func getCLImageFile(client *http.Client, src *storage.Bucket, fileName string) (string, error) { 230 cacheDir := filepath.Join(sdk.RepoCache(), "images", specChannel, specBoard, specVersion) 231 bzipPath := filepath.Join(cacheDir, fileName) 232 imagePath := strings.TrimSuffix(bzipPath, filepath.Ext(bzipPath)) 233 234 if _, err := os.Stat(imagePath); err == nil { 235 plog.Printf("Reusing existing image %q", imagePath) 236 return imagePath, nil 237 } 238 239 bzipUri, err := url.Parse(fileName) 240 if err != nil { 241 return "", err 242 } 243 244 bzipUri = src.URL().ResolveReference(bzipUri) 245 246 plog.Printf("Downloading image %q to %q", bzipUri, bzipPath) 247 248 if err := sdk.UpdateSignedFile(bzipPath, bzipUri.String(), client, verifyKeyFile); err != nil { 249 return "", err 250 } 251 252 // decompress it 253 plog.Printf("Decompressing %q...", bzipPath) 254 if err := util.Bunzip2File(imagePath, bzipPath); err != nil { 255 return "", err 256 } 257 return imagePath, nil 258 } 259 260 func getFedoraImageFile(client *http.Client, spec *channelSpec, src *storage.Bucket, fileName string) (string, error) { 261 cacheDir := filepath.Join(sdk.RepoCache(), "images", specChannel, specVersion) 262 rawxzPath := filepath.Join(cacheDir, fileName) 263 imagePath := strings.TrimSuffix(rawxzPath, ".xz") 264 265 if _, err := os.Stat(imagePath); err == nil { 266 plog.Printf("Reusing existing image %q", imagePath) 267 return imagePath, nil 268 } 269 270 rawxzURI, err := url.Parse(fmt.Sprintf("%v/%v/compose/%v/%v/images/%v", spec.BaseURL, specComposeID, getImageTypeURI(), specBoard, fileName)) 271 if err != nil { 272 return "", err 273 } 274 275 plog.Printf("Downloading image %q to %q", rawxzURI, rawxzPath) 276 277 if err := sdk.UpdateFile(rawxzPath, rawxzURI.String(), client); err != nil { 278 return "", err 279 } 280 281 // decompress it 282 plog.Printf("Decompressing %q...", rawxzPath) 283 if err := util.XZ2File(imagePath, rawxzPath); err != nil { 284 return "", err 285 } 286 return imagePath, nil 287 } 288 289 func uploadAzureBlob(spec *channelSpec, api *azure.API, storageKey storageservice.GetStorageServiceKeysResponse, vhdfile, container, blobName string) error { 290 blobExists, err := api.BlobExists(spec.Azure.StorageAccount, storageKey.PrimaryKey, container, blobName) 291 if err != nil { 292 return fmt.Errorf("failed to check if file %q in account %q container %q exists: %v", vhdfile, spec.Azure.StorageAccount, container, err) 293 } 294 295 if blobExists { 296 return nil 297 } 298 299 if err := api.UploadBlob(spec.Azure.StorageAccount, storageKey.PrimaryKey, vhdfile, container, blobName, false); err != nil { 300 if _, ok := err.(azure.BlobExistsError); !ok { 301 return fmt.Errorf("uploading file %q to account %q container %q failed: %v", vhdfile, spec.Azure.StorageAccount, container, err) 302 } 303 } 304 return nil 305 } 306 307 func createAzureImage(spec *channelSpec, api *azure.API, blobName, imageName string) error { 308 imageexists, err := api.OSImageExists(imageName) 309 if err != nil { 310 return fmt.Errorf("failed to check if image %q exists: %T %v", imageName, err, err) 311 } 312 313 if imageexists { 314 plog.Printf("OS Image %q exists, using it", imageName) 315 return nil 316 } 317 318 plog.Printf("Creating OS image with name %q", imageName) 319 320 bloburl := api.UrlOfBlob(spec.Azure.StorageAccount, spec.Azure.Container, blobName).String() 321 322 // a la https://github.com/coreos/scripts/blob/998c7e093922298637e7c7e82e25cee7d336144d/oem/azure/set-image-metadata.sh 323 md := &azure.OSImage{ 324 Label: spec.Azure.Label, 325 Name: imageName, 326 OS: "Linux", 327 Description: spec.Azure.Description, 328 MediaLink: bloburl, 329 ImageFamily: spec.Azure.Label, 330 PublishedDate: time.Now().UTC().Format("2006-01-02"), 331 RecommendedVMSize: spec.Azure.RecommendedVMSize, 332 IconURI: spec.Azure.IconURI, 333 SmallIconURI: spec.Azure.SmallIconURI, 334 } 335 336 return api.AddOSImage(md) 337 } 338 339 func replicateAzureImage(spec *channelSpec, api *azure.API, imageName string) error { 340 plog.Printf("Fetching Azure Locations...") 341 locations, err := api.Locations() 342 if err != nil { 343 return err 344 } 345 346 plog.Printf("Replicating image to locations: %s", strings.Join(locations, ", ")) 347 348 channelTitle := strings.Title(specChannel) 349 350 if err := api.ReplicateImage(imageName, spec.Azure.Offer, channelTitle, specVersion, locations...); err != nil { 351 return fmt.Errorf("image replication failed: %v", err) 352 } 353 354 return nil 355 } 356 357 type azureImageInfo struct { 358 ImageName string `json:"image"` 359 } 360 361 // azurePreRelease runs everything necessary to prepare a CoreOS release for Azure. 362 // 363 // This includes uploading the vhd image to Azure storage, creating an OS image from it, 364 // and replicating that OS image. 365 func azurePreRelease(ctx context.Context, client *http.Client, src *storage.Bucket, spec *channelSpec, imageInfo *imageInfo) error { 366 if spec.Azure.StorageAccount == "" { 367 plog.Notice("Azure image creation disabled.") 368 return nil 369 } 370 371 // download azure vhd image and unzip it 372 vhdfile, err := getImageFile(client, spec, src, spec.Azure.Image) 373 if err != nil { 374 return err 375 } 376 377 // sanity check - validate VHD file 378 plog.Printf("Validating VHD file %q", vhdfile) 379 if err := validator.ValidateVhd(vhdfile); err != nil { 380 return err 381 } 382 if err := validator.ValidateVhdSize(vhdfile); err != nil { 383 return err 384 } 385 386 blobName := fmt.Sprintf("container-linux-%s-%s.vhd", specVersion, specChannel) 387 // channel name should be caps for azure image 388 imageName := fmt.Sprintf("%s-%s-%s", spec.Azure.Offer, strings.Title(specChannel), specVersion) 389 390 for _, environment := range spec.Azure.Environments { 391 // construct azure api client 392 api, err := azure.New(&azure.Options{ 393 AzureProfile: azureProfile, 394 AzureSubscription: environment.SubscriptionName, 395 }) 396 if err != nil { 397 return fmt.Errorf("failed to create Azure API: %v", err) 398 } 399 400 plog.Printf("Fetching Azure storage credentials") 401 402 storageKey, err := api.GetStorageServiceKeys(spec.Azure.StorageAccount) 403 if err != nil { 404 return err 405 } 406 407 // upload blob, do not overwrite 408 plog.Printf("Uploading %q to Azure Storage...", vhdfile) 409 410 containers := append([]string{spec.Azure.Container}, environment.AdditionalContainers...) 411 for _, container := range containers { 412 err := uploadAzureBlob(spec, api, storageKey, vhdfile, container, blobName) 413 if err != nil { 414 return err 415 } 416 } 417 418 // create image 419 if err := createAzureImage(spec, api, blobName, imageName); err != nil { 420 // if it is a conflict, it already exists! 421 if !azure.IsConflictError(err) { 422 return err 423 } 424 425 plog.Printf("Azure image %q already exists", imageName) 426 } 427 428 // replicate it 429 if err := replicateAzureImage(spec, api, imageName); err != nil { 430 return err 431 } 432 } 433 434 imageInfo.Azure = &azureImageInfo{ 435 ImageName: imageName, 436 } 437 return nil 438 } 439 440 func getSpecAWSImageMetadata(spec *channelSpec) (map[string]string, error) { 441 imageFileName := spec.AWS.Image 442 imageMetadata := imageMetadataAbstract{ 443 Env: specEnv, 444 Version: specVersion, 445 Timestamp: specTimestamp, 446 Respin: specRespin, 447 ImageType: specImageType, 448 Arch: specBoard, 449 } 450 t := template.Must(template.New("filename").Parse(imageFileName)) 451 buffer := &bytes.Buffer{} 452 if err := t.Execute(buffer, imageMetadata); err != nil { 453 return nil, err 454 } 455 imageFileName = buffer.String() 456 457 var imageName string 458 switch selectedDistro { 459 case "cl": 460 imageName = fmt.Sprintf("%v-%v-%v", spec.AWS.BaseName, specChannel, specVersion) 461 imageName = regexp.MustCompile(`[^A-Za-z0-9()\\./_-]`).ReplaceAllLiteralString(imageName, "_") 462 case "fedora": 463 imageName = strings.TrimSuffix(imageFileName, ".raw.xz") 464 } 465 466 imageDescription := fmt.Sprintf("%v %v %v", spec.AWS.BaseDescription, specChannel, specVersion) 467 468 awsImageMetaData := map[string]string{ 469 "imageFileName": imageFileName, 470 "imageName": imageName, 471 "imageDescription": imageDescription, 472 } 473 474 return awsImageMetaData, nil 475 } 476 477 func awsUploadToPartition(spec *channelSpec, part *awsPartitionSpec, imageName, imageDescription, imagePath string) (map[string]string, map[string]string, error) { 478 plog.Printf("Connecting to %v...", part.Name) 479 api, err := aws.New(&aws.Options{ 480 CredentialsFile: awsCredentialsFile, 481 Profile: part.Profile, 482 Region: part.BucketRegion, 483 }) 484 if err != nil { 485 return nil, nil, fmt.Errorf("creating client for %v: %v", part.Name, err) 486 } 487 488 f, err := os.Open(imagePath) 489 if err != nil { 490 return nil, nil, fmt.Errorf("Could not open image file %v: %v", imagePath, err) 491 } 492 defer f.Close() 493 494 awsImageMetadata, err := getSpecAWSImageMetadata(spec) 495 if err != nil { 496 return nil, nil, fmt.Errorf("Could not generate the image metadata: %v", err) 497 } 498 499 imageFileName := awsImageMetadata["imageFileName"] 500 imageName = awsImageMetadata["imageName"] 501 imageDescription = awsImageMetadata["imageDescription"] 502 503 var s3ObjectPath string 504 switch selectedDistro { 505 case "cl": 506 s3ObjectPath = fmt.Sprintf("%s/%s/%s", specBoard, specVersion, strings.TrimSuffix(imageFileName, filepath.Ext(imageFileName))) 507 case "fedora": 508 s3ObjectPath = fmt.Sprintf("%s/%s/%s", specBoard, specVersion, strings.TrimSuffix(imageFileName, filepath.Ext(imageFileName))) 509 } 510 s3ObjectURL := fmt.Sprintf("s3://%s/%s", part.Bucket, s3ObjectPath) 511 512 snapshot, err := api.FindSnapshot(imageName) 513 if err != nil { 514 return nil, nil, fmt.Errorf("unable to check for snapshot: %v", err) 515 } 516 517 if snapshot == nil { 518 plog.Printf("Creating S3 object %v...", s3ObjectURL) 519 err = api.UploadObject(f, part.Bucket, s3ObjectPath, false, "", "") 520 if err != nil { 521 return nil, nil, fmt.Errorf("Error uploading: %v", err) 522 } 523 524 plog.Printf("Creating EBS snapshot...") 525 526 var format aws.EC2ImageFormat 527 switch selectedDistro { 528 case "cl": 529 format = aws.EC2ImageFormatVmdk 530 case "fedora": 531 format = aws.EC2ImageFormatRaw 532 } 533 534 snapshot, err = api.CreateSnapshot(imageName, s3ObjectURL, format) 535 if err != nil { 536 return nil, nil, fmt.Errorf("unable to create snapshot: %v", err) 537 } 538 } 539 540 // delete unconditionally to avoid leaks after a restart 541 plog.Printf("Deleting S3 object %v...", s3ObjectURL) 542 err = api.DeleteObject(part.Bucket, s3ObjectPath) 543 if err != nil { 544 return nil, nil, fmt.Errorf("Error deleting S3 object: %v", err) 545 } 546 547 plog.Printf("Creating AMIs from %v...", snapshot.SnapshotID) 548 549 hvmImageID, err := api.CreateHVMImage(snapshot.SnapshotID, aws.ContainerLinuxDiskSizeGiB, imageName+"-hvm", imageDescription+" (HVM)") 550 if err != nil { 551 return nil, nil, fmt.Errorf("unable to create HVM image: %v", err) 552 } 553 resources := []string{snapshot.SnapshotID, hvmImageID} 554 555 var pvImageID string 556 if selectedDistro == "cl" { 557 pvImageID, err = api.CreatePVImage(snapshot.SnapshotID, aws.ContainerLinuxDiskSizeGiB, imageName, imageDescription+" (PV)") 558 if err != nil { 559 return nil, nil, fmt.Errorf("unable to create PV image: %v", err) 560 } 561 resources = append(resources, pvImageID) 562 } 563 564 switch selectedDistro { 565 case "cl": 566 err = api.CreateTags(resources, map[string]string{ 567 "Channel": specChannel, 568 "Version": specVersion, 569 }) 570 if err != nil { 571 return nil, nil, fmt.Errorf("couldn't tag images: %v", err) 572 } 573 case "fedora": 574 err = api.CreateTags(resources, map[string]string{ 575 "Channel": specChannel, 576 "Version": specVersion, 577 "ComposeID": specComposeID, 578 }) 579 if err != nil { 580 return nil, nil, fmt.Errorf("couldn't tag images: %v", err) 581 } 582 } 583 584 postprocess := func(imageID string, pv bool) (map[string]string, error) { 585 if len(part.LaunchPermissions) > 0 { 586 if err := api.GrantLaunchPermission(imageID, part.LaunchPermissions); err != nil { 587 return nil, err 588 } 589 } 590 591 destRegions := make([]string, 0, len(part.Regions)) 592 foundBucketRegion := false 593 for _, region := range part.Regions { 594 if region != part.BucketRegion { 595 if pv && !aws.RegionSupportsPV(region) { 596 plog.Debugf("%v doesn't support PV AMIs; skipping", region) 597 } else { 598 destRegions = append(destRegions, region) 599 } 600 } else { 601 foundBucketRegion = true 602 } 603 } 604 if !foundBucketRegion { 605 // We don't handle this case and shouldn't ever 606 // encounter it 607 return nil, fmt.Errorf("BucketRegion %v is not listed in Regions", part.BucketRegion) 608 } 609 610 amis := map[string]string{} 611 if len(destRegions) > 0 { 612 plog.Printf("Replicating AMI %v...", imageID) 613 amis, err = api.CopyImage(imageID, destRegions) 614 if err != nil { 615 return nil, fmt.Errorf("couldn't copy image: %v", err) 616 } 617 } 618 amis[part.BucketRegion] = imageID 619 620 return amis, nil 621 } 622 623 hvmAmis, err := postprocess(hvmImageID, false) 624 if err != nil { 625 return nil, nil, fmt.Errorf("processing HVM images: %v", err) 626 } 627 628 var pvAmis map[string]string 629 if selectedDistro == "cl" { 630 pvAmis, err = postprocess(pvImageID, true) 631 if err != nil { 632 return nil, nil, fmt.Errorf("processing PV images: %v", err) 633 } 634 } 635 636 return hvmAmis, pvAmis, nil 637 } 638 639 type amiListEntry struct { 640 Region string `json:"name"` 641 PvAmi string `json:"pv,omitempty"` 642 HvmAmi string `json:"hvm"` 643 } 644 645 type amiList struct { 646 Entries []amiListEntry `json:"amis"` 647 } 648 649 func awsUploadAmiLists(ctx context.Context, bucket *storage.Bucket, spec *channelSpec, amis *amiList) error { 650 upload := func(name string, data string) error { 651 var contentType string 652 if strings.HasSuffix(name, ".txt") { 653 contentType = "text/plain" 654 } else if strings.HasSuffix(name, ".json") { 655 contentType = "application/json" 656 } else { 657 return fmt.Errorf("unknown file extension in %v", name) 658 } 659 660 obj := gs.Object{ 661 Name: bucket.Prefix() + spec.AWS.Prefix + name, 662 ContentType: contentType, 663 } 664 media := bytes.NewReader([]byte(data)) 665 if err := bucket.Upload(ctx, &obj, media); err != nil { 666 return fmt.Errorf("couldn't upload %v: %v", name, err) 667 } 668 return nil 669 } 670 671 // emit keys in stable order 672 sort.Slice(amis.Entries, func(i, j int) bool { 673 return amis.Entries[i].Region < amis.Entries[j].Region 674 }) 675 676 // format JSON AMI list 677 var jsonBuf bytes.Buffer 678 encoder := json.NewEncoder(&jsonBuf) 679 encoder.SetIndent("", " ") 680 if err := encoder.Encode(amis); err != nil { 681 return fmt.Errorf("couldn't encode JSON: %v", err) 682 } 683 jsonAll := jsonBuf.String() 684 685 // format text AMI lists and upload AMI IDs for individual regions 686 var hvmRecords, pvRecords []string 687 for _, entry := range amis.Entries { 688 hvmRecords = append(hvmRecords, 689 fmt.Sprintf("%v=%v", entry.Region, entry.HvmAmi)) 690 if entry.PvAmi != "" { 691 pvRecords = append(pvRecords, 692 fmt.Sprintf("%v=%v", entry.Region, entry.PvAmi)) 693 } 694 695 if err := upload(fmt.Sprintf("hvm_%v.txt", entry.Region), 696 entry.HvmAmi+"\n"); err != nil { 697 return err 698 } 699 if entry.PvAmi != "" { 700 if err := upload(fmt.Sprintf("pv_%v.txt", entry.Region), 701 entry.PvAmi+"\n"); err != nil { 702 return err 703 } 704 // compatibility 705 if err := upload(fmt.Sprintf("%v.txt", entry.Region), 706 entry.PvAmi+"\n"); err != nil { 707 return err 708 } 709 } 710 } 711 hvmAll := strings.Join(hvmRecords, "|") + "\n" 712 pvAll := strings.Join(pvRecords, "|") + "\n" 713 714 // upload AMI lists 715 if err := upload("all.json", jsonAll); err != nil { 716 return err 717 } 718 if err := upload("hvm.txt", hvmAll); err != nil { 719 return err 720 } 721 if err := upload("pv.txt", pvAll); err != nil { 722 return err 723 } 724 // compatibility 725 if err := upload("all.txt", pvAll); err != nil { 726 return err 727 } 728 729 return nil 730 } 731 732 // awsPreRelease runs everything necessary to prepare a CoreOS release for AWS. 733 // 734 // This includes uploading the ami_vmdk image to an S3 bucket in each EC2 735 // partition, creating HVM and PV AMIs, and replicating the AMIs to each 736 // region. 737 func awsPreRelease(ctx context.Context, client *http.Client, src *storage.Bucket, spec *channelSpec, imageInfo *imageInfo) error { 738 if spec.AWS.Image == "" { 739 plog.Notice("AWS image creation disabled.") 740 return nil 741 } 742 743 awsImageMetadata, err := getSpecAWSImageMetadata(spec) 744 if err != nil { 745 return fmt.Errorf("Could not generate the image filname: %v", err) 746 } 747 748 imageFileName := awsImageMetadata["imageFileName"] 749 imageName := awsImageMetadata["imageName"] 750 imageDescription := awsImageMetadata["imageDescription"] 751 752 imagePath, err := getImageFile(client, spec, src, imageFileName) 753 if err != nil { 754 return err 755 } 756 757 var amis amiList 758 for i := range spec.AWS.Partitions { 759 hvmAmis, pvAmis, err := awsUploadToPartition(spec, &spec.AWS.Partitions[i], imageName, imageDescription, imagePath) 760 if err != nil { 761 return err 762 } 763 764 for region := range hvmAmis { 765 amis.Entries = append(amis.Entries, amiListEntry{ 766 Region: region, 767 PvAmi: pvAmis[region], 768 HvmAmi: hvmAmis[region], 769 }) 770 } 771 } 772 773 if selectedDistro == "cl" { 774 if err := awsUploadAmiLists(ctx, src, spec, &amis); err != nil { 775 return fmt.Errorf("uploading AMI IDs: %v", err) 776 } 777 } 778 779 imageInfo.AWS = &amis 780 return nil 781 }