Skip to content

Commit

Permalink
Wait for box creation operation to complete.
Browse files Browse the repository at this point in the history
If the registry box needs to be created, wait for the create operation
to finish before allowing the post-processor to continue to the next
step.
  • Loading branch information
chrisroberts committed Sep 12, 2024
1 parent 82efb5c commit 784850a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 4 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/go-openapi/runtime v0.26.2
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl/v2 v2.19.1
github.com/hashicorp/hcp-sdk-go v0.98.0
github.com/hashicorp/hcp-sdk-go v0.111.0
github.com/hashicorp/packer-plugin-sdk v0.5.2
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
github.com/mitchellh/mapstructure v1.5.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5R
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hashicorp/hcp-sdk-go v0.98.0 h1:DKLbGJcP9tCR4EBme6npvcigcRuvma6WCyH1ApZuNaU=
github.com/hashicorp/hcp-sdk-go v0.98.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/hcp-sdk-go v0.111.0 h1:tPQs4N3HdwF8NF3gwZQ8b00CJDeuEzmrQh/OsJlhSSs=
github.com/hashicorp/hcp-sdk-go v0.111.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
Expand Down
2 changes: 2 additions & 0 deletions post-processor/hcp-vagrant-registry/post-processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"

"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-operation/stable/2020-05-05/client/operation_service"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service"
hcpconfig "github.com/hashicorp/hcp-sdk-go/config"
Expand Down Expand Up @@ -285,6 +286,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
state := new(multistep.BasicStateBag)
state.Put("config", &p.config)
state.Put("client", registry_service.New(p.client.Transport, nil))
state.Put("operation-client", operation_service.New(p.client.Transport, nil))
state.Put("artifact", artifact)
state.Put("artifactFilePath", artifact.Files()[0])
state.Put("ui", ui)
Expand Down
172 changes: 171 additions & 1 deletion post-processor/hcp-vagrant-registry/post-processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,69 @@ func TestPostProcessor(t *testing.T) {
wantErr: "Invalid response body",
},
{
desc: "OK - creates box when missing",
desc: "Invalid - creates box when missing - no operation provided",
stack: []stubResponse{
{
Method: "POST",
Path: "/oauth2/token",
Response: `{"access_token": "TEST_TOKEN", "expiry": 0}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64",
StatusCode: 404,
},
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{}`,
StatusCode: 200,
},
},
files: tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox", "architecture": "amd64"}`},
},
wantErr: "Unable to wait for box to become available - Please check HCP Vagrant for box status, and try again.",
},
{
desc: "Invalid - creates box when missing - operation service error",
stack: []stubResponse{
{
Method: "POST",
Path: "/oauth2/token",
Response: `{"access_token": "TEST_TOKEN", "expiry": 0}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64",
StatusCode: 404,
},
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{"operation": {"id": "OP-ID", "location": {"organization_id": "ORG-ID", "project_id": "PROJ-ID"}}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/operation/2020-05-05/organizations/ORG-ID/projects/PROJ-ID/operations/OP-ID/wait",
Response: `{}`,
StatusCode: 500,
},
},
files: tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox", "architecture": "amd64"}`},
},
wantErr: "Unexpected error encountered",
},
{
desc: "Invalid - creates box when missing - no operation data",
stack: []stubResponse{
{
Method: "POST",
Expand All @@ -397,9 +459,117 @@ func TestPostProcessor(t *testing.T) {
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{"operation": {"id": "OP-ID", "location": {"organization_id": "ORG-ID", "project_id": "PROJ-ID"}}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/operation/2020-05-05/organizations/ORG-ID/projects/PROJ-ID/operations/OP-ID/wait",
Response: `{}`,
StatusCode: 200,
},
},
files: tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox", "architecture": "amd64"}`},
},
wantErr: "Unable to check box creation operation status - Please check HCP Vagrant for box status, and try again.",
},
{
desc: "Invalid - creates box when missing - operation reported error",
stack: []stubResponse{
{
Method: "POST",
Path: "/oauth2/token",
Response: `{"access_token": "TEST_TOKEN", "expiry": 0}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64",
StatusCode: 404,
},
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{"operation": {"id": "OP-ID", "location": {"organization_id": "ORG-ID", "project_id": "PROJ-ID"}}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/operation/2020-05-05/organizations/ORG-ID/projects/PROJ-ID/operations/OP-ID/wait",
Response: `{"operation": {"error": {"message": "testing error"}}}`,
StatusCode: 200,
},
},
files: tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox", "architecture": "amd64"}`},
},
wantErr: "Box creation operation reported a failure: testing error - Please try again.",
},
{
desc: "Invalid - creates box when missing - operation not done",
stack: []stubResponse{
{
Method: "POST",
Path: "/oauth2/token",
Response: `{"access_token": "TEST_TOKEN", "expiry": 0}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64",
StatusCode: 404,
},
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{"operation": {"id": "OP-ID", "location": {"organization_id": "ORG-ID", "project_id": "PROJ-ID"}}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/operation/2020-05-05/organizations/ORG-ID/projects/PROJ-ID/operations/OP-ID/wait",
Response: `{"operation": {"state": "RUNNING"}}`,
StatusCode: 200,
},
},
files: tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox", "architecture": "amd64"}`},
},
wantErr: "Timeout exceeded waiting for box to become available - Please verify box creation in HCP Vagrant and try again.",
},
{
desc: "OK - creates box when missing",
stack: []stubResponse{
{
Method: "POST",
Path: "/oauth2/token",
Response: `{"access_token": "TEST_TOKEN", "expiry": 0}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64",
StatusCode: 404,
},
{
Method: "POST",
Path: "/vagrant/2022-09-30/registry/hashicorp/boxes",
Response: `{"operation": {"id": "OP-ID", "location": {"organization_id": "ORG-ID", "project_id": "PROJ-ID"}}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/operation/2020-05-05/organizations/ORG-ID/projects/PROJ-ID/operations/OP-ID/wait",
Response: `{"operation": {"state": "DONE"}}`,
StatusCode: 200,
},
{
Method: "GET",
Path: "/vagrant/2022-09-30/registry/hashicorp/box/precise64/version/0.5",
Expand Down
4 changes: 4 additions & 0 deletions post-processor/hcp-vagrant-registry/step_confirm_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (

"github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packer"
)

type stepConfirmUpload struct{}

func (s *stepConfirmUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*registry_service.Client)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)

if config.NoDirectUpload {
return multistep.ActionContinue
Expand All @@ -25,6 +27,8 @@ func (s *stepConfirmUpload) Run(ctx context.Context, state multistep.StateBag) m
archName := state.Get("architecture").(string)
object := state.Get("upload-object").(string)

ui.Say("Completing box upload...")

_, err := client.CompleteDirectUploadBox(
&registry_service.CompleteDirectUploadBoxParams{
Context: ctx,
Expand Down
50 changes: 48 additions & 2 deletions post-processor/hcp-vagrant-registry/step_create_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"context"
"fmt"

"github.com/hashicorp/hcp-sdk-go/clients/cloud-operation/stable/2020-05-05/client/operation_service"
shared_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/client/registry_service"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-vagrant-box-registry/stable/2022-09-30/models"
"github.com/hashicorp/packer-plugin-sdk/multistep"
Expand All @@ -15,6 +17,8 @@ import (

type stepCreateBox struct{}

var BOX_CREATE_TIMEOUT = "60s"

func (s *stepCreateBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*registry_service.Client)
ui := state.Get("ui").(packer.Ui)
Expand Down Expand Up @@ -45,7 +49,7 @@ func (s *stepCreateBox) Run(ctx context.Context, state multistep.StateBag) multi
}

// Create the box
_, err = client.CreateBox(
cresp, err := client.CreateBox(
&registry_service.CreateBoxParams{
Context: ctx,
Registry: config.registry,
Expand All @@ -62,11 +66,53 @@ func (s *stepCreateBox) Run(ctx context.Context, state multistep.StateBag) multi
}

if errMsg, ok := errorResponseMsg(err); ok {
state.Put("error", fmt.Errorf("Failure creating new box: %s", errMsg))
state.Put("error", fmt.Errorf("Failure creating new box: %s - Please try again.", errMsg))
return multistep.ActionHalt
}

ui.Say(fmt.Sprintf("Created new box: %s", config.Tag))
if cresp.Payload == nil || cresp.Payload.Operation == nil {
state.Put("error", fmt.Errorf("Unable to wait for box to become available - Please check HCP Vagrant for box status, and try again."))
return multistep.ActionHalt
}

ui.Say("Waiting for box to become available...")
op := cresp.Payload.Operation

operationClient := state.Get("operation-client").(operation_service.ClientService)
waitReq := &operation_service.WaitParams{
ID: op.ID,
LocationOrganizationID: op.Location.OrganizationID,
LocationProjectID: op.Location.ProjectID,
Timeout: &BOX_CREATE_TIMEOUT,
Context: ctx,
}

wresp, err := operationClient.Wait(waitReq, nil)
if isErrorUnexpected(err, state) {
return multistep.ActionHalt
}

if errMsg, ok := errorResponseMsg(err); ok {
state.Put("error", fmt.Errorf("Unexpected failure waiting for box to become available: %s - Please try again.", errMsg))
return multistep.ActionHalt
}

if wresp.Payload == nil || wresp.Payload.Operation == nil {
state.Put("error", fmt.Errorf("Unable to check box creation operation status - Please check HCP Vagrant for box status, and try again."))
return multistep.ActionHalt
}

operation := wresp.Payload.Operation
if operation.Error != nil {
state.Put("error", fmt.Errorf("Box creation operation reported a failure: %s - Please try again.", operation.Error.Message))
return multistep.ActionHalt
}

if operation.State == nil || *operation.State != shared_models.HashicorpCloudOperationOperationStateDONE {
state.Put("error", fmt.Errorf("Timeout exceeded waiting for box to become available - Please verify box creation in HCP Vagrant and try again."))
return multistep.ActionHalt
}

return multistep.ActionContinue
}
Expand Down

0 comments on commit 784850a

Please sign in to comment.