From e858b68213dfd0eecd2b47830ed13347610faba0 Mon Sep 17 00:00:00 2001 From: Henrik Gerdes Date: Wed, 23 Oct 2024 21:20:35 +0200 Subject: [PATCH] feat: allow additional methods in http datasource This adds support for additional http methods for the http datasource. Fixes #13169 Signed-off-by: Henrik Gerdes --- datasource/http/data.go | 36 +++++++++++++++++-- datasource/http/data.hcl2spec.go | 4 +++ datasource/http/data_acc_test.go | 11 ++++++ .../http/test-fixtures/invalid_method.pkr.hcl | 26 ++++++++++++++ .../datasource/http/Config-not-required.mdx | 4 +++ .../datasource/http/Config-required.mdx | 2 +- 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 datasource/http/test-fixtures/invalid_method.pkr.hcl diff --git a/datasource/http/data.go b/datasource/http/data.go index 89b8c826ffe..7cbbcbc6506 100644 --- a/datasource/http/data.go +++ b/datasource/http/data.go @@ -24,10 +24,14 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - // The URL to request data from. This URL must respond with a `200 OK` response and a `text/*` or `application/json` Content-Type. + // The URL to request data from. This URL must respond with a `2xx` range response code and a `text/*` or `application/json` Content-Type. Url string `mapstructure:"url" required:"true"` + // HTTP method used for the request. Supported methods are `HEAD`, `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH`. Default is `GET`. + Method string `mapstructure:"method" required:"false"` // A map of strings representing additional HTTP headers to include in the request. RequestHeaders map[string]string `mapstructure:"request_headers" required:"false"` + // HTTP request payload send with the request. Default is empty. + RequestBody string `mapstructure:"request_body" required:"false"` } type Datasource struct { @@ -62,6 +66,26 @@ func (d *Datasource) Configure(raws ...interface{}) error { fmt.Errorf("the `url` must be specified")) } + // Default to GET if no method is specified + if d.config.Method == "" { + d.config.Method = "GET" + } + + // Check if the input is in the list of allowed methods + validMethod := false + allowedMethods := []string{"HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"} + for _, method := range allowedMethods { + if method == d.config.Method { + validMethod = true + break + } + } + if !validMethod { + errs = packersdk.MultiErrorAppend( + errs, + fmt.Errorf("the `method` must be one of %v", allowedMethods)) + } + if errs != nil && len(errs.Errors) > 0 { return errs } @@ -102,10 +126,16 @@ func isContentTypeText(contentType string) bool { // https://github.com/hashicorp/terraform-provider-http/blob/main/internal/provider/data_source.go func (d *Datasource) Execute() (cty.Value, error) { ctx := context.TODO() - url, headers := d.config.Url, d.config.RequestHeaders + url, method, headers := d.config.Url, d.config.Method, d.config.RequestHeaders client := &http.Client{} - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + // Create request body if it is provided + var requestBody io.Reader + if d.config.RequestBody != "" { + requestBody = strings.NewReader(d.config.RequestBody) + } + + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) // TODO: How to make a test case for this? if err != nil { fmt.Println("Error creating http request") diff --git a/datasource/http/data.hcl2spec.go b/datasource/http/data.hcl2spec.go index fb9bfc6169f..5ef1d3ff12a 100644 --- a/datasource/http/data.hcl2spec.go +++ b/datasource/http/data.hcl2spec.go @@ -19,7 +19,9 @@ type FlatConfig struct { PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` Url *string `mapstructure:"url" required:"true" cty:"url" hcl:"url"` + Method *string `mapstructure:"method" required:"false" cty:"method" hcl:"method"` RequestHeaders map[string]string `mapstructure:"request_headers" required:"false" cty:"request_headers" hcl:"request_headers"` + RequestBody *string `mapstructure:"request_body" required:"false" cty:"request_body" hcl:"request_body"` } // FlatMapstructure returns a new FlatConfig. @@ -43,7 +45,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, "url": &hcldec.AttrSpec{Name: "url", Type: cty.String, Required: false}, + "method": &hcldec.AttrSpec{Name: "method", Type: cty.String, Required: false}, "request_headers": &hcldec.AttrSpec{Name: "request_headers", Type: cty.Map(cty.String), Required: false}, + "request_body": &hcldec.AttrSpec{Name: "request_body", Type: cty.String, Required: false}, } return s } diff --git a/datasource/http/data_acc_test.go b/datasource/http/data_acc_test.go index fdf680b8298..57d45bc7922 100644 --- a/datasource/http/data_acc_test.go +++ b/datasource/http/data_acc_test.go @@ -24,6 +24,9 @@ var testDatasourceEmptyUrl string //go:embed test-fixtures/404_url.pkr.hcl var testDatasource404Url string +//go:embed test-fixtures/invalid_method.pkr.hcl +var testDatasourceInvalidMethod string + func TestHttpDataSource(t *testing.T) { tests := []struct { Name string @@ -49,6 +52,14 @@ func TestHttpDataSource(t *testing.T) { "error": "the `url` must be specified", }, }, + { + Name: "method_is_invalid", + Path: testDatasourceInvalidMethod, + Error: true, + Outputs: map[string]string{ + "error": "the `method` must be one of [HEAD GET POST PUT DELETE OPTIONS PATCH]", + }, + }, { Name: "404_url", Path: testDatasource404Url, diff --git a/datasource/http/test-fixtures/invalid_method.pkr.hcl b/datasource/http/test-fixtures/invalid_method.pkr.hcl new file mode 100644 index 00000000000..cd6fbcf6243 --- /dev/null +++ b/datasource/http/test-fixtures/invalid_method.pkr.hcl @@ -0,0 +1,26 @@ +source "null" "example" { + communicator = "none" +} + +data "http" "basic" { + url = "https://www.packer.io/" + method = "NONEEXISTING" +} + +locals { + url = "${data.http.basic.url}" + body = "${data.http.basic.body}" != "" +} + +build { + name = "mybuild" + sources = [ + "source.null.example" + ] + provisioner "shell-local" { + inline = [ + "echo url is ${local.url}", + "echo body is ${local.body}" + ] + } +} diff --git a/website/content/partials/datasource/http/Config-not-required.mdx b/website/content/partials/datasource/http/Config-not-required.mdx index 24814625fd4..24fd7e77823 100644 --- a/website/content/partials/datasource/http/Config-not-required.mdx +++ b/website/content/partials/datasource/http/Config-not-required.mdx @@ -1,5 +1,9 @@ +- `method` (string) - HTTP method used for the request. Supported methods are `HEAD`, `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH`. Default is `GET`. + - `request_headers` (map[string]string) - A map of strings representing additional HTTP headers to include in the request. +- `request_body` (string) - HTTP request payload send with the request. Default is empty. + diff --git a/website/content/partials/datasource/http/Config-required.mdx b/website/content/partials/datasource/http/Config-required.mdx index a9cc5183bce..05a17cc4b78 100644 --- a/website/content/partials/datasource/http/Config-required.mdx +++ b/website/content/partials/datasource/http/Config-required.mdx @@ -1,5 +1,5 @@ -- `url` (string) - The URL to request data from. This URL must respond with a `200 OK` response and a `text/*` or `application/json` Content-Type. +- `url` (string) - The URL to request data from. This URL must respond with a `2xx` range response code and a `text/*` or `application/json` Content-Type.