Skip to content

Commit

Permalink
Chore: add User-Agent header (#111)
Browse files Browse the repository at this point in the history
* add User-Agent header

* use provider.UserAgent func

* set user-agent header on http client constructor

* fix tests

* add version to main serve provider

* fix tests structure after provider change

* use nicer types
  • Loading branch information
yaronya committed Jun 8, 2021
1 parent 3040cb9 commit 2e3c856
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 67 deletions.
16 changes: 12 additions & 4 deletions client/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ type HttpClient struct {
client *resty.Client
}

func NewHttpClient(apiKey string, apiSecret string, apiEndpoint string, restClient *resty.Client) (*HttpClient, error) {
type HttpClientConfig struct {
ApiKey string
ApiSecret string
ApiEndpoint string
UserAgent string
RestClient *resty.Client
}

func NewHttpClient(config HttpClientConfig) (*HttpClient, error) {
return &HttpClient{
ApiKey: apiKey,
ApiSecret: apiSecret,
client: restClient.SetHostURL(apiEndpoint),
ApiKey: config.ApiKey,
ApiSecret: config.ApiSecret,
client: config.RestClient.SetHostURL(config.ApiEndpoint).SetHeader("User-Agent", config.UserAgent),
}, nil
}

Expand Down
24 changes: 16 additions & 8 deletions client/http/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,24 @@ const BaseUrl = "https://fake.env0.com"
const ApiKey = "MY_USER"
const ApiSecret = "MY_PASS"
const ExpectedBasicAuth = "Basic TVlfVVNFUjpNWV9QQVNT"

const UserAgent = "super-cool-ua"
const ErrorStatusCode = 500
const ErrorMessage = "Very bad!"

var httpclient *httpModule.HttpClient

var _ = BeforeSuite(func() {
// mock all HTTP requests
restClient := resty.New()
httpclient, _ = httpModule.NewHttpClient(ApiKey, ApiSecret, BaseUrl, restClient)

config := httpModule.HttpClientConfig{
ApiKey: ApiKey,
ApiSecret: ApiSecret,
ApiEndpoint: BaseUrl,
UserAgent: UserAgent,
RestClient: restClient,
}
httpclient, _ = httpModule.NewHttpClient(config)
httpmock.ActivateNonDefault(restClient.GetClient())
})

Expand All @@ -58,7 +67,7 @@ func TestHttpClient(t *testing.T) {
var _ = Describe("Http Client", func() {
var httpRequest *http.Request

mockRequest := RequestBody {
mockRequest := RequestBody{
Message: "I have a request",
}
mockedResponse := ResponseType{
Expand All @@ -69,7 +78,6 @@ var _ = Describe("Http Client", func() {
failureURI := "/path/to/failure"
successUrl := BaseUrl + successURI
failureUrl := BaseUrl + failureURI


AssertAuth := func() {
authorization := httpRequest.Header["Authorization"]
Expand All @@ -82,19 +90,19 @@ var _ = Describe("Http Client", func() {
}

AssertError := func(err error) {
Expect(err.Error()).To(Equal(strconv.Itoa(ErrorStatusCode) + ": " + ErrorMessage), "Should return error message")
Expect(err.Error()).To(Equal(strconv.Itoa(ErrorStatusCode)+": "+ErrorMessage), "Should return error message")
}

AssertHttpCall := func(method string, url string) {
methodAndUrl := method + " " + url
// Validate call happened once
callMap := httpmock.GetCallCountInfo()
Expect(callMap[methodAndUrl]).Should(Equal(1), "Should call " + methodAndUrl)
Expect(callMap[methodAndUrl]).Should(Equal(1), "Should call "+methodAndUrl)

// Validate no other call happened
delete(callMap, methodAndUrl)
for unexpectedCall, amount := range callMap {
Expect(amount).To(BeZero(), "Should not call " + unexpectedCall)
Expect(amount).To(BeZero(), "Should not call "+unexpectedCall)
}
}

Expand Down Expand Up @@ -123,7 +131,7 @@ var _ = Describe("Http Client", func() {
})
httpmock.RegisterResponder(methodType, failureUrl, func(req *http.Request) (*http.Response, error) {
httpRequest = req
return httpmock.NewStringResponse(ErrorStatusCode, ErrorMessage), nil
return httpmock.NewStringResponse(ErrorStatusCode, ErrorMessage), nil
})
}
})
Expand Down
107 changes: 59 additions & 48 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,78 @@ package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/env0/terraform-provider-env0/client/http"
"github.com/go-resty/resty/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
)

func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"api_endpoint": {
Type: schema.TypeString,
Description: "override api endpoint (used for testing)",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_ENDPOINT", "https://api.env0.com/"),
Optional: true,
func Provider(version string) plugin.ProviderFunc {
return func() *schema.Provider {
provider := &schema.Provider{
Schema: map[string]*schema.Schema{
"api_endpoint": {
Type: schema.TypeString,
Description: "override api endpoint (used for testing)",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_ENDPOINT", "https://api.env0.com/"),
Optional: true,
},
"api_key": {
Type: schema.TypeString,
Description: "env0 api key (https://docs.env0.com/reference#authentication)",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_KEY", nil),
Required: true,
Sensitive: true,
},
"api_secret": {
Type: schema.TypeString,
Description: "env0 api key secret",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_SECRET", nil),
Required: true,
Sensitive: true,
},
},
"api_key": {
Type: schema.TypeString,
Description: "env0 api key (https://docs.env0.com/reference#authentication)",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_KEY", nil),
Required: true,
Sensitive: true,
DataSourcesMap: map[string]*schema.Resource{
"env0_organization": dataOrganization(),
"env0_project": dataProject(),
"env0_configuration_variable": dataConfigurationVariable(),
"env0_template": dataTemplate(),
"env0_ssh_key": dataSshKey(),
"env0_aws_credentials": dataAwsCredentials(),
},
"api_secret": {
Type: schema.TypeString,
Description: "env0 api key secret",
DefaultFunc: schema.EnvDefaultFunc("ENV0_API_SECRET", nil),
Required: true,
Sensitive: true,
ResourcesMap: map[string]*schema.Resource{
"env0_project": resourceProject(),
"env0_configuration_variable": resourceConfigurationVariable(),
"env0_template": resourceTemplate(),
"env0_ssh_key": resourceSshKey(),
"env0_aws_credentials": resourceAwsCredentials(),
"env0_template_project_assignment": resourceTemplateProjectAssignment(),
"env0_cloud_credentials_project_assignment": resourceCloudCredentialsProjectAssignment(),
},
},
DataSourcesMap: map[string]*schema.Resource{
"env0_organization": dataOrganization(),
"env0_project": dataProject(),
"env0_configuration_variable": dataConfigurationVariable(),
"env0_template": dataTemplate(),
"env0_ssh_key": dataSshKey(),
"env0_aws_credentials": dataAwsCredentials(),
},
ResourcesMap: map[string]*schema.Resource{
"env0_project": resourceProject(),
"env0_configuration_variable": resourceConfigurationVariable(),
"env0_template": resourceTemplate(),
"env0_ssh_key": resourceSshKey(),
"env0_aws_credentials": resourceAwsCredentials(),
"env0_template_project_assignment": resourceTemplateProjectAssignment(),
"env0_cloud_credentials_project_assignment": resourceCloudCredentialsProjectAssignment(),
},
ConfigureContextFunc: configureProvider,
}

provider.ConfigureContextFunc = configureProvider(version, provider)
return provider
}
}

func configureProvider(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
apiKey := d.Get("api_key")
apiSecret := d.Get("api_secret")
func configureProvider(version string, p *schema.Provider) schema.ConfigureContextFunc {
userAgent := p.UserAgent("terraform-provider-env0", version)

httpClient, err := http.NewHttpClient(apiKey.(string), apiSecret.(string), d.Get("api_endpoint").(string), resty.New())
if err != nil {
return nil, diag.Diagnostics{diag.Diagnostic{Severity: diag.Error, Summary: err.Error()}}
}
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
httpClient, err := http.NewHttpClient(http.HttpClientConfig{
ApiKey: d.Get("api_key").(string),
ApiSecret: d.Get("api_secret").(string),
ApiEndpoint: d.Get("api_endpoint").(string),
UserAgent: userAgent,
RestClient: resty.New(),
})
if err != nil {
return nil, diag.Diagnostics{diag.Diagnostic{Severity: diag.Error, Summary: err.Error()}}
}

return client.NewApiClient(httpClient), nil
return client.NewApiClient(httpClient), nil
}
}
8 changes: 4 additions & 4 deletions env0/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var (

var testUnitProviders = map[string]func() (*schema.Provider, error){
"env0": func() (*schema.Provider, error) {
provider := Provider()
provider := Provider("")()
provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
return apiClientMock, nil
}
Expand All @@ -49,7 +49,7 @@ func runUnitTest(t *testing.T, testCase resource.TestCase, mockFunc func(mockFun
}

func TestProvider(t *testing.T) {
if err := Provider().InternalValidate(); err != nil {
if err := Provider("")().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
Expand All @@ -74,12 +74,12 @@ func testMissingEnvVar(t *testing.T, envVars map[string]string, expectedKey stri
defer os.Setenv(key, "")
}

diags := Provider().Validate(&terraform.ResourceConfig{})
diags := Provider("")().Validate(&terraform.ResourceConfig{})
testExpectedProviderError(t, diags, expectedKey)
}

func testMissingConfig(t *testing.T, config map[string]interface{}, expectedKey string) {
diags := Provider().Validate(terraform.NewResourceConfigRaw(config))
diags := Provider("")().Validate(terraform.NewResourceConfigRaw(config))
testExpectedProviderError(t, diags, expectedKey)
}

Expand Down
29 changes: 26 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
package main

import (
"context"
"flag"
"github.com/env0/terraform-provider-env0/env0"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"log"
)

//go:generate terraform fmt -recursive ./examples/

var (
// these will be set by the goreleaser configuration
// to appropriate values for the compiled binary
version string = "dev"
)

func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: env0.Provider,
})
var debugMode bool
flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()

opts := &plugin.ServeOpts{ProviderFunc: env0.Provider(version)}

if debugMode {
err := plugin.Debug(context.Background(), "registry.terraform.io/env0/env0", opts)
if err != nil {
log.Fatal(err.Error())
}
return
}

plugin.Serve(opts)
}

0 comments on commit 2e3c856

Please sign in to comment.