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
9 changes: 6 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,16 @@ provider "restapi" {
<a id="nestedblock--oauth_client_credentials"></a>
### Nested Schema for `oauth_client_credentials`

NOTE: One of `oauth_client_id_environment_variable` and `oauth_client_secret_environment_variable` or `oauth_client_id` and `oauth_client_secret` MUST be set if this block is configured. If both are set environment variables take priority.

Required:

- `oauth_client_id` (String) client id
- `oauth_client_secret` (String) client secret
- `oauth_token_endpoint` (String) oauth token endpoint

Optional:

- `oauth_client_id_environment_variable` (String) client id
- `oauth_client_secret_environment_variable` (String) client secret
- `oauth_client_id` (String) client id
- `oauth_client_secret` (String) client secret
- `endpoint_params` (Map of String) Additional key/values to pass to the underlying Oauth client library (as EndpointParams)
- `oauth_scopes` (List of String) scopes
22 changes: 22 additions & 0 deletions examples/workingexamples/provider_with_oauth_env.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
provider "restapi" {
alias = "restapi_oauth_env"
uri = "https://graph.microsoft.com/beta/"
write_returns_object = true
debug = true

oauth_client_credentials {
oauth_client_id_environment_variable = "ARM_CLIENT_ID"
oauth_client_secret_environment_variable = "ARM_CLIENT_SECRET"
oauth_token_endpoint = "https://login.microsoft.com/${var.tenantId}/oauth2/v2.0/token"
oauth_scopes = ["https://graph.microsoft.com/.default"]
endpoint_params = {"grant_type"="client_credentials"}
}

headers = {
"Content-Type" = "application/json"
}

create_method = "PUT"
update_method = "PATCH"
destroy_method = "DELETE"
}
84 changes: 49 additions & 35 deletions restapi/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,40 @@ import (
)

type apiClientOpt struct {
uri string
insecure bool
username string
password string
headers map[string]string
timeout int
idAttribute string
createMethod string
readMethod string
readData string
updateMethod string
updateData string
destroyMethod string
destroyData string
copyKeys []string
writeReturnsObject bool
createReturnsObject bool
xssiPrefix string
useCookies bool
rateLimit float64
oauthClientID string
oauthClientSecret string
oauthScopes []string
oauthTokenURL string
oauthEndpointParams url.Values
certFile string
keyFile string
rootCAFile string
certString string
keyString string
rootCAString string
debug bool
uri string
insecure bool
username string
password string
headers map[string]string
timeout int
idAttribute string
createMethod string
readMethod string
readData string
updateMethod string
updateData string
destroyMethod string
destroyData string
copyKeys []string
writeReturnsObject bool
createReturnsObject bool
xssiPrefix string
useCookies bool
rateLimit float64
oauthClientIDEnvVar string
oauthClientSecretEnvVar string
oauthClientID string
oauthClientSecret string
oauthScopes []string
oauthTokenURL string
oauthEndpointParams url.Values
certFile string
keyFile string
rootCAFile string
certString string
keyString string
rootCAString string
debug bool
}

/*APIClient is a HTTP client with additional controlling fields*/
Expand Down Expand Up @@ -82,6 +84,15 @@ type APIClient struct {
oauthConfig *clientcredentials.Config
}

// Helper function for optional environment imports
func GetEnvStringOrDefault(key, def string) string {
if env := os.Getenv(key); env != "" {
log.Printf("Got env for %s", key)
return env
}
return def
}

// NewAPIClient makes a new api client for RESTful calls
func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
if opt.debug {
Expand Down Expand Up @@ -205,10 +216,13 @@ func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
debug: opt.debug,
}

if opt.oauthClientID != "" && opt.oauthClientSecret != "" && opt.oauthTokenURL != "" {
resolvedClientID := GetEnvStringOrDefault(opt.oauthClientIDEnvVar, opt.oauthClientID)
resolvedClientSecret := GetEnvStringOrDefault(opt.oauthClientSecretEnvVar, opt.oauthClientSecret)

if resolvedClientID != "" && resolvedClientSecret != "" && opt.oauthTokenURL != "" {
client.oauthConfig = &clientcredentials.Config{
ClientID: opt.oauthClientID,
ClientSecret: opt.oauthClientSecret,
ClientID: resolvedClientID,
ClientSecret: resolvedClientSecret,
TokenURL: opt.oauthTokenURL,
Scopes: opt.oauthScopes,
EndpointParams: opt.oauthEndpointParams,
Expand Down
33 changes: 31 additions & 2 deletions restapi/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,25 @@ func Provider() *schema.Provider {
Description: "Configuration for oauth client credential flow using the https://pkg.go.dev/golang.org/x/oauth2 implementation",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"oauth_client_id_environment_variable": {
Type: schema.TypeString,
Description: "Name of client id environment variable, when using an environment variable to specify the client id",
Optional: true,
},
"oauth_client_secret_environment_variable": {
Type: schema.TypeString,
Description: "Name of client secret environment variable, when using an environment variable to specify the client id",
Optional: true,
},
"oauth_client_id": {
Type: schema.TypeString,
Description: "client id",
Required: true,
Optional: true,
},
"oauth_client_secret": {
Type: schema.TypeString,
Description: "client secret",
Required: true,
Optional: true,
},
"oauth_token_endpoint": {
Type: schema.TypeString,
Expand Down Expand Up @@ -214,6 +224,7 @@ func Provider() *schema.Provider {
"restapi_object": dataSourceRestAPI(),
},
ConfigureFunc: configureProvider,

}
}

Expand Down Expand Up @@ -267,6 +278,24 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) {
if v, ok := d.GetOk("oauth_client_credentials"); ok {
oauthConfig := v.([]interface{})[0].(map[string]interface{})

if (oauthConfig["oauth_client_id_environment_variable"] == "" && oauthConfig["oauth_client_secret_environment_variable"] == "" && oauthConfig["oauth_client_id"] == "" && oauthConfig["oauth_client_secret"] == "") {
return nil, fmt.Errorf("If configuring oauth, either `oauth_client_id_environment_variable`, `oauth_client_secret_environment_variable` OR `oauth_client_id` and `oauth_client_secret` must be specified")
}
if (oauthConfig["oauth_client_id_environment_variable"] != "" && oauthConfig["oauth_client_secret_environment_variable"] == "") {
return nil, fmt.Errorf("`oauth_client_id_environment_variable` is configured, but `oauth_client_secret_environment_variable` is missing")
}
if (oauthConfig["oauth_client_id_environment_variable"] == "" && oauthConfig["oauth_client_secret_environment_variable"] != "") {
return nil, fmt.Errorf("`oauth_client_secret_environment_variable` is configured, but `oauth_client_id_environment_variable` is missing")
}
if (oauthConfig["oauth_client_id"] != "" && oauthConfig["oauth_client_secret"] == "") {
return nil, fmt.Errorf("`oauth_client_id` is configured, but `oauth_client_secret` is missing")
}
if (oauthConfig["oauth_client_id"] == "" && oauthConfig["oauth_client_secret"] != "") {
return nil, fmt.Errorf("`oauth_client_secret` is configured, but `oauth_client_id` is missing")
}

opt.oauthClientIDEnvVar = oauthConfig["oauth_client_id_environment_variable"].(string)
opt.oauthClientSecretEnvVar = oauthConfig["oauth_client_secret_environment_variable"].(string)
opt.oauthClientID = oauthConfig["oauth_client_id"].(string)
opt.oauthClientSecret = oauthConfig["oauth_client_secret"].(string)
opt.oauthTokenURL = oauthConfig["oauth_token_endpoint"].(string)
Expand Down
21 changes: 21 additions & 0 deletions restapi/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ func TestResourceProvider_Oauth(t *testing.T) {
}
}

func TestResourceProvider_Oauth_Env(t *testing.T) {
rp := Provider()
raw := map[string]interface{}{
"uri": "http://foo.bar/baz",
"oauth_client_credentials": map[string]interface{}{
"oauth_client_id_environment_variable": "test",
"oauth_client_secret_environment_variable": "secret",
},
}

/*
XXX: This is expected to work even though we are not
explicitly declaring the required url parameter since
the test suite is run with the ENV entry set.
*/
err := rp.Configure(context.TODO(), terraform.NewResourceConfigRaw(raw))
if err != nil {
t.Fatalf("Provider failed with error: %v", err)
}
}

func TestResourceProvider_RequireTestPath(t *testing.T) {
debug := false
apiServerObjects := make(map[string]map[string]interface{})
Expand Down