http provider and http request data source
This commit is contained in:
parent
9a1c6d990f
commit
ace0456d58
|
@ -0,0 +1,104 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func dataSource() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Read: dataSourceRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
"request_headers": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
"body": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceRead(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
url := d.Get("url").(string)
|
||||
headers := d.Get("request_headers").(map[string]interface{})
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating request: %s", err)
|
||||
}
|
||||
|
||||
for name, value := range headers {
|
||||
req.Header.Set(name, value.(string))
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error during making a request: %s", url)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP request error. Response code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType == "" || isContentTypeAllowed(contentType) == false {
|
||||
return fmt.Errorf("Content-Type is not a text type. Got: %s", contentType)
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while reading response body. %s", err)
|
||||
}
|
||||
|
||||
d.Set("body", string(bytes))
|
||||
d.SetId(time.Now().UTC().String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is to prevent potential issues w/ binary files
|
||||
// and generally unprintable characters
|
||||
// See https://github.com/hashicorp/terraform/pull/3858#issuecomment-156856738
|
||||
func isContentTypeAllowed(contentType string) bool {
|
||||
allowedContentTypes := []*regexp.Regexp{
|
||||
regexp.MustCompile("^text/.+"),
|
||||
regexp.MustCompile("^application/json$"),
|
||||
}
|
||||
|
||||
for _, r := range allowedContentTypes {
|
||||
if r.MatchString(contentType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
type TestHttpMock struct {
|
||||
server *httptest.Server
|
||||
}
|
||||
|
||||
const testDataSourceConfig_basic = `
|
||||
data "http" "http_test" {
|
||||
url = "%s/meta_%d.txt"
|
||||
}
|
||||
|
||||
output "body" {
|
||||
value = "${data.http.http_test.body}"
|
||||
}
|
||||
`
|
||||
|
||||
func TestDataSource_http200(t *testing.T) {
|
||||
testHttpMock := setUpMockHttpServer()
|
||||
|
||||
defer testHttpMock.server.Close()
|
||||
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: fmt.Sprintf(testDataSourceConfig_basic, testHttpMock.server.URL, 200),
|
||||
Check: func(s *terraform.State) error {
|
||||
_, ok := s.RootModule().Resources["data.http.http_test"]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing data resource")
|
||||
}
|
||||
|
||||
outputs := s.RootModule().Outputs
|
||||
|
||||
if outputs["body"].Value != "1.0.0" {
|
||||
return fmt.Errorf(
|
||||
`'body' output is %s; want '1.0.0'`,
|
||||
outputs["body"].Value,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestDataSource_http404(t *testing.T) {
|
||||
testHttpMock := setUpMockHttpServer()
|
||||
|
||||
defer testHttpMock.server.Close()
|
||||
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: fmt.Sprintf(testDataSourceConfig_basic, testHttpMock.server.URL, 404),
|
||||
ExpectError: regexp.MustCompile("HTTP request error. Response code: 404"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const testDataSourceConfig_withHeaders = `
|
||||
data "http" "http_test" {
|
||||
url = "%s/restricted/meta_%d.txt"
|
||||
|
||||
request_headers = {
|
||||
"Authorization" = "Zm9vOmJhcg=="
|
||||
}
|
||||
}
|
||||
|
||||
output "body" {
|
||||
value = "${data.http.http_test.body}"
|
||||
}
|
||||
`
|
||||
|
||||
func TestDataSource_withHeaders200(t *testing.T) {
|
||||
testHttpMock := setUpMockHttpServer()
|
||||
|
||||
defer testHttpMock.server.Close()
|
||||
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: fmt.Sprintf(testDataSourceConfig_withHeaders, testHttpMock.server.URL, 200),
|
||||
Check: func(s *terraform.State) error {
|
||||
_, ok := s.RootModule().Resources["data.http.http_test"]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing data resource")
|
||||
}
|
||||
|
||||
outputs := s.RootModule().Outputs
|
||||
|
||||
if outputs["body"].Value != "1.0.0" {
|
||||
return fmt.Errorf(
|
||||
`'body' output is %s; want '1.0.0'`,
|
||||
outputs["body"].Value,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const testDataSourceConfig_error = `
|
||||
data "http" "http_test" {
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
func TestDataSource_compileError(t *testing.T) {
|
||||
resource.UnitTest(t, resource.TestCase{
|
||||
Providers: testProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testDataSourceConfig_error,
|
||||
ExpectError: regexp.MustCompile("required field is not set"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func setUpMockHttpServer() *TestHttpMock {
|
||||
Server := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/meta_200.txt" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("1.0.0"))
|
||||
} else if r.URL.Path == "/restricted/meta_200.txt" {
|
||||
if r.Header.Get("Authorization") == "Zm9vOmJhcg==" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("1.0.0"))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
} else if r.URL.Path == "/meta_404.txt" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
}),
|
||||
)
|
||||
|
||||
return &TestHttpMock{
|
||||
server: Server,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func Provider() terraform.ResourceProvider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{},
|
||||
|
||||
DataSourcesMap: map[string]*schema.Resource{
|
||||
"http": dataSource(),
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testProviders = map[string]terraform.ResourceProvider{
|
||||
"http": Provider(),
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import (
|
|||
googleprovider "github.com/hashicorp/terraform/builtin/providers/google"
|
||||
grafanaprovider "github.com/hashicorp/terraform/builtin/providers/grafana"
|
||||
herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku"
|
||||
httpprovider "github.com/hashicorp/terraform/builtin/providers/http"
|
||||
icinga2provider "github.com/hashicorp/terraform/builtin/providers/icinga2"
|
||||
ignitionprovider "github.com/hashicorp/terraform/builtin/providers/ignition"
|
||||
influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb"
|
||||
|
@ -115,6 +116,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
|||
"google": googleprovider.Provider,
|
||||
"grafana": grafanaprovider.Provider,
|
||||
"heroku": herokuprovider.Provider,
|
||||
"http": httpprovider.Provider,
|
||||
"icinga2": icinga2provider.Provider,
|
||||
"ignition": ignitionprovider.Provider,
|
||||
"influxdb": influxdbprovider.Provider,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP Data Source"
|
||||
sidebar_current: "docs-http-data-source"
|
||||
description: |-
|
||||
Retrieves the content at an HTTP or HTTPS URL.
|
||||
---
|
||||
|
||||
# `http` Data Source
|
||||
|
||||
The `http` data source 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. At present this resource
|
||||
can only retrieve data from URLs that respond with `text/*` or
|
||||
`application/json` content types, and expects the result to be UTF-8 encoded
|
||||
regardless of the returned content type header.
|
||||
|
||||
~> **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.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```hcl
|
||||
data "http" "example" {
|
||||
url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"
|
||||
|
||||
# Optional request headers
|
||||
request_headers {
|
||||
"Accept" = "application/json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `url` - (Required) The URL to request data from. This URL must respond with
|
||||
a `200 OK` response and a `text/*` or `application/json` Content-Type.
|
||||
|
||||
* `request_headers` - (Optional) A map of strings representing additional HTTP
|
||||
headers to include in the request.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `body` - The raw body of the HTTP response.
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "Provider: HTTP"
|
||||
sidebar_current: "docs-http-index"
|
||||
description: |-
|
||||
The HTTP provider interacts with HTTP servers.
|
||||
---
|
||||
|
||||
# HTTP Provider
|
||||
|
||||
The HTTP provider is a utility provider for interacting with generic HTTP
|
||||
servers as part of a Terraform configuration.
|
||||
|
||||
This provider requires no configuration. For information on the resources
|
||||
it provides, see the navigation bar.
|
|
@ -288,6 +288,10 @@
|
|||
<a href="/docs/providers/heroku/index.html">Heroku</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-http") %>>
|
||||
<a href="/docs/providers/http/index.html">HTTP</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-icinga2") %>>
|
||||
<a href="/docs/providers/icinga2/index.html">Icinga2</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%#= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/providers/index.html">All Providers</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-index") %>>
|
||||
<a href="/docs/providers/http/index.html">HTTP Provider</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-http-data-source") %>>
|
||||
<a href="/docs/providers/http/data_source.html">Data Source</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
Loading…
Reference in New Issue