Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add block_volumes #105

Merged
merged 10 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .web-docs/components/builder/scaleway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ can also be supplied to override the typical auto-generated key:
- `boottype` (string) - The type of boot, can be either local or
bootscript, Default bootscript

- `remove_volume` (bool) - Remove Volume
- `remove_volume` (bool) - RemoveVolume remove the temporary volumes created before running the server

- `block_volume` ([]ConfigBlockVolume) - BlockVolumes define block volumes attached to the server alongside the default volume
See the [BlockVolumes](#block-volumes-configuration) documentation for fields.

- `cleanup_machine_related_data` (string) - This value allows the user to remove information
that is particular to the instance used to build the image
Expand Down Expand Up @@ -129,6 +132,21 @@ can also be supplied to override the typical auto-generated key:
<!-- End of code generated from the comments of the Config struct in builder/scaleway/config.go; -->


### Block volumes configuration

<!-- Code generated from the comments of the ConfigBlockVolume struct in builder/scaleway/config.go; DO NOT EDIT MANUALLY -->

- `name` (string) - The name of the created volume

- `snapshot_id` (string) - ID of the snapshot to create the volume from

- `size_in_gb` (uint64) - Size of the newly created volume

- `iops` (\*uint32) - IOPS is the number of requested iops for the server's volume. This will not impact created snapshot.

<!-- End of code generated from the comments of the ConfigBlockVolume struct in builder/scaleway/config.go; -->


## Basic Example

Here is a basic example. It is completely valid as soon as you enter your own
Expand Down
2 changes: 1 addition & 1 deletion builder/scaleway/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Debug: b.Config.PackerDebug,
DebugKeyPath: fmt.Sprintf("scw_%s.pem", b.Config.PackerBuildName),
},
new(stepRemoveVolume),
new(stepCreateVolume),
new(stepCreateServer),
new(stepServerInfo),
&communicator.StepConnect{
Expand Down
25 changes: 24 additions & 1 deletion builder/scaleway/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,ConfigBlockVolume

package scaleway

Expand Down Expand Up @@ -30,6 +30,17 @@ const (
defaultCleanupMachineRelatedDataStatus = "false"
)

type ConfigBlockVolume struct {
// The name of the created volume
Name string `mapstructure:"name"`
// ID of the snapshot to create the volume from
SnapshotID string `mapstructure:"snapshot_id"`
// Size of the newly created volume
SizeInGB uint64 `mapstructure:"size_in_gb"`
// IOPS is the number of requested iops for the server's volume. This will not impact created snapshot.
IOPS *uint32 `mapstructure:"iops"`
}

type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
Expand Down Expand Up @@ -86,8 +97,13 @@ type Config struct {
// bootscript, Default bootscript
BootType string `mapstructure:"boottype" required:"false"`

// RemoveVolume remove the temporary volumes created before running the server
RemoveVolume bool `mapstructure:"remove_volume"`

// BlockVolumes define block volumes attached to the server alongside the default volume
// See the [BlockVolumes](#block-volumes-configuration) documentation for fields.
BlockVolumes []ConfigBlockVolume `mapstructure:"block_volume"`

// This value allows the user to remove information
// that is particular to the instance used to build the image
CleanupMachineRelatedData string `mapstructure:"cleanup_machine_related_data" required:"false"`
Expand Down Expand Up @@ -314,6 +330,13 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { //nolint:gocyc
c.CleanupMachineRelatedData = defaultCleanupMachineRelatedDataStatus
}

if len(c.BlockVolumes) > 0 {
blockErrors := prepareBlockVolumes(c.BlockVolumes)
if blockErrors != nil {
errs = packersdk.MultiErrorAppend(errs, blockErrors.Errors...)
}
}

if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
Expand Down
195 changes: 113 additions & 82 deletions builder/scaleway/config.hcl2spec.go

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions builder/scaleway/config_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package scaleway

import (
"fmt"

packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/uuid"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

func prepareBlockVolumes(volumes []ConfigBlockVolume) *packersdk.MultiError {
var errs *packersdk.MultiError

for i := range volumes {
volume := &volumes[i]

if volume.Name == "" {
volume.Name = "packer-" + uuid.TimeOrderedUUID()
}
if volume.SizeInGB != 0 && volume.SnapshotID != "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("volume (index: %d) can't have a snapshot_id and a size", i))
}
if volume.SizeInGB == 0 && volume.SnapshotID == "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("volume (index: %d) must have a snapshot_id or a size", i))
}
}

return errs
}

func (blockVolume *ConfigBlockVolume) VolumeTemplate() *instance.VolumeServerTemplate {
return &instance.VolumeServerTemplate{
Name: &blockVolume.Name,
Size: scw.SizePtr(scw.Size(blockVolume.SizeInGB) * scw.GB),
BaseSnapshot: &blockVolume.SnapshotID,
}
}
4 changes: 1 addition & 3 deletions builder/scaleway/step_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ func backupVolumesFromServer(server *instance.Server) map[string]*instance.Serve
backupVolumes := map[string]*instance.ServerActionRequestVolumeBackupTemplate{}

for _, volume := range server.Volumes {
backupVolumes[volume.ID] = &instance.ServerActionRequestVolumeBackupTemplate{
VolumeType: instance.SnapshotVolumeTypeUnified,
}
backupVolumes[volume.ID] = &instance.ServerActionRequestVolumeBackupTemplate{}
}
return backupVolumes
}
Expand Down
12 changes: 12 additions & 0 deletions builder/scaleway/step_create_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"

