diff --git a/go.mod b/go.mod index c0c8817d..bcf8c082 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index b4c4db4b..3bbb0fef 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/post-processor/hcp-vagrant-registry/post-processor.go b/post-processor/hcp-vagrant-registry/post-processor.go index 25926468..e3511b92 100644 --- a/post-processor/hcp-vagrant-registry/post-processor.go +++ b/post-processor/hcp-vagrant-registry/post-processor.go @@ -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" @@ -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) diff --git a/post-processor/hcp-vagrant-registry/post-processor_test.go b/post-processor/hcp-vagrant-registry/post-processor_test.go index ce6e6514..c46c83af 100644 --- a/post-processor/hcp-vagrant-registry/post-processor_test.go +++ b/post-processor/hcp-vagrant-registry/post-processor_test.go @@ -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", @@ -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", diff --git a/post-processor/hcp-vagrant-registry/step_confirm_upload.go b/post-processor/hcp-vagrant-registry/step_confirm_upload.go index 2105faa1..8a572364 100644 --- a/post-processor/hcp-vagrant-registry/step_confirm_upload.go +++ b/post-processor/hcp-vagrant-registry/step_confirm_upload.go @@ -9,6 +9,7 @@ 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{} @@ -16,6 +17,7 @@ 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 @@ -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( ®istry_service.CompleteDirectUploadBoxParams{ Context: ctx, diff --git a/post-processor/hcp-vagrant-registry/step_create_box.go b/post-processor/hcp-vagrant-registry/step_create_box.go index f1b8a484..452d1f36 100644 --- a/post-processor/hcp-vagrant-registry/step_create_box.go +++ b/post-processor/hcp-vagrant-registry/step_create_box.go @@ -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" @@ -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) @@ -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( ®istry_service.CreateBoxParams{ Context: ctx, Registry: config.registry, @@ -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 }