* Implement Stringer inteface for multistep.StepAction * scaleway: add pre validate step (check image and snapshot names) Before, it was possible to create multiple images with the same name, leading to a confusing and wasteful situation (same for snapshots). Now, we perform the same kind of checks done by the AWS EC2 builder, and refuse to proceed if there is an existing image with the same name (same for snapshots). As usual, invoking `packer build -force` will bypass the checks. Closes #9839.
155 lines
4.0 KiB
Go
155 lines
4.0 KiB
Go
package scaleway
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
|
"github.com/scaleway/scaleway-sdk-go/scw"
|
|
)
|
|
|
|
// 1. Configure a httptest server to return the list of fakeImgNames or fakeSnapNames
|
|
// (depending on the endpoint).
|
|
// 2. Instantiate a Scaleway API client and wire it to send requests to the httptest
|
|
// server.
|
|
// 3. Return a state (containing the client) ready to be passed to the step.Run() method.
|
|
// 4. Return a teardown function meant to be deferred from the test.
|
|
func setup(t *testing.T, fakeImgNames []string, fakeSnapNames []string) (*multistep.BasicStateBag, func()) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
enc := json.NewEncoder(w)
|
|
switch r.URL.Path {
|
|
case "/instance/v1/zones/fr-par-1/images":
|
|
var imgs instance.ListImagesResponse
|
|
for _, name := range fakeImgNames {
|
|
imgs.Images = append(imgs.Images, &instance.Image{
|
|
ID: strconv.Itoa(rand.Int()),
|
|
Name: name,
|
|
Zone: "fr-par-1",
|
|
})
|
|
}
|
|
imgs.TotalCount = uint32(len(fakeImgNames))
|
|
if err := enc.Encode(imgs); err != nil {
|
|
t.Fatalf("fake server: encoding reply: %s", err)
|
|
}
|
|
case "/instance/v1/zones/fr-par-1/snapshots":
|
|
var snaps instance.ListSnapshotsResponse
|
|
for _, name := range fakeSnapNames {
|
|
snaps.Snapshots = append(snaps.Snapshots, &instance.Snapshot{
|
|
ID: strconv.Itoa(rand.Int()),
|
|
Name: name,
|
|
Zone: "fr-par-1",
|
|
})
|
|
}
|
|
snaps.TotalCount = uint32(len(fakeSnapNames))
|
|
if err := enc.Encode(snaps); err != nil {
|
|
t.Fatalf("fake server: encoding reply: %s", err)
|
|
}
|
|
default:
|
|
t.Fatalf("fake server: unexpected path: %q", r.URL.Path)
|
|
}
|
|
}))
|
|
|
|
clientOpts := []scw.ClientOption{
|
|
scw.WithDefaultZone(scw.ZoneFrPar1),
|
|
scw.WithAPIURL(ts.URL),
|
|
}
|
|
|
|
client, err := scw.NewClient(clientOpts...)
|
|
if err != nil {
|
|
ts.Close()
|
|
t.Fatalf("setup: client: %s", err)
|
|
}
|
|
|
|
state := multistep.BasicStateBag{}
|
|
state.Put("ui", &packer.BasicUi{
|
|
Reader: new(bytes.Buffer),
|
|
Writer: new(bytes.Buffer),
|
|
})
|
|
state.Put("client", client)
|
|
|
|
teardown := func() {
|
|
ts.Close()
|
|
}
|
|
return &state, teardown
|
|
}
|
|
|
|
func TestStepPreValidate(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
fakeImgNames []string
|
|
fakeSnapNames []string
|
|
step stepPreValidate
|
|
wantAction multistep.StepAction
|
|
}{
|
|
{"happy path: both image name and snapshot name are new",
|
|
[]string{"image-old"},
|
|
[]string{"snapshot-old"},
|
|
stepPreValidate{
|
|
Force: false,
|
|
ImageName: "image-new",
|
|
SnapshotName: "snapshot-new",
|
|
},
|
|
multistep.ActionContinue,
|
|
},
|
|
{"want failure: old image name",
|
|
[]string{"image-old"},
|
|
[]string{"snapshot-old"},
|
|
stepPreValidate{
|
|
Force: false,
|
|
ImageName: "image-old",
|
|
SnapshotName: "snapshot-new",
|
|
},
|
|
multistep.ActionHalt,
|
|
},
|
|
{"want failure: old snapshot name",
|
|
[]string{"image-old"},
|
|
[]string{"snapshot-old"},
|
|
stepPreValidate{
|
|
Force: false,
|
|
ImageName: "image-new",
|
|
SnapshotName: "snapshot-old",
|
|
},
|
|
multistep.ActionHalt,
|
|
},
|
|
{"old image name but force flag",
|
|
[]string{"image-old"},
|
|
[]string{"snapshot-old"},
|
|
stepPreValidate{
|
|
Force: true,
|
|
ImageName: "image-old",
|
|
SnapshotName: "snapshot-new",
|
|
},
|
|
multistep.ActionContinue,
|
|
},
|
|
{"old snapshot name but force flag",
|
|
[]string{"image-old"},
|
|
[]string{"snapshot-old"},
|
|
stepPreValidate{
|
|
Force: true,
|
|
ImageName: "image-new",
|
|
SnapshotName: "snapshot-old",
|
|
},
|
|
multistep.ActionContinue,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
state, teardown := setup(t, tc.fakeImgNames, tc.fakeSnapNames)
|
|
defer teardown()
|
|
|
|
if action := tc.step.Run(context.Background(), state); action != tc.wantAction {
|
|
t.Fatalf("step.Run: want: %v; got: %v", tc.wantAction, action)
|
|
}
|
|
})
|
|
}
|
|
}
|