"github.com/hashicorp/packer-plugin-sdk/multistep"
Expand Down Expand Up @@ -57,6 +58,17 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
}
}

if len(c.BlockVolumes) > 0 {
if createServerReq.Volumes == nil {
createServerReq.Volumes = make(map[string]*instance.VolumeServerTemplate)
}
createdVolumes := state.Get(StateKeyCreatedVolumes).([]*instance.VolumeServerTemplate)
for i, blockVolume := range createdVolumes {
volumeIndex := strconv.FormatInt(int64(i+1), 10)
createServerReq.Volumes[volumeIndex] = blockVolume
}
}

createServerResp, err := instanceAPI.CreateServer(createServerReq, scw.WithContext(ctx))
if err != nil {
err := fmt.Errorf("error creating server: %s", err)
Expand Down
119 changes: 119 additions & 0 deletions builder/scaleway/step_create_volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package scaleway

import (
"context"
"fmt"

"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/scaleway/packer-plugin-scaleway/internal/httperrors"
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

const StateKeyCreatedVolumes = "created_volumes"

type stepCreateVolume struct{}

func (s *stepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*scw.Client)
blockAPI := block.NewAPI(client)
ui := state.Get("ui").(packersdk.Ui)
c := state.Get("config").(*Config)

volumeTemplates := []*instance.VolumeServerTemplate(nil)
for _, requestedVolume := range c.BlockVolumes {
req := &block.CreateVolumeRequest{
Zone: scw.Zone(c.Zone),
Name: requestedVolume.Name,
PerfIops: requestedVolume.IOPS,
ProjectID: c.ProjectID,
}
if requestedVolume.SnapshotID != "" {
req.FromSnapshot = &block.CreateVolumeRequestFromSnapshot{}
} else {
req.FromEmpty = &block.CreateVolumeRequestFromEmpty{
Size: scw.Size(requestedVolume.SizeInGB) * scw.GB,
}
}
volume, err := blockAPI.CreateVolume(req, scw.WithContext(ctx))
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

volumeTemplates = append(volumeTemplates, &instance.VolumeServerTemplate{
ID: &volume.ID,
VolumeType: instance.VolumeVolumeTypeSbsVolume,
})
}

state.Put(StateKeyCreatedVolumes, volumeTemplates)

return multistep.ActionContinue
}

func (s *stepCreateVolume) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packersdk.Ui)
c := state.Get("config").(*Config)

if !c.RemoveVolume {
return
}

blockAPI := block.NewAPI(state.Get("client").(*scw.Client))

