diff --git a/docs/post-processors/vagrant-cloud.mdx b/docs/post-processors/vagrant-cloud.mdx index e0810496..fb4df977 100644 --- a/docs/post-processors/vagrant-cloud.mdx +++ b/docs/post-processors/vagrant-cloud.mdx @@ -115,6 +115,16 @@ on Vagrant Cloud, as well as authentication and version information. - `Provider`: The Vagrant provider the box is for - `ArtifactId`: The ID of the input artifact. +- `box_checksum` (string) - Optional checksum for the provider .box file. + The type of the checksum is specified within the checksum field as a prefix, + ex: "md5:{$checksum}". Valid values are: + - null or "" + - "md5:{$checksum}" + - "sha1:{$checksum}" + - "sha256:{$checksum}" + - "sha512:{$checksum}" + See https://www.vagrantup.com/vagrant-cloud/api#arguments-7 + - `no_direct_upload` (boolean) - When `true`, upload the box artifact through Vagrant Cloud instead of directly to the backend storage. diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index ecb9db9f..b602776d 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -11,6 +11,7 @@ import ( "compress/gzip" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -48,6 +49,7 @@ type Config struct { InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"` BoxDownloadUrl string `mapstructure:"box_download_url"` NoDirectUpload bool `mapstructure:"no_direct_upload"` + BoxChecksum string `mapstructure:"box_checksum"` ctx interpolate.Context } @@ -171,6 +173,12 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa return nil, false, false, fmt.Errorf("Error processing box_download_url: %s", err) } + if p.config.BoxChecksum != "" { + if checksumParts := strings.SplitN(p.config.BoxChecksum, ":", 2); len(checksumParts) != 2 { + return nil, false, false, errors.New(" box checksum must be specified as `$type:$digest`") + } + } + // Set up the state state := new(multistep.BasicStateBag) state.Put("config", &p.config) @@ -180,6 +188,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa state.Put("ui", ui) state.Put("providerName", providerName) state.Put("boxDownloadUrl", boxDownloadUrl) + state.Put("boxChecksum", p.config.BoxChecksum) // Build the steps steps := []multistep.Step{ diff --git a/post-processor/vagrant-cloud/post-processor.hcl2spec.go b/post-processor/vagrant-cloud/post-processor.hcl2spec.go index 8dce364e..47e7238b 100644 --- a/post-processor/vagrant-cloud/post-processor.hcl2spec.go +++ b/post-processor/vagrant-cloud/post-processor.hcl2spec.go @@ -27,6 +27,7 @@ type FlatConfig struct { InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` BoxDownloadUrl *string `mapstructure:"box_download_url" cty:"box_download_url" hcl:"box_download_url"` NoDirectUpload *bool `mapstructure:"no_direct_upload" cty:"no_direct_upload" hcl:"no_direct_upload"` + BoxChecksum *string `mapstructure:"box_checksum" cty:"box_checksum" hcl:"box_checksum"` } // FlatMapstructure returns a new FlatConfig. @@ -58,6 +59,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false}, "box_download_url": &hcldec.AttrSpec{Name: "box_download_url", Type: cty.String, Required: false}, "no_direct_upload": &hcldec.AttrSpec{Name: "no_direct_upload", Type: cty.Bool, Required: false}, + "box_checksum": &hcldec.AttrSpec{Name: "box_checksum", Type: cty.String, Required: false}, } return s } diff --git a/post-processor/vagrant-cloud/post-processor_test.go b/post-processor/vagrant-cloud/post-processor_test.go index f4571a8b..9053d1f3 100644 --- a/post-processor/vagrant-cloud/post-processor_test.go +++ b/post-processor/vagrant-cloud/post-processor_test.go @@ -35,6 +35,17 @@ func testGoodConfig() map[string]interface{} { "version_description": "bar", "box_tag": "hashicorp/precise64", "version": "0.5", + "box_checksum": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // /dev/null + } +} + +func testBadChecksumSpec() map[string]interface{} { + return map[string]interface{}{ + "access_token": "foo", + "version_description": "bar", + "box_tag": "hashicorp/precise64", + "version": "0.5", + "box_checksum": "test", } } @@ -229,6 +240,55 @@ func TestPostProcessor_Configure_checkAccessTokenIsNotRequiredForOverridenVagran } } +func TestPostProcessor_PostProcess_badChecksumSpec(t *testing.T) { + files := tarFiles{ + {"foo.txt", "This is a foo file"}, + {"bar.txt", "This is a bar file"}, + {"metadata.json", `{"provider": "virtualbox"}`}, + } + boxfile, err := createBox(files) + if err != nil { + t.Fatalf("%s", err) + } + defer os.Remove(boxfile.Name()) + + artifact := &packersdk.MockArtifact{ + BuilderIdValue: "mitchellh.post-processor.vagrant", + FilesValue: []string{boxfile.Name()}, + } + + s := newStackServer([]stubResponse{stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"}}) + defer s.Close() + + stack := []stubResponse{ + stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"}, + stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`}, + stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`}, + stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`}, + stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`}, + } + + server := newStackServer(stack) + defer server.Close() + config := testBadChecksumSpec() + config["vagrant_cloud_url"] = server.URL + + var p PostProcessor + + err = p.Configure(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + _, _, _, err = p.PostProcess(context.Background(), testUi(), artifact) + if err == nil { + t.Fatal("Expected bad checksum spec error") + } + if !strings.Contains(err.Error(), "box checksum must be specified as") { + t.Fatalf("Unexpected error: %v", err) + } +} + func TestPostProcessor_PostProcess_checkArtifactType(t *testing.T) { artifact := &packersdk.MockArtifact{ BuilderIdValue: "invalid.builder", diff --git a/post-processor/vagrant-cloud/step_create_provider.go b/post-processor/vagrant-cloud/step_create_provider.go index 5b639511..5b937a00 100644 --- a/post-processor/vagrant-cloud/step_create_provider.go +++ b/post-processor/vagrant-cloud/step_create_provider.go @@ -2,17 +2,21 @@ package vagrantcloud import ( "context" + "errors" "fmt" + "strings" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" ) type Provider struct { - Name string `json:"name"` - Url string `json:"url,omitempty"` - HostedToken string `json:"hosted_token,omitempty"` - UploadUrl string `json:"upload_url,omitempty"` + Name string `json:"name"` + Url string `json:"url,omitempty"` + HostedToken string `json:"hosted_token,omitempty"` + UploadUrl string `json:"upload_url,omitempty"` + Checksum string `json:"checksum,omitempty"` + ChecksumType string `json:"checksum_type,omitempty"` } type stepCreateProvider struct { @@ -26,6 +30,7 @@ func (s *stepCreateProvider) Run(ctx context.Context, state multistep.StateBag) version := state.Get("version").(*Version) providerName := state.Get("providerName").(string) downloadUrl := state.Get("boxDownloadUrl").(string) + checksum := state.Get("boxChecksum").(string) path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Version) @@ -35,6 +40,16 @@ func (s *stepCreateProvider) Run(ctx context.Context, state multistep.StateBag) provider.Url = downloadUrl } + if checksum != "" { + checksumParts := strings.SplitN(checksum, ":", 2) + if len(checksumParts) != 2 { + state.Put("error", errors.New("Error parsing box_checksum: invalid format")) + return multistep.ActionHalt + } + provider.ChecksumType = checksumParts[0] + provider.Checksum = checksumParts[1] + } + // Wrap the provider in a provider object for the API wrapper := make(map[string]interface{}) wrapper["provider"] = provider