diff --git a/builder/yandex/artifact.go b/builder/yandex/artifact.go index bb6ff465b..dadc45b89 100644 --- a/builder/yandex/artifact.go +++ b/builder/yandex/artifact.go @@ -2,59 +2,45 @@ package yandex import ( "fmt" - "log" + + "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" ) -//revive:disable:var-naming - -// Artifact represents a image as the result of a Packer build. type Artifact struct { - image *Image - driver Driver config *Config + driver Driver + image *compute.Image } -// BuilderID returns the builder Id. //revive:disable:var-naming func (*Artifact) BuilderId() string { return BuilderID } -// Destroy destroys the image represented by the artifact. -func (a *Artifact) Destroy() error { - log.Printf("Destroying image: %s", a.image.Name) - errCh := a.driver.DeleteImage(a.image.Name) - return errCh +func (a *Artifact) Id() string { + return a.image.Id } -// Files returns the files represented by the artifact. func (*Artifact) Files() []string { return nil } -// Id returns the image name. -//revive:disable:var-naming -func (a *Artifact) Id() string { - return a.image.Name -} - -// String returns the string representation of the artifact. +//revive:enable:var-naming func (a *Artifact) String() string { - return fmt.Sprintf("A disk image was created: %v (id: %v)", a.image.Name, a.image.ID) + return fmt.Sprintf("A disk image was created: %v (id: %v) with family name %v", a.image.Name, a.image.Id, a.image.Family) } func (a *Artifact) State(name string) interface{} { switch name { case "ImageID": - return a.image.ID - case "ImageName": - return a.image.Name - case "ImageSizeGb": - return a.image.SizeGb + return a.image.Id case "FolderID": - return a.config.FolderID - case "BuildZone": - return a.config.Zone + return a.image.FolderId } return nil + +} + +func (a *Artifact) Destroy() error { + return a.driver.DeleteImage(a.image.Id) } diff --git a/builder/yandex/artifact_mini.go b/builder/yandex/artifact_mini.go deleted file mode 100644 index 297f9fd9f..000000000 --- a/builder/yandex/artifact_mini.go +++ /dev/null @@ -1,46 +0,0 @@ -package yandex - -import "fmt" - -type ArtifactMini struct { - config *Config - imageID string - imageName string - imageFamily string -} - -//revive:disable:var-naming -func (*ArtifactMini) BuilderId() string { - return BuilderID -} - -func (a *ArtifactMini) Id() string { - return a.imageID -} - -func (*ArtifactMini) Files() []string { - return nil -} - -//revive:enable:var-naming -func (a *ArtifactMini) String() string { - return fmt.Sprintf("A disk image was created: %v (id: %v) (family: %v)", a.imageName, a.imageID, a.imageFamily) -} - -func (a *ArtifactMini) State(name string) interface{} { - switch name { - case "ImageID": - return a.imageID - case "FolderID": - return a.config.FolderID - case "BuildZone": - return a.config.Zone - } - return nil - -} - -func (*ArtifactMini) Destroy() error { - // no destroy right now - return nil -} diff --git a/builder/yandex/artifact_test.go b/builder/yandex/artifact_test.go new file mode 100644 index 000000000..9d167b702 --- /dev/null +++ b/builder/yandex/artifact_test.go @@ -0,0 +1,43 @@ +package yandex + +import ( + "testing" + + "github.com/hashicorp/packer/packer" + + "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" +) + +func TestArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(Artifact) +} + +func TestArtifact_Id(t *testing.T) { + i := &compute.Image{ + Id: "test-id-value", + FolderId: "test-folder-id", + } + a := &Artifact{ + image: i} + expected := "test-id-value" + + if a.Id() != expected { + t.Fatalf("artifact ID should match: %v", expected) + } +} + +func TestArtifact_String(t *testing.T) { + i := &compute.Image{ + Id: "test-id-value", + FolderId: "test-folder-id", + Name: "test-name", + Family: "test-family", + } + a := &Artifact{ + image: i} + expected := "A disk image was created: test-name (id: test-id-value) with family name test-family" + + if a.String() != expected { + t.Fatalf("artifact string should match: %v", expected) + } +} diff --git a/builder/yandex/builder.go b/builder/yandex/builder.go index 2ec7f04ee..b462c1578 100644 --- a/builder/yandex/builder.go +++ b/builder/yandex/builder.go @@ -8,6 +8,8 @@ import ( "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" + + "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" ) // The unique ID for this builder. @@ -30,9 +32,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } // Run executes a yandex Packer build and returns a packer.Artifact -// representing a Yandex Cloud machine image. +// representing a Yandex.Cloud compute image. func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { - driver, err := NewDriverYandexCloud(ui, b.config) + driver, err := NewDriverYC(ui, b.config) if err != nil { return nil, err @@ -66,9 +68,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { &common.StepCleanupTempKeys{ Comm: &b.config.Communicator, }, - &stepTeardownInstance{ - Debug: b.config.PackerDebug, - }, + &stepTeardownInstance{}, &stepCreateImage{}, } @@ -80,16 +80,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { if rawErr, ok := state.GetOk("error"); ok { return nil, rawErr.(error) } - if _, ok := state.GetOk("image_id"); !ok { - log.Println("Failed to find image_id in state. Bug?") - return nil, nil + + image, ok := state.GetOk("image") + if !ok { + return nil, fmt.Errorf("Failed to find 'image' in state. Bug?") } - artifact := &ArtifactMini{ - imageID: state.Get("image_id").(string), - imageName: state.Get("image_name").(string), - imageFamily: state.Get("image_family").(string), - config: b.config, + artifact := &Artifact{ + image: image.(*compute.Image), + config: b.config, } return artifact, nil } diff --git a/builder/yandex/builder_test.go b/builder/yandex/builder_test.go new file mode 100644 index 000000000..bbb3bfa51 --- /dev/null +++ b/builder/yandex/builder_test.go @@ -0,0 +1,14 @@ +package yandex + +import ( + "testing" + + "github.com/hashicorp/packer/packer" +) + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Fatalf("Builder should be a builder") + } +} diff --git a/builder/yandex/config.go b/builder/yandex/config.go index 948eb0d2e..c725aba4c 100644 --- a/builder/yandex/config.go +++ b/builder/yandex/config.go @@ -27,37 +27,36 @@ type Config struct { Communicator communicator.Config `mapstructure:",squash"` Endpoint string `mapstructure:"endpoint"` - Token string `mapstructure:"token"` - ServiceAccountKeyFile string `mapstructure:"service_account_key_file"` FolderID string `mapstructure:"folder_id"` - Zone string `mapstructure:"zone"` + ServiceAccountKeyFile string `mapstructure:"service_account_key_file"` + Token string `mapstructure:"token"` - SerialLogFile string `mapstructure:"serial_log_file"` - InstanceCores int `mapstructure:"instance_cores"` - InstanceMemory int `mapstructure:"instance_mem_gb"` + DiskName string `mapstructure:"disk_name"` DiskSizeGb int `mapstructure:"disk_size_gb"` DiskType string `mapstructure:"disk_type"` - SubnetID string `mapstructure:"subnet_id"` - ImageName string `mapstructure:"image_name"` - ImageFamily string `mapstructure:"image_family"` ImageDescription string `mapstructure:"image_description"` + ImageFamily string `mapstructure:"image_family"` ImageLabels map[string]string `mapstructure:"image_labels"` + ImageName string `mapstructure:"image_name"` ImageProductIDs []string `mapstructure:"image_product_ids"` + InstanceCores int `mapstructure:"instance_cores"` + InstanceMemory int `mapstructure:"instance_mem_gb"` InstanceName string `mapstructure:"instance_name"` Labels map[string]string `mapstructure:"labels"` - DiskName string `mapstructure:"disk_name"` - MachineType string `mapstructure:"machine_type"` + PlatformID string `mapstructure:"platform_id"` Metadata map[string]string `mapstructure:"metadata"` - SourceImageID string `mapstructure:"source_image_id"` + SerialLogFile string `mapstructure:"serial_log_file"` SourceImageFamily string `mapstructure:"source_image_family"` SourceImageFolderID string `mapstructure:"source_image_folder_id"` - UseInternalIP bool `mapstructure:"use_internal_ip"` + SourceImageID string `mapstructure:"source_image_id"` + SubnetID string `mapstructure:"subnet_id"` UseIPv4Nat bool `mapstructure:"use_ipv4_nat"` UseIPv6 bool `mapstructure:"use_ipv6"` + UseInternalIP bool `mapstructure:"use_internal_ip"` + Zone string `mapstructure:"zone"` + ctx interpolate.Context StateTimeout time.Duration `mapstructure:"state_timeout"` - - ctx interpolate.Context } func NewConfig(raws ...interface{}) (*Config, []string, error) { @@ -132,8 +131,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.DiskName = c.InstanceName + "-disk" } - if c.MachineType == "" { - c.MachineType = "standard-v1" + if c.PlatformID == "" { + c.PlatformID = "standard-v1" } if es := c.Communicator.Prepare(&c.ctx); len(es) > 0 { @@ -141,7 +140,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } // Process required parameters. - if c.SourceImageID == "" && c.SourceImageFamily == "" { errs = packer.MultiErrorAppend( errs, errors.New("a source_image_id or source_image_family must be specified")) diff --git a/builder/yandex/config_test.go b/builder/yandex/config_test.go index 5c7b89482..e526f6f8c 100644 --- a/builder/yandex/config_test.go +++ b/builder/yandex/config_test.go @@ -197,7 +197,6 @@ func TestZone(t *testing.T) { // Helper stuff below func testConfig(t *testing.T) (config map[string]interface{}) { - config = map[string]interface{}{ "token": "test_token", "folder_id": "hashicorp", diff --git a/builder/yandex/driver.go b/builder/yandex/driver.go new file mode 100644 index 000000000..83585e368 --- /dev/null +++ b/builder/yandex/driver.go @@ -0,0 +1,18 @@ +package yandex + +import ( + "context" + + ycsdk "github.com/yandex-cloud/go-sdk" +) + +type Driver interface { + DeleteImage(id string) error + SDK() *ycsdk.SDK + GetImage(imageID string) (*Image, error) + GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) + DeleteDisk(ctx context.Context, diskID string) error + DeleteInstance(ctx context.Context, instanceID string) error + DeleteSubnet(ctx context.Context, subnetID string) error + DeleteNetwork(ctx context.Context, networkID string) error +} diff --git a/builder/yandex/driver_yc.go b/builder/yandex/driver_yc.go index ebcb7a124..45a46f274 100644 --- a/builder/yandex/driver_yc.go +++ b/builder/yandex/driver_yc.go @@ -11,64 +11,19 @@ import ( "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" "github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint" + "github.com/yandex-cloud/go-genproto/yandex/cloud/vpc/v1" ycsdk "github.com/yandex-cloud/go-sdk" "github.com/yandex-cloud/go-sdk/iamkey" "github.com/yandex-cloud/go-sdk/pkg/requestid" ) -type Driver interface { - DeleteImage(id string) error - SDK() *ycsdk.SDK - GetImage(imageID string) (*Image, error) - GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) -} - type driverYC struct { sdk *ycsdk.SDK ui packer.Ui } -func (d *driverYC) GetImage(imageID string) (*Image, error) { - image, err := d.sdk.Compute().Image().Get(context.Background(), &compute.GetImageRequest{ - ImageId: imageID, - }) - if err != nil { - return nil, err - } - - return &Image{ - ID: image.Id, - Labels: image.Labels, - Licenses: image.ProductIds, - Name: image.Name, - FolderID: image.FolderId, - MinDiskSizeGb: toGigabytes(image.MinDiskSize), - SizeGb: toGigabytes(image.StorageSize), - }, nil -} - -func (d *driverYC) GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) { - image, err := d.sdk.Compute().Image().GetLatestByFamily(ctx, &compute.GetImageLatestByFamilyRequest{ - FolderId: folderID, - Family: family, - }) - if err != nil { - return nil, err - } - - return &Image{ - ID: image.Id, - Labels: image.Labels, - Licenses: image.ProductIds, - Name: image.Name, - FolderID: image.FolderId, - MinDiskSizeGb: toGigabytes(image.MinDiskSize), - SizeGb: toGigabytes(image.StorageSize), - }, nil -} - -func NewDriverYandexCloud(ui packer.Ui, config *Config) (Driver, error) { - log.Printf("[INFO] Initialize Yandex Cloud client...") +func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) { + log.Printf("[INFO] Initialize Yandex.Cloud client...") sdkConfig := ycsdk.Config{} @@ -115,10 +70,134 @@ func NewDriverYandexCloud(ui packer.Ui, config *Config) (Driver, error) { } +func (d *driverYC) GetImage(imageID string) (*Image, error) { + image, err := d.sdk.Compute().Image().Get(context.Background(), &compute.GetImageRequest{ + ImageId: imageID, + }) + if err != nil { + return nil, err + } + + return &Image{ + ID: image.Id, + Labels: image.Labels, + Licenses: image.ProductIds, + Name: image.Name, + FolderID: image.FolderId, + MinDiskSizeGb: toGigabytes(image.MinDiskSize), + SizeGb: toGigabytes(image.StorageSize), + }, nil +} + +func (d *driverYC) GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) { + image, err := d.sdk.Compute().Image().GetLatestByFamily(ctx, &compute.GetImageLatestByFamilyRequest{ + FolderId: folderID, + Family: family, + }) + if err != nil { + return nil, err + } + + return &Image{ + ID: image.Id, + Labels: image.Labels, + Licenses: image.ProductIds, + Name: image.Name, + FolderID: image.FolderId, + Family: image.Family, + MinDiskSizeGb: toGigabytes(image.MinDiskSize), + SizeGb: toGigabytes(image.StorageSize), + }, nil +} + func (d *driverYC) DeleteImage(ID string) error { - return nil + ctx := context.TODO() + op, err := d.sdk.WrapOperation(d.sdk.Compute().Image().Delete(ctx, &compute.DeleteImageRequest{ + ImageId: ID, + })) + if err != nil { + return err + } + + err = op.Wait(ctx) + if err != nil { + return err + } + + _, err = op.Response() + return err } func (d *driverYC) SDK() *ycsdk.SDK { return d.sdk } + +func (d *driverYC) DeleteInstance(ctx context.Context, instanceID string) error { + op, err := d.sdk.WrapOperation(d.sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{ + InstanceId: instanceID, + })) + if err != nil { + return err + } + + err = op.Wait(ctx) + if err != nil { + return err + } + + _, err = op.Response() + return err +} + +func (d *driverYC) DeleteSubnet(ctx context.Context, subnetID string) error { + op, err := d.sdk.WrapOperation(d.sdk.VPC().Subnet().Delete(ctx, &vpc.DeleteSubnetRequest{ + SubnetId: subnetID, + })) + if err != nil { + return err + } + + err = op.Wait(ctx) + if err != nil { + return err + } + + _, err = op.Response() + return err +} + +func (d *driverYC) DeleteNetwork(ctx context.Context, networkID string) error { + op, err := d.sdk.WrapOperation(d.sdk.VPC().Network().Delete(ctx, &vpc.DeleteNetworkRequest{ + NetworkId: networkID, + })) + if err != nil { + return err + } + + err = op.Wait(ctx) + if err != nil { + return err + } + + _, err = op.Response() + return err + +} + +func (d *driverYC) DeleteDisk(ctx context.Context, diskID string) error { + op, err := d.sdk.WrapOperation(d.sdk.Compute().Disk().Delete(ctx, &compute.DeleteDiskRequest{ + DiskId: diskID, + })) + if err != nil { + return err + } + + err = op.Wait(ctx) + if err != nil { + return err + } + + _, err = op.Response() + return err + +} diff --git a/builder/yandex/image.go b/builder/yandex/image.go index e52a3b6b8..c3d80b7ca 100644 --- a/builder/yandex/image.go +++ b/builder/yandex/image.go @@ -7,5 +7,6 @@ type Image struct { Licenses []string MinDiskSizeGb int Name string + Family string SizeGb int } diff --git a/builder/yandex/step_create_image.go b/builder/yandex/step_create_image.go index 75acdfcb1..4e40f188c 100644 --- a/builder/yandex/step_create_image.go +++ b/builder/yandex/step_create_image.go @@ -60,10 +60,7 @@ func (stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multis log.Printf("Image Family: %s", image.Family) log.Printf("Image Description: %s", image.Description) log.Printf("Image Storage size: %d", image.StorageSize) - state.Put("image_id", image.Id) - state.Put("image_name", c.ImageName) - state.Put("image_family", c.ImageFamily) - state.Put("image_description", c.ImageDescription) + state.Put("image", image) return multistep.ActionContinue } diff --git a/builder/yandex/step_create_instance.go b/builder/yandex/step_create_instance.go index a9dfc2c1c..993c53152 100644 --- a/builder/yandex/step_create_instance.go +++ b/builder/yandex/step_create_instance.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/ioutil" - "time" "github.com/c2h5oh/datasize" "github.com/hashicorp/packer/common/uuid" @@ -17,12 +16,11 @@ import ( ycsdk "github.com/yandex-cloud/go-sdk" ) +const StandardImagesFolderID = "standard-images" + type stepCreateInstance struct { - Debug bool - SerialLogFile string - cleanupInstanceID string - cleanupNetworkID string - cleanupSubnetID string + Debug bool + SerialLogFile string } func createNetwork(ctx context.Context, c *Config, d Driver) (*vpc.Network, error) { @@ -81,11 +79,11 @@ func createSubnet(ctx context.Context, c *Config, d Driver, networkID string) (* return nil, err } - network, ok := resp.(*vpc.Subnet) + subnet, ok := resp.(*vpc.Subnet) if !ok { - return nil, errors.New("subnet create operation response doesn't contain Network") + return nil, errors.New("subnet create operation response doesn't contain Subnet") } - return network, nil + return subnet, nil } func getImage(ctx context.Context, c *Config, d Driver) (*Image, error) { @@ -97,63 +95,61 @@ func getImage(ctx context.Context, c *Config, d Driver) (*Image, error) { if c.SourceImageFolderID != "" { return d.GetImageFromFolder(ctx, c.SourceImageFolderID, familyName) } - return d.GetImageFromFolder(ctx, "standard-images", familyName) + return d.GetImageFromFolder(ctx, StandardImagesFolderID, familyName) } func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { sdk := state.Get("sdk").(*ycsdk.SDK) ui := state.Get("ui").(packer.Ui) - c := state.Get("config").(*Config) - d := state.Get("driver").(Driver) + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) - ctx, cancel := context.WithTimeout(ctx, c.StateTimeout) + ctx, cancel := context.WithTimeout(ctx, config.StateTimeout) defer cancel() - // create or reuse network configuration - instanceSubnetID := "" - if c.SubnetID == "" { - // create Network and Subnet - ui.Say("Creating network...") - network, err := createNetwork(ctx, c, d) - if err != nil { - return stepHaltWithError(state, fmt.Errorf("Error creating network: %s", err)) - } - state.Put("network_id", network.Id) - s.cleanupNetworkID = network.Id - - ui.Say(fmt.Sprintf("Creating subnet in zone %q...", c.Zone)) - subnet, err := createSubnet(ctx, c, d, network.Id) - if err != nil { - return stepHaltWithError(state, fmt.Errorf("Error creating subnet: %s", err)) - } - state.Put("subnet_id", subnet.Id) - instanceSubnetID = subnet.Id - // save for cleanup - s.cleanupSubnetID = subnet.Id - } else { - ui.Say("Use provided subnet id " + c.SubnetID) - instanceSubnetID = c.SubnetID - } - - // Create an instance based on the configuration - ui.Say("Creating instance...") - sourceImage, err := getImage(ctx, c, d) + sourceImage, err := getImage(ctx, config, driver) if err != nil { return stepHaltWithError(state, fmt.Errorf("Error getting source image for instance creation: %s", err)) } - if sourceImage.MinDiskSizeGb > c.DiskSizeGb { + if sourceImage.MinDiskSizeGb > config.DiskSizeGb { return stepHaltWithError(state, fmt.Errorf("Instance DiskSizeGb (%d) should be equal or greater "+ - "than SourceImage disk requirement (%d)", c.DiskSizeGb, sourceImage.MinDiskSizeGb)) + "than SourceImage disk requirement (%d)", config.DiskSizeGb, sourceImage.MinDiskSizeGb)) } - instanceMetadata, err := c.createInstanceMetadata(string(c.Communicator.SSHPublicKey)) - if err != nil { - return stepHaltWithError(state, fmt.Errorf("instance metadata prepare error: %s", err)) + ui.Say(fmt.Sprintf("Using as source image: %s (name: %q, family: %q)", sourceImage.ID, sourceImage.Name, sourceImage.Family)) + + // create or reuse network configuration + instanceSubnetID := "" + if config.SubnetID == "" { + // create Network and Subnet + ui.Say("Creating network...") + network, err := createNetwork(ctx, config, driver) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error creating network: %s", err)) + } + state.Put("network_id", network.Id) + + ui.Say(fmt.Sprintf("Creating subnet in zone %q...", config.Zone)) + subnet, err := createSubnet(ctx, config, driver, network.Id) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error creating subnet: %s", err)) + } + instanceSubnetID = subnet.Id + // save for cleanup + state.Put("subnet_id", subnet.Id) + } else { + ui.Say("Use provided subnet id " + config.SubnetID) + instanceSubnetID = config.SubnetID } + // Create an instance based on the configuration + ui.Say("Creating instance...") + + instanceMetadata := config.createInstanceMetadata(string(config.Communicator.SSHPublicKey)) + // TODO make part metadata prepare process - if c.UseIPv6 { + if config.UseIPv6 { // this ugly hack will replace user provided 'user-data' userData := `#cloud-config runcmd: @@ -163,23 +159,23 @@ runcmd: } req := &compute.CreateInstanceRequest{ - FolderId: c.FolderID, - Name: c.InstanceName, - Labels: c.Labels, - ZoneId: c.Zone, - PlatformId: "standard-v1", + FolderId: config.FolderID, + Name: config.InstanceName, + Labels: config.Labels, + ZoneId: config.Zone, + PlatformId: config.PlatformID, ResourcesSpec: &compute.ResourcesSpec{ - Memory: toBytes(c.InstanceMemory), - Cores: int64(c.InstanceCores), + Memory: toBytes(config.InstanceMemory), + Cores: int64(config.InstanceCores), }, Metadata: instanceMetadata, BootDiskSpec: &compute.AttachedDiskSpec{ AutoDelete: false, Disk: &compute.AttachedDiskSpec_DiskSpec_{ DiskSpec: &compute.AttachedDiskSpec_DiskSpec{ - Name: c.DiskName, - TypeId: c.DiskType, - Size: int64((datasize.ByteSize(c.DiskSizeGb) * datasize.GB).Bytes()), + Name: config.DiskName, + TypeId: config.DiskType, + Size: int64((datasize.ByteSize(config.DiskSizeGb) * datasize.GB).Bytes()), Source: &compute.AttachedDiskSpec_DiskSpec_ImageId{ ImageId: sourceImage.ID, }, @@ -194,11 +190,11 @@ runcmd: }, } - if c.UseIPv6 { + if config.UseIPv6 { req.NetworkInterfaceSpecs[0].PrimaryV6AddressSpec = &compute.PrimaryAddressSpec{} } - if c.UseIPv4Nat { + if config.UseIPv4Nat { req.NetworkInterfaceSpecs[0].PrimaryV4AddressSpec = &compute.PrimaryAddressSpec{ OneToOneNatSpec: &compute.OneToOneNatSpec{ IpVersion: compute.IpVersion_IPV4, @@ -211,6 +207,17 @@ runcmd: return stepHaltWithError(state, fmt.Errorf("Error create instance: %s", err)) } + opMetadata, err := op.Metadata() + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error get create operation metadata: %s", err)) + } + + if cimd, ok := opMetadata.(*compute.CreateInstanceMetadata); ok { + state.Put("instance_id", cimd.InstanceId) + } else { + return stepHaltWithError(state, fmt.Errorf("could not get Instance ID from operation metadata")) + } + err = op.Wait(ctx) if err != nil { return stepHaltWithError(state, fmt.Errorf("Error create instance: %s", err)) @@ -226,137 +233,101 @@ runcmd: return stepHaltWithError(state, fmt.Errorf("response doesn't contain Instance")) } - // We use this in cleanup - s.cleanupInstanceID = instance.Id + state.Put("disk_id", instance.BootDisk.DiskId) if s.Debug { ui.Message(fmt.Sprintf("Instance ID %s started. Current instance status %s", instance.Id, instance.Status)) + ui.Message(fmt.Sprintf("Disk ID %s. ", instance.BootDisk.DiskId)) } - // Store the instance id for later - state.Put("instance_id", instance.Id) - state.Put("disk_id", instance.BootDisk.DiskId) - return multistep.ActionContinue } func (s *stepCreateInstance) Cleanup(state multistep.StateBag) { - // If the cleanupInstanceID isn't there, we probably never created it - if s.cleanupInstanceID == "" { - return - } - - c := state.Get("config").(*Config) + config := state.Get("config").(*Config) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + ctx, cancel := context.WithTimeout(context.Background(), config.StateTimeout) + defer cancel() + if s.SerialLogFile != "" { ui.Say("Current state 'cancelled' or 'halted'...") - err := s.writeSerialLogFile(state) + err := s.writeSerialLogFile(ctx, state) if err != nil { ui.Error(err.Error()) } } - ctx, cancel := context.WithTimeout(context.Background(), c.StateTimeout) - defer cancel() - - if s.cleanupSubnetID != "" { - // Destroy the subnet we just created - ui.Say("Destroying subnet...") - err := deleteSubnet(ctx, s.cleanupSubnetID, state) - if err != nil { - ui.Error(fmt.Sprintf( - "Error destroying subnet (id: %s). Please destroy it manually: %s", s.cleanupSubnetID, err)) - } - - // some sleep before delete network - time.Sleep(10 * time.Second) - - // Destroy the network we just created - ui.Say("Destroying network...") - err = deleteNetwork(ctx, s.cleanupNetworkID, state) - if err != nil { - ui.Error(fmt.Sprintf( - "Error destroying network (id: %s). Please destroy it manually: %s", s.cleanupNetworkID, err)) + instanceIDRaw, ok := state.GetOk("instance_id") + if ok { + instanceID := instanceIDRaw.(string) + if instanceID != "" { + ui.Say("Destroying instance...") + err := driver.DeleteInstance(ctx, instanceID) + if err != nil { + ui.Error(fmt.Sprintf( + "Error destroying instance (id: %s). Please destroy it manually: %s", instanceID, err)) + } + ui.Message("Instance has been destroyed!") } } - ui.Say("Destroying boot disk...") - diskID := state.Get("disk_id").(string) - err := deleteDisk(ctx, diskID, state) - if err != nil { - ui.Error(fmt.Sprintf( - "Error destroying boot disk (id: %s). Please destroy it manually: %s", s.cleanupNetworkID, err)) + subnetIDRaw, ok := state.GetOk("subnet_id") + if ok { + subnetID := subnetIDRaw.(string) + if subnetID != "" { + // Destroy the subnet we just created + ui.Say("Destroying subnet...") + err := driver.DeleteSubnet(ctx, subnetID) + if err != nil { + ui.Error(fmt.Sprintf( + "Error destroying subnet (id: %s). Please destroy it manually: %s", subnetID, err)) + } + ui.Message("Subnet has been deleted!") + } + } + + // Destroy the network we just created + networkIDRaw, ok := state.GetOk("network_id") + if ok { + networkID := networkIDRaw.(string) + if networkID != "" { + // Destroy the network we just created + ui.Say("Destroying network...") + err := driver.DeleteNetwork(ctx, networkID) + if err != nil { + ui.Error(fmt.Sprintf( + "Error destroying network (id: %s). Please destroy it manually: %s", networkID, err)) + } + ui.Message("Network has been deleted!") + } + } + + diskIDRaw, ok := state.GetOk("disk_id") + if ok { + ui.Say("Destroying boot disk...") + diskID := diskIDRaw.(string) + err := driver.DeleteDisk(ctx, diskID) + if err != nil { + ui.Error(fmt.Sprintf( + "Error destroying boot disk (id: %s). Please destroy it manually: %s", diskID, err)) + } + ui.Message("Disk has been deleted!") } } -func deleteSubnet(ctx context.Context, subnetID string, state multistep.StateBag) error { - sdk := state.Get("sdk").(*ycsdk.SDK) - - op, err := sdk.WrapOperation(sdk.VPC().Subnet().Delete(ctx, &vpc.DeleteSubnetRequest{ - SubnetId: subnetID, - })) - if err != nil { - return err - } - - err = op.Wait(ctx) - if err != nil { - return err - } - - _, err = op.Response() - return err -} - -func deleteNetwork(ctx context.Context, networkID string, state multistep.StateBag) error { - sdk := state.Get("sdk").(*ycsdk.SDK) - - op, err := sdk.WrapOperation(sdk.VPC().Network().Delete(ctx, &vpc.DeleteNetworkRequest{ - NetworkId: networkID, - })) - if err != nil { - return err - } - - err = op.Wait(ctx) - if err != nil { - return err - } - - _, err = op.Response() - return err -} - -func deleteDisk(ctx context.Context, diskID string, state multistep.StateBag) error { - sdk := state.Get("sdk").(*ycsdk.SDK) - - op, err := sdk.WrapOperation(sdk.Compute().Disk().Delete(ctx, &compute.DeleteDiskRequest{ - DiskId: diskID, - })) - if err != nil { - return err - } - - err = op.Wait(ctx) - if err != nil { - return err - } - - _, err = op.Response() - return err -} - -func (s *stepCreateInstance) writeSerialLogFile(state multistep.StateBag) error { +func (s *stepCreateInstance) writeSerialLogFile(ctx context.Context, state multistep.StateBag) error { sdk := state.Get("sdk").(*ycsdk.SDK) ui := state.Get("ui").(packer.Ui) - ui.Say("Try get serial port output to file " + s.SerialLogFile) - serialOutput, err := sdk.Compute().Instance().GetSerialPortOutput(context.Background(), &compute.GetInstanceSerialPortOutputRequest{ - InstanceId: s.cleanupInstanceID, + instanceID := state.Get("instance_id").(string) + ui.Say("Try get instance's serial port output and write to file " + s.SerialLogFile) + serialOutput, err := sdk.Compute().Instance().GetSerialPortOutput(ctx, &compute.GetInstanceSerialPortOutputRequest{ + InstanceId: instanceID, }) if err != nil { - return fmt.Errorf("Failed to get serial port output for instance (id: %s): %s", s.cleanupInstanceID, err) + return fmt.Errorf("Failed to get serial port output for instance (id: %s): %s", instanceID, err) } if err := ioutil.WriteFile(s.SerialLogFile, []byte(serialOutput.Contents), 0600); err != nil { return fmt.Errorf("Failed to write serial port output to file: %s", err) @@ -365,9 +336,8 @@ func (s *stepCreateInstance) writeSerialLogFile(state multistep.StateBag) error return nil } -func (c *Config) createInstanceMetadata(sshPublicKey string) (map[string]string, error) { +func (c *Config) createInstanceMetadata(sshPublicKey string) map[string]string { instanceMetadata := make(map[string]string) - var err error // Copy metadata from config. for k, v := range c.Metadata { @@ -383,5 +353,5 @@ func (c *Config) createInstanceMetadata(sshPublicKey string) (map[string]string, instanceMetadata[sshMetaKey] = sshKeys } - return instanceMetadata, err + return instanceMetadata } diff --git a/builder/yandex/step_create_ssh_key.go b/builder/yandex/step_create_ssh_key.go index 2bc71c739..6255c3c90 100644 --- a/builder/yandex/step_create_ssh_key.go +++ b/builder/yandex/step_create_ssh_key.go @@ -10,7 +10,6 @@ import ( "io/ioutil" "log" - "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "golang.org/x/crypto/ssh" @@ -23,11 +22,11 @@ type stepCreateSSHKey struct { func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - c := state.Get("config").(*Config) + config := state.Get("config").(*Config) - if c.Communicator.SSHPrivateKeyFile != "" { + if config.Communicator.SSHPrivateKeyFile != "" { ui.Say("Using existing SSH private key") - privateKeyBytes, err := c.Communicator.ReadSSHPrivateKeyFile() + privateKeyBytes, err := config.Communicator.ReadSSHPrivateKeyFile() if err != nil { state.Put("error", err) return multistep.ActionHalt @@ -41,8 +40,8 @@ func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult return multistep.ActionHalt } - c.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(key.PublicKey()) - c.Communicator.SSHPrivateKey = privateKeyBytes + config.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(key.PublicKey()) + config.Communicator.SSHPrivateKey = privateKeyBytes return multistep.ActionContinue } @@ -63,33 +62,32 @@ func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult } // Marshal the public key into SSH compatible format - // TODO properly handle the public key error - pub, _ := ssh.NewPublicKey(&priv.PublicKey) + pub, err := ssh.NewPublicKey(&priv.PublicKey) + if err != nil { + err = fmt.Errorf("Error creating public ssh key: %s", err) + ui.Error(err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub)) - // The name of the public key on DO - name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) + hashMD5 := ssh.FingerprintLegacyMD5(pub) + hashSHA256 := ssh.FingerprintSHA256(pub) - hashMd5 := ssh.FingerprintLegacyMD5(pub) - hashSha256 := ssh.FingerprintSHA256(pub) - - log.Printf("[INFO] temporary ssh key name: %s", name) - log.Printf("[INFO] md5 hash of ssh pub key: %s", hashMd5) - log.Printf("[INFO] sha256 hash of ssh pub key: %s", hashSha256) + log.Printf("[INFO] md5 hash of ssh pub key: %s", hashMD5) + log.Printf("[INFO] sha256 hash of ssh pub key: %s", hashSHA256) // Remember some state for the future - //state.Put("ssh_key_id", key.ID) state.Put("ssh_key_public", pubSSHFormat) - state.Put("ssh_key_name", name) // Set the private key in the config for later - c.Communicator.SSHPrivateKey = pem.EncodeToMemory(&privBlk) - c.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(pub) + config.Communicator.SSHPrivateKey = pem.EncodeToMemory(&privBlk) + config.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(pub) // If we're in debug mode, output the private key to the working directory. if s.Debug { ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath)) - err := ioutil.WriteFile(s.DebugKeyPath, c.Communicator.SSHPrivateKey, 0600) + err := ioutil.WriteFile(s.DebugKeyPath, config.Communicator.SSHPrivateKey, 0600) if err != nil { return stepHaltWithError(state, fmt.Errorf("Error saving debug key: %s", err)) } diff --git a/builder/yandex/step_teardown_instance.go b/builder/yandex/step_teardown_instance.go index ded44437e..eb901094a 100644 --- a/builder/yandex/step_teardown_instance.go +++ b/builder/yandex/step_teardown_instance.go @@ -11,9 +11,7 @@ import ( ycsdk "github.com/yandex-cloud/go-sdk" ) -type stepTeardownInstance struct { - Debug bool -} +type stepTeardownInstance struct{} func (s *stepTeardownInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { sdk := state.Get("sdk").(*ycsdk.SDK) diff --git a/website/source/docs/builders/yandex.html.md b/website/source/docs/builders/yandex.html.md index 746d3b2a3..24a292271 100644 --- a/website/source/docs/builders/yandex.html.md +++ b/website/source/docs/builders/yandex.html.md @@ -76,14 +76,14 @@ can be configured for this builder. - `instance_cores` (number) - The number of cores available to the instance. -- `instance_mem_gb` (number) - The amount of memory available to the instance, specified in gigabytes. +- `instance_mem_gb` (number) - The amount of memory available to the instance, specified in gigabytes. - `disk_name` (string) - The name of the disk, if unset the instance name will be used. - `disk_size_gb` (number) - The size of the disk in GB. This defaults to `10`, which is 10GB. -- `disk_type` (string) - Specify disk type for the launched instance. Defaults to `network-hdd`. +- `disk_type` (string) - Specify disk type for the launched instance. Defaults to `network-hdd`. - `image_description` (string) - The description of the resulting image. @@ -97,12 +97,12 @@ can be configured for this builder. - `image_product_ids` (list) - License IDs that indicate which licenses are attached to resulting image. -- `instance_name` (string) - The name assigned to the instance. +- `instance_name` (string) - The name assigned to the instance. - `labels` (object of key/value strings) - Key/value pair labels to apply to the launched instance. -- `machine_type` (string) - The type of virtual machine to launch. This defaults to 'standard-v1'. +- `platform_id` (string) - Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. - `metadata` (object of key/value strings) - Metadata applied to the launched instance.