_, serverWasCreated := state.GetOk("server_id")
createdVolumesI, createdVolumesExists := state.GetOk("created_volumes")
if !serverWasCreated && createdVolumesExists {
// If server was not created, we need to clean up manually created volumes
createdVolumes := createdVolumesI.([]*instance.VolumeServerTemplate)
for _, volume := range createdVolumes {
err := blockAPI.DeleteVolume(&block.DeleteVolumeRequest{
Zone: scw.Zone(c.Zone),
VolumeID: *volume.ID,
})
if err != nil {
ui.Error(fmt.Sprintf("failed to cleanup block volume %s: %s", *volume.ID, err))
}
}
}

if _, ok := state.GetOk("snapshots"); !ok {
// volume will be detached from server only after snapshotting ... so we don't
// need to remove volume before snapshot step.
return
}

instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))

ui.Say("Removing Volumes ...")

volumes := state.Get("volumes").([]*instance.VolumeServer)
for _, volume := range volumes {
err := instanceAPI.DeleteVolume(&instance.DeleteVolumeRequest{
VolumeID: volume.ID,
Zone: scw.Zone(c.Zone),
})
if err != nil && !httperrors.Is404(err) {
err := fmt.Errorf("error removing block volume %s: %s", volume.ID, err)
state.Put("error", err)
ui.Error(fmt.Sprintf("Error removing block volume %s: %s. (Ignored)", volume.ID, err))
}
if err == nil {
continue
}

err = blockAPI.DeleteVolume(&block.DeleteVolumeRequest{
Zone: scw.Zone(c.Zone),
VolumeID: volume.ID,
})
if err != nil {
err := fmt.Errorf("error removing block volume %s: %s", volume.ID, err)
state.Put("error", err)
ui.Error(fmt.Sprintf("Error removing block volume %s: %s. (Ignored)", volume.ID, err))
}
}
}
47 changes: 0 additions & 47 deletions builder/scaleway/step_remove_volume.go

This file was deleted.

8 changes: 7 additions & 1 deletion builder/scaleway/step_server_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) mult
}

state.Put("server_ip", instanceResp.PublicIP.Address.String())
state.Put("root_volume_id", instanceResp.Volumes["0"].ID)
state.Put("server", instanceResp)

volumes := []*instance.VolumeServer(nil)
for _, volume := range instanceResp.Volumes {
volumes = append(volumes, volume)
}

state.Put("volumes", volumes)

return multistep.ActionContinue
}

Expand Down
5 changes: 4 additions & 1 deletion docs-partials/builder/scaleway/Config-not-required.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
- `boottype` (string) - The type of boot, can be either local or
bootscript, Default bootscript

- `remove_volume` (bool) - Remove Volume
- `remove_volume` (bool) - RemoveVolume remove the temporary volumes created before running the server

- `block_volume` ([]ConfigBlockVolume) - BlockVolumes define block volumes attached to the server alongside the default volume
See the [BlockVolumes](#block-volumes-configuration) documentation for fields.

- `cleanup_machine_related_data` (string) - This value allows the user to remove information
that is particular to the instance used to build the image
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Code generated from the comments of the ConfigBlockVolume struct in builder/scaleway/config.go; DO NOT EDIT MANUALLY -->

- `name` (string) - The name of the created volume

- `snapshot_id` (string) - ID of the snapshot to create the volume from

- `size_in_gb` (uint64) - Size of the newly created volume

- `iops` (\*uint32) - IOPS is the number of requested iops for the server's volume. This will not impact created snapshot.

<!-- End of code generated from the comments of the ConfigBlockVolume struct in builder/scaleway/config.go; -->
4 changes: 4 additions & 0 deletions docs/builders/scaleway.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ can also be supplied to override the typical auto-generated key:

@include 'builder/scaleway/Config-not-required.mdx'

### Block volumes configuration

@include 'builder/scaleway/ConfigBlockVolume-not-required.mdx'

## Basic Example

Here is a basic example. It is completely valid as soon as you enter your own
Expand Down
Loading
Loading