Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/ephemeral-resources/http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "http Ephemeral Resource - terraform-provider-http"
subcategory: ""
description: |-
The http ephemeral resource makes an HTTP GET request to the given URL and exports
information about the response.
The given URL may be either an http or https URL. This resource
will issue a warning if the result is not UTF-8 encoded.
~> Important Although https URLs can be used, there is currently no
mechanism to authenticate the remote server except for general verification of
the server certificate's chain of trust. Data retrieved from servers not under
your control should be treated as untrustworthy.
By default, there are no retries. Configuring the retry block will result in
retries if an error is returned by the client (e.g., connection errors) or if
a 5xx-range (except 501) status code is received. For further details see
go-retryablehttp https://pkg.go.dev/github.com/hashicorp/go-retryablehttp.
---

# http (Ephemeral Resource)

The `http` ephemeral resource makes an HTTP GET request to the given URL and exports
information about the response.

The given URL may be either an `http` or `https` URL. This resource
will issue a warning if the result is not UTF-8 encoded.

~> **Important** Although `https` URLs can be used, there is currently no
mechanism to authenticate the remote server except for general verification of
the server certificate's chain of trust. Data retrieved from servers not under
your control should be treated as untrustworthy.

By default, there are no retries. Configuring the retry block will result in
retries if an error is returned by the client (e.g., connection errors) or if
a 5xx-range (except 501) status code is received. For further details see
[go-retryablehttp](https://pkg.go.dev/github.com/hashicorp/go-retryablehttp).



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `url` (String) The URL for the request. Supported schemes are `http` and `https`.

### Optional

- `ca_cert_pem` (String) Certificate Authority (CA) in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format.
- `client_cert_pem` (String) Client certificate in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format.
- `client_key_pem` (String) Client key in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format.
- `insecure` (Boolean) Disables verification of the server's certificate chain and hostname. Defaults to `false`
- `method` (String) The HTTP Method for the request. Allowed methods are a subset of methods defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3) namely, `GET`, `HEAD`, and `POST`. `POST` support is only intended for read-only URLs, such as submitting a search.
- `request_body` (String) The request body as a string.
- `request_headers` (Map of String) A map of request header field names and values.
- `request_timeout_ms` (Number) The request timeout in milliseconds.
- `retry` (Block, Optional) Retry request configuration. By default there are no retries. Configuring this block will result in retries if an error is returned by the client (e.g., connection errors) or if a 5xx-range (except 501) status code is received. For further details see [go-retryablehttp](https://pkg.go.dev/github.com/hashicorp/go-retryablehttp). (see [below for nested schema](#nestedblock--retry))

### Read-Only

- `body` (String, Deprecated) The response body returned as a string. **NOTE**: This is deprecated, use `response_body` instead.
- `id` (String) The URL used for the request.
- `response_body` (String) The response body returned as a string.
- `response_body_base64` (String) The response body encoded as base64 (standard) as defined in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4).
- `response_headers` (Map of String) A map of response header field names and values. Duplicate headers are concatenated according to [RFC2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2).
- `status_code` (Number) The HTTP response status code.

<a id="nestedblock--retry"></a>
### Nested Schema for `retry`

Optional:

- `attempts` (Number) The number of times the request is to be retried. For example, if 2 is specified, the request will be tried a maximum of 3 times.
- `max_delay_ms` (Number) The maximum delay between retry requests in milliseconds.
- `min_delay_ms` (Number) The minimum delay between retry requests in milliseconds.
72 changes: 40 additions & 32 deletions internal/provider/data_source_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
Expand Down Expand Up @@ -209,12 +210,22 @@ a 5xx-range (except 501) status code is received. For further details see

