github.phpd.cn/hashicorp/packer@v1.3.2/builder/openstack/step_create_image.go (about) 1 package openstack 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" 10 11 "github.com/gophercloud/gophercloud" 12 "github.com/gophercloud/gophercloud/openstack/compute/v2/images" 13 "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" 14 "github.com/hashicorp/packer/helper/multistep" 15 "github.com/hashicorp/packer/packer" 16 ) 17 18 type stepCreateImage struct { 19 UseBlockStorageVolume bool 20 } 21 22 func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { 23 config := state.Get("config").(*Config) 24 server := state.Get("server").(*servers.Server) 25 ui := state.Get("ui").(packer.Ui) 26 27 // We need the v2 compute client 28 client, err := config.computeV2Client() 29 if err != nil { 30 err = fmt.Errorf("Error initializing compute client: %s", err) 31 state.Put("error", err) 32 return multistep.ActionHalt 33 } 34 35 // Create the image. 36 // Image source depends on the type of the Compute instance. It can be 37 // Block Storage service volume or regular Compute service local volume. 38 ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName)) 39 var imageId string 40 if s.UseBlockStorageVolume { 41 // We need the v3 block storage client. 42 blockStorageClient, err := config.blockStorageV3Client() 43 if err != nil { 44 err = fmt.Errorf("Error initializing block storage client: %s", err) 45 state.Put("error", err) 46 return multistep.ActionHalt 47 } 48 volume := state.Get("volume_id").(string) 49 image, err := volumeactions.UploadImage(blockStorageClient, volume, volumeactions.UploadImageOpts{ 50 DiskFormat: config.ImageDiskFormat, 51 ImageName: config.ImageName, 52 }).Extract() 53 if err != nil { 54 err := fmt.Errorf("Error creating image: %s", err) 55 state.Put("error", err) 56 ui.Error(err.Error()) 57 return multistep.ActionHalt 58 } 59 imageId = image.ImageID 60 } else { 61 imageId, err = servers.CreateImage(client, server.ID, servers.CreateImageOpts{ 62 Name: config.ImageName, 63 Metadata: config.ImageMetadata, 64 }).ExtractImageID() 65 if err != nil { 66 err := fmt.Errorf("Error creating image: %s", err) 67 state.Put("error", err) 68 ui.Error(err.Error()) 69 return multistep.ActionHalt 70 } 71 } 72 73 // Set the Image ID in the state 74 ui.Message(fmt.Sprintf("Image: %s", imageId)) 75 state.Put("image", imageId) 76 77 // Wait for the image to become ready 78 ui.Say(fmt.Sprintf("Waiting for image %s (image id: %s) to become ready...", config.ImageName, imageId)) 79 if err := WaitForImage(client, imageId); err != nil { 80 err := fmt.Errorf("Error waiting for image: %s", err) 81 state.Put("error", err) 82 ui.Error(err.Error()) 83 return multistep.ActionHalt 84 } 85 86 return multistep.ActionContinue 87 } 88 89 func (s *stepCreateImage) Cleanup(multistep.StateBag) { 90 // No cleanup... 91 } 92 93 // WaitForImage waits for the given Image ID to become ready. 94 func WaitForImage(client *gophercloud.ServiceClient, imageId string) error { 95 maxNumErrors := 10 96 numErrors := 0 97 98 for { 99 image, err := images.Get(client, imageId).Extract() 100 if err != nil { 101 errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode) 102 if ok && (errCode.Actual == 500 || errCode.Actual == 404) { 103 numErrors++ 104 if numErrors >= maxNumErrors { 105 log.Printf("[ERROR] Maximum number of errors (%d) reached; failing with: %s", numErrors, err) 106 return err 107 } 108 log.Printf("[ERROR] %d error received, will ignore and retry: %s", errCode.Actual, err) 109 time.Sleep(2 * time.Second) 110 continue 111 } 112 113 return err 114 } 115 116 if image.Status == "ACTIVE" { 117 return nil 118 } 119 120 log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress) 121 time.Sleep(2 * time.Second) 122 } 123 }