func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var model modelV0
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(doRequest(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
}

func doRequest(ctx context.Context, model *modelV0) diag.Diagnostics {
var diags diag.Diagnostics

requestURL := model.URL.ValueString()
method := model.Method.ValueString()
requestHeaders := model.RequestHeaders
Expand All @@ -227,11 +238,11 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r

tr, ok := http.DefaultTransport.(*http.Transport)
if !ok {
resp.Diagnostics.AddError(
diags.AddError(
"Error configuring http transport",
"Error http: Can't configure http transport.",
)
return
return diags
}

// Prevent issues with multiple data source configurations modifying the shared transport.
Expand All @@ -257,11 +268,11 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
if !caCertificate.IsNull() {
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM([]byte(caCertificate.ValueString())); !ok {
resp.Diagnostics.AddError(
diags.AddError(
"Error configuring TLS client",
"Error tls: Can't add the CA certificate to certificate pool. Only PEM encoded certificates are supported.",
)
return
return diags
}

if clonedTr.TLSClientConfig == nil {
Expand All @@ -273,11 +284,11 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
if !model.ClientCert.IsNull() && !model.ClientKey.IsNull() {
cert, err := tls.X509KeyPair([]byte(model.ClientCert.ValueString()), []byte(model.ClientKey.ValueString()))
if err != nil {
resp.Diagnostics.AddError(
diags.AddError(
"error creating x509 key pair",
fmt.Sprintf("error creating x509 key pair from provided pem blocks\n\nError: %s", err),
)
return
return diags
}
clonedTr.TLSClientConfig.Certificates = []tls.Certificate{cert}
}
Expand All @@ -286,9 +297,9 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r

if !model.Retry.IsNull() && !model.Retry.IsUnknown() {
diags = model.Retry.As(ctx, &retry, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
diags.Append(diags...)
if diags.HasError() {
return diags
}
}

Expand All @@ -314,34 +325,32 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
}

request, err := retryablehttp.NewRequestWithContext(ctx, method, requestURL, nil)

if err != nil {
resp.Diagnostics.AddError(
diags.AddError(
"Error creating request",
fmt.Sprintf("Error creating request: %s", err),
)
return
return diags
}

if !model.RequestBody.IsNull() {
err = request.SetBody(strings.NewReader(model.RequestBody.ValueString()))

if err != nil {
resp.Diagnostics.AddError(
diags.AddError(
"Error Setting Request Body",
"An unexpected error occurred while setting the request body: "+err.Error(),
)

return
return diags
}
}

for name, value := range requestHeaders.Elements() {
var header string
diags = tfsdk.ValueAs(ctx, value, &header)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
diags.Append(diags...)
if diags.HasError() {
return diags
}

request.Header.Set(name, header)
Expand All @@ -361,34 +370,34 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
detail = fmt.Sprintf("request exceeded the specified timeout: %s, err: %s", timeout.String(), err)
}

resp.Diagnostics.AddError(
diags.AddError(
"Error making request",
detail,
)
return
return diags
}
}

resp.Diagnostics.AddError(
diags.AddError(
"Error making request",
fmt.Sprintf("Error making request: %s", err),
)
return
return diags
}

defer response.Body.Close()

bytes, err := io.ReadAll(response.Body)
if err != nil {
resp.Diagnostics.AddError(
diags.AddError(
"Error reading response body",
fmt.Sprintf("Error reading response body: %s", err),
)
return
return diags
}

if !utf8.Valid(bytes) {
resp.Diagnostics.AddWarning(
diags.AddWarning(
"Response body is not recognized as UTF-8",
"Terraform may not properly handle the response_body if the contents are binary.",
)
Expand All @@ -404,9 +413,9 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
}

respHeadersState, diags := types.MapValueFrom(ctx, types.StringType, responseHeaders)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
diags.Append(diags...)
if diags.HasError() {
return diags
}

model.ID = types.StringValue(requestURL)
Expand All @@ -416,8 +425,7 @@ func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
model.ResponseBodyBase64 = types.StringValue(responseBodyBase64Std)
model.StatusCode = types.Int64Value(int64(response.StatusCode))

diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
return diags
}

type modelV0 struct {
Expand Down
Loading