diff --git a/dynatrace/api/iam/account_policy_service_client.go b/dynatrace/api/iam/account_policy_service_client.go index d100d53f9..06644d794 100644 --- a/dynatrace/api/iam/account_policy_service_client.go +++ b/dynatrace/api/iam/account_policy_service_client.go @@ -4,8 +4,8 @@ type AccountPolicyServiceClient struct { PolicyClient *BasePolicyServiceClient } -func NewAccountPolicyService(clientID string, accountID string, clientSecret string) *AccountPolicyServiceClient { - return &AccountPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret)} +func NewAccountPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *AccountPolicyServiceClient { + return &AccountPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret, tokenURL, endpointURL)} } func (me *AccountPolicyServiceClient) CREATE(policy *Policy) (string, error) { diff --git a/dynatrace/api/iam/base_policy_service_client.go b/dynatrace/api/iam/base_policy_service_client.go index c2911c9a5..e25b21b9f 100644 --- a/dynatrace/api/iam/base_policy_service_client.go +++ b/dynatrace/api/iam/base_policy_service_client.go @@ -9,6 +9,8 @@ type BasePolicyServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *BasePolicyServiceClient) ClientID() string { @@ -23,8 +25,16 @@ func (me *BasePolicyServiceClient) ClientSecret() string { return me.clientSecret } -func NewBasePolicyService(clientID string, accountID string, clientSecret string) *BasePolicyServiceClient { - return &BasePolicyServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *BasePolicyServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *BasePolicyServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewBasePolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *BasePolicyServiceClient { + return &BasePolicyServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func (me *BasePolicyServiceClient) CREATE(level PolicyLevel, levelID string, policy *Policy) (string, error) { @@ -32,7 +42,7 @@ func (me *BasePolicyServiceClient) CREATE(level PolicyLevel, levelID string, pol var responseBytes []byte client := NewIAMClient(me) - if responseBytes, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies", level, levelID), policy, 201, false); err != nil { + if responseBytes, err = client.POST(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies", me.endpointURL, level, levelID), policy, 201, false); err != nil { return "", err } @@ -49,7 +59,7 @@ func (me *BasePolicyServiceClient) GET(level PolicyLevel, levelID string, uuid s client := NewIAMClient(me) - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", level, levelID, uuid), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, level, levelID, uuid), 200, false); err != nil { return nil, err } @@ -65,7 +75,7 @@ func (me *BasePolicyServiceClient) UPDATE(level PolicyLevel, levelID string, pol client := NewIAMClient(me) - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", level, levelID, uuid), policy, 200, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, level, levelID, uuid), policy, 200, false); err != nil { return err } return nil @@ -85,7 +95,7 @@ func (me *BasePolicyServiceClient) List(level PolicyLevel, levelID string) ([]Po var err error var responseBytes []byte - if responseBytes, err = NewIAMClient(me).GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies", level, levelID), 200, false); err != nil { + if responseBytes, err = NewIAMClient(me).GET(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies", me.endpointURL, level, levelID), 200, false); err != nil { return nil, err } @@ -111,6 +121,6 @@ func (me *BasePolicyServiceClient) LIST(level PolicyLevel, levelID string) ([]st } func (me *BasePolicyServiceClient) DELETE(level PolicyLevel, levelID string, uuid string) error { - _, err := NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", level, levelID, uuid), 204, false) + _, err := NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, level, levelID, uuid), 204, false) return err } diff --git a/dynatrace/api/iam/bindings/service.go b/dynatrace/api/iam/bindings/service.go index 8b723e6fd..e34c455ae 100644 --- a/dynatrace/api/iam/bindings/service.go +++ b/dynatrace/api/iam/bindings/service.go @@ -17,6 +17,8 @@ type BindingServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *BindingServiceClient) ClientID() string { @@ -31,12 +33,20 @@ func (me *BindingServiceClient) ClientSecret() string { return me.clientSecret } -func NewPolicyService(clientID string, accountID string, clientSecret string) *BindingServiceClient { - return &BindingServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *BindingServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *BindingServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *BindingServiceClient { + return &BindingServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*bindings.PolicyBinding] { - return &BindingServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &BindingServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *BindingServiceClient) SchemaID() string { @@ -69,7 +79,7 @@ func (me *BindingServiceClient) Get(ctx context.Context, id string, v *bindings. client := iam.NewIAMClient(me) - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/groups/%s", levelType, levelID, groupID), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/groups/%s", me.endpointURL, levelType, levelID, groupID), 200, false); err != nil { return err } if err = json.Unmarshal(responseBytes, &v); err != nil { @@ -109,7 +119,7 @@ func (me *BindingServiceClient) Update(ctx context.Context, id string, bindings } bindings.PolicyIDs = policyIDs - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/groups/%s", levelType, levelID, groupID), bindings, 204, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/groups/%s", me.endpointURL, levelType, levelID, groupID), bindings, 204, false); err != nil { return err } return nil @@ -136,7 +146,7 @@ func (me *BindingServiceClient) List(ctx context.Context) (api.Stubs, error) { var responseBytes []byte client := iam.NewIAMClient(me) - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/env/v2/accounts/%s/environments", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/env/v2/accounts/%s/environments", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { return nil, err } @@ -145,7 +155,7 @@ func (me *BindingServiceClient) List(ctx context.Context) (api.Stubs, error) { return nil, err } - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/account/%s/bindings", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/repo/account/%s/bindings", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { return nil, err } @@ -167,7 +177,7 @@ func (me *BindingServiceClient) List(ctx context.Context) (api.Stubs, error) { } for _, environment := range envResponse.Data { - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/environment/%s/bindings", environment.ID), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/repo/environment/%s/bindings", me.endpointURL, environment.ID), 200, false); err != nil { return nil, err } @@ -200,7 +210,7 @@ func (me *BindingServiceClient) Delete(ctx context.Context, id string) error { return err } for _, policyID := range binding.PolicyIDs { - if _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/%s/%s", levelType, levelID, policyID, groupID), 204, false); err != nil { + if _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/%s/%s", me.endpointURL, levelType, levelID, policyID, groupID), 204, false); err != nil { return err } } diff --git a/dynatrace/api/iam/environment_policy_service_client.go b/dynatrace/api/iam/environment_policy_service_client.go index 5dfd3375a..a53d3628b 100644 --- a/dynatrace/api/iam/environment_policy_service_client.go +++ b/dynatrace/api/iam/environment_policy_service_client.go @@ -4,8 +4,8 @@ type EnvironmentPolicyServiceClient struct { PolicyClient *BasePolicyServiceClient } -func NewEnvironmentPolicyService(clientID string, accountID string, clientSecret string) *EnvironmentPolicyServiceClient { - return &EnvironmentPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret)} +func NewEnvironmentPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *EnvironmentPolicyServiceClient { + return &EnvironmentPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret, tokenURL, endpointURL)} } func (me *EnvironmentPolicyServiceClient) CREATE(policy *Policy) (string, error) { diff --git a/dynatrace/api/iam/global_policy_service_client.go b/dynatrace/api/iam/global_policy_service_client.go index 170b4723b..e27d52157 100644 --- a/dynatrace/api/iam/global_policy_service_client.go +++ b/dynatrace/api/iam/global_policy_service_client.go @@ -4,8 +4,8 @@ type GlobalPolicyServiceClient struct { PolicyClient *BasePolicyServiceClient } -func NewGlobalPolicyService(clientID string, accountID string, clientSecret string) *GlobalPolicyServiceClient { - return &GlobalPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret)} +func NewGlobalPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *GlobalPolicyServiceClient { + return &GlobalPolicyServiceClient{PolicyClient: NewBasePolicyService(clientID, accountID, clientSecret, tokenURL, endpointURL)} } func (me *GlobalPolicyServiceClient) GET(levelID string, uuid string) (*Policy, error) { diff --git a/dynatrace/api/iam/groups/service.go b/dynatrace/api/iam/groups/service.go index eeaa5433d..0bbaf59cf 100644 --- a/dynatrace/api/iam/groups/service.go +++ b/dynatrace/api/iam/groups/service.go @@ -29,6 +29,8 @@ type GroupServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *GroupServiceClient) ClientID() string { @@ -43,12 +45,20 @@ func (me *GroupServiceClient) ClientSecret() string { return me.clientSecret } -func NewGroupService(clientID string, accountID string, clientSecret string) settings.CRUDService[*groups.Group] { - return &GroupServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *GroupServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *GroupServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewGroupService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) settings.CRUDService[*groups.Group] { + return &GroupServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*groups.Group] { - return &GroupServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &GroupServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *GroupServiceClient) SchemaID() string { @@ -70,7 +80,7 @@ func (me *GroupServiceClient) Create(ctx context.Context, group *groups.Group) ( var responseBytes []byte client := iam.NewIAMClient(me) - if responseBytes, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), []*groups.Group{group}, 201, false); err != nil { + if responseBytes, err = client.POST(fmt.Sprintf("%s/iam/v1/accounts/%s/groups", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), []*groups.Group{group}, 201, false); err != nil { return nil, err } @@ -82,7 +92,7 @@ func (me *GroupServiceClient) Create(ctx context.Context, group *groups.Group) ( groupName := responseGroups[0].Name if len(group.Permissions) > 0 { - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID), group.Permissions, 200, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID), group.Permissions, 200, false); err != nil { return nil, err } } @@ -100,7 +110,7 @@ func (me *GroupServiceClient) Update(ctx context.Context, uuid string, group *gr var err error client := iam.NewIAMClient(me) - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), uuid), group, 200, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), uuid), group, 200, false); err != nil { return err } @@ -109,7 +119,7 @@ func (me *GroupServiceClient) Update(ctx context.Context, uuid string, group *gr if len(group.Permissions) > 0 { permissions = group.Permissions } - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), uuid), permissions, 200, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), uuid), permissions, 200, false); err != nil { return err } @@ -176,7 +186,7 @@ func (me *GroupServiceClient) listUnguarded() ([]*ListGroup, error) { client := iam.NewIAMClient(me) var response ListGroupsResponse accountID := strings.TrimPrefix(me.AccountID(), "urn:dtaccount:") - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups", accountID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/accounts/%s/groups", me.endpointURL, accountID), 200, false, &response); err != nil { return nil, err } return response.Items, nil @@ -192,7 +202,7 @@ func (me *GroupServiceClient) Get(ctx context.Context, id string, v *groups.Grou accountID := strings.TrimPrefix(me.AccountID(), "urn:dtaccount:") client := iam.NewIAMClient(me) var groupStub ListGroup - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", accountID, id), 200, false, &groupStub); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, accountID, id), 200, false, &groupStub); err != nil { return err } @@ -209,7 +219,7 @@ func (me *GroupServiceClient) Get(ctx context.Context, id string, v *groups.Grou } func (me *GroupServiceClient) Delete(ctx context.Context, id string) error { - _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), id), 200, false) + _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), id), 200, false) // data sources MAY have cached a list of group IDs // Updating the (publicly available) revision signals to them that either a CREATE or DELETE has happened since diff --git a/dynatrace/api/iam/oauth_client.go b/dynatrace/api/iam/oauth_client.go index 3d1712b3b..e9ee828d7 100644 --- a/dynatrace/api/iam/oauth_client.go +++ b/dynatrace/api/iam/oauth_client.go @@ -18,6 +18,8 @@ type Authenticator interface { ClientID() string AccountID() string ClientSecret() string + TokenURL() string + EndpointURL() string } var tokens = map[string]string{} @@ -36,6 +38,7 @@ var msgInvalidOAuthCredentials = "Invalid OAuth credentials" const errMsgClientIDMissing = ` No OAuth Client configured. Please specify either one of these environment variables: IAM_CLIENT_ID, DYNATRACE_IAM_CLIENT_ID, DT_IAM_CLIENT_ID, DT_CLIENT_ID, DYNATRACE_CLIENT_ID` const errMsgAccountIDMissing = ` No Account ID configured. Please specify either one of these environment variables: IAM_ACCOUNT_ID, DYNATRACE_IAM_ACCOUNT_ID, DT_IAM_ACCOUNT_ID, DT_ACCOUNT_ID, DYNATRACE_ACCOUNT_ID` const errMsgClientSecretMissing = ` No OAuth Client Secret configured. Please specify either one of these environment variables: IAM_CLIENT_SECRET, DYNATRACE_IAM_CLIENT_SECRET, DT_IAM_CLIENT_SECRET, DYNATRACE_CLIENT_SECRET, DT_CLIENT_SECRET` +const errMsgTokenURLMissing = ` No OAuth Token URL configured. Please specify either one of these environment variables: IAM_TOKEN_URL, DYNATRACE_IAM_TOKEN_URL, DT_IAM_TOKEN_URL, DYNATRACE_TOKEN_URL, DT_TOKEN_URL` func getBearer(auth Authenticator, forceNew bool) (string, error) { mutex.Lock() @@ -53,6 +56,10 @@ func getBearer(auth Authenticator, forceNew bool) (string, error) { if len(strings.TrimSpace(clientSecret)) == 0 { return "", errors.New(errMsgClientSecretMissing) } + tokenURL := auth.TokenURL() + if len(strings.TrimSpace(tokenURL)) == 0 { + return "", errors.New(errMsgTokenURLMissing) + } var httpReq *http.Request var httpRes *http.Response @@ -74,7 +81,7 @@ func getBearer(auth Authenticator, forceNew bool) (string, error) { ) payload := strings.NewReader(payloadStr) - if httpReq, err = http.NewRequest(http.MethodPost, "https://sso.dynatrace.com/sso/oauth2/token", payload); err != nil { + if httpReq, err = http.NewRequest(http.MethodPost, tokenURL, payload); err != nil { return "", err } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -91,7 +98,7 @@ func getBearer(auth Authenticator, forceNew bool) (string, error) { url.QueryEscape(auth.ClientID()), url.QueryEscape(""), ) - rest.Logger.Println("POST https://sso.dynatrace.com/sso/oauth2/token") + rest.Logger.Println("POST", tokenURL) rest.Logger.Println(" " + debugPayloadStr) rest.Logger.Println(" -> " + string(body)) } diff --git a/dynatrace/api/iam/permissions/service.go b/dynatrace/api/iam/permissions/service.go index 7165f7881..4f4f1f335 100644 --- a/dynatrace/api/iam/permissions/service.go +++ b/dynatrace/api/iam/permissions/service.go @@ -19,6 +19,8 @@ type PermissionServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *PermissionServiceClient) ClientID() string { @@ -33,12 +35,20 @@ func (me *PermissionServiceClient) ClientSecret() string { return me.clientSecret } -func NewPermissionService(clientID string, accountID string, clientSecret string) *PermissionServiceClient { - return &PermissionServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *PermissionServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *PermissionServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewPermissionService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *PermissionServiceClient { + return &PermissionServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*permissions.Permission] { - return &PermissionServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &PermissionServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *PermissionServiceClient) SchemaID() string { @@ -71,7 +81,7 @@ func (me *PermissionServiceClient) Create(ctx context.Context, permission *permi ScopeType: scopeType, Name: permission.Name, }} - if _, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), permission.GroupID), payload, 201, false); err != nil { + if _, err = client.POST(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), permission.GroupID), payload, 201, false); err != nil { return nil, err } @@ -97,7 +107,7 @@ func (me *PermissionServiceClient) Get(ctx context.Context, id string, v *permis scope := parts[2] scopeType := parts[3] - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID), 200, false); err != nil { return err } @@ -132,7 +142,7 @@ func (me *PermissionServiceClient) Update(ctx context.Context, email string, per } func (me *PermissionServiceClient) List(ctx context.Context) (api.Stubs, error) { - groupsService := groups.NewGroupService(me.clientID, me.accountID, me.clientSecret) + groupsService := groups.NewGroupService(me.clientID, me.accountID, me.clientSecret, me.tokenURL, me.endpointURL) groupStubs, err := groupsService.List(ctx) if err != nil { return nil, err @@ -147,7 +157,7 @@ func (me *PermissionServiceClient) List(ctx context.Context) (api.Stubs, error) accountID := strings.TrimPrefix(me.AccountID(), "urn:dtaccount:") var response GetGroupPermissionsResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions", accountID, groupID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions", me.endpointURL, accountID, groupID), 200, false, &response); err != nil { return nil, err } @@ -172,7 +182,7 @@ func (me *PermissionServiceClient) Delete(ctx context.Context, id string) error scope := parts[2] scopeType := parts[3] - _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/groups/%s/permissions?scope=%s&permission-name=%s&scope-type=%s", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID, url.QueryEscape(scope), url.QueryEscape(name), url.QueryEscape(scopeType)), 200, false) + _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/accounts/%s/groups/%s/permissions?scope=%s&permission-name=%s&scope-type=%s", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), groupID, url.QueryEscape(scope), url.QueryEscape(name), url.QueryEscape(scopeType)), 200, false) if err != nil && strings.Contains(err.Error(), fmt.Sprintf("Permission %s not found", id)) { return nil } diff --git a/dynatrace/api/iam/policies/policy_level.go b/dynatrace/api/iam/policies/policy_level.go index e38a4ed22..5d44ad86e 100644 --- a/dynatrace/api/iam/policies/policy_level.go +++ b/dynatrace/api/iam/policies/policy_level.go @@ -35,7 +35,7 @@ func CheckPolicyExists(auth iam.Authenticator, levelType string, levelID string, Name string `json:"name"` }{} client := iam.NewIAMClient(auth) - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", levelType, levelID, policyUUID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", auth.EndpointURL(), levelType, levelID, policyUUID), 200, false, &response); err != nil { // TODO: this is dirty. The IAM client unfortunately doesn't produce special kinds errors. string compare is the only option atm if strings.HasPrefix(err.Error(), "response code 404") { return false, "", nil @@ -208,7 +208,7 @@ func fetchGlobalPolicies(auth iam.Authenticator) (results chan *api.Stub) { }() var response ListPoliciesResponse - if err := iam.GET(client, "https://api.dynatrace.com/iam/v1/repo/global/global/policies", 200, false, &response); err != nil { + if err := iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/global/global/policies", auth.EndpointURL()), 200, false, &response); err != nil { return } @@ -291,14 +291,14 @@ func getEnvironmentIDs(auth iam.Authenticator) ([]string, error) { var err error var envResponse ListEnvResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/env/v2/accounts/%s/environments", accountID), 200, false, &envResponse); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/env/v2/accounts/%s/environments", auth.EndpointURL(), accountID), 200, false, &envResponse); err != nil { return nil, err } environmentIDs := []string{} for _, dataStub := range envResponse.Data { - if len(dataStub.ID) > 0 { // we don't trust api.dynatrace.com + if len(dataStub.ID) > 0 { environmentIDs = append(environmentIDs, dataStub.ID) } } diff --git a/dynatrace/api/iam/policies/service.go b/dynatrace/api/iam/policies/service.go index 05c9f9184..3a95099ae 100644 --- a/dynatrace/api/iam/policies/service.go +++ b/dynatrace/api/iam/policies/service.go @@ -19,6 +19,8 @@ type PolicyServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *PolicyServiceClient) ClientID() string { @@ -33,16 +35,24 @@ func (me *PolicyServiceClient) ClientSecret() string { return me.clientSecret } -func NewPolicyService(clientID string, accountID string, clientSecret string) *PolicyServiceClient { - return &PolicyServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *PolicyServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *PolicyServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *PolicyServiceClient { + return &PolicyServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*policies.Policy] { - return &PolicyServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &PolicyServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func ServiceWithGloabals(credentials *settings.Credentials) *PolicyServiceClient { - return &PolicyServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &PolicyServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *PolicyServiceClient) SchemaID() string { @@ -64,7 +74,7 @@ func (me *PolicyServiceClient) Create(ctx context.Context, v *policies.Policy) ( levelType, levelID := getLevel(v) client := iam.NewIAMClient(me) - if responseBytes, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies", levelType, levelID), v, 201, false); err != nil { + if responseBytes, err = client.POST(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies", me.endpointURL, levelType, levelID), v, 201, false); err != nil { return nil, err } var pcr PolicyCreateResponse @@ -111,7 +121,7 @@ func (me *PolicyServiceClient) get(ctx context.Context, id string, v *policies.P return nil } - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", levelType, levelID, uuid), 200, false, &v); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, levelType, levelID, uuid), 200, false, &v); err != nil { return err } if levelType == "account" { @@ -143,7 +153,7 @@ func (me *PolicyServiceClient) Update(ctx context.Context, id string, user *poli } client := iam.NewIAMClient(me) - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", levelType, levelID, uuid), user, 204, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, levelType, levelID, uuid), user, 204, false); err != nil { return err } return nil @@ -174,7 +184,7 @@ func listForEnvironment(auth iam.Authenticator, environmentID string) (results c client := iam.NewIAMClient(auth) var response ListPoliciesResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/environment/%s/policies", environmentID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/environment/%s/policies", auth.EndpointURL(), environmentID), 200, false, &response); err != nil { return } @@ -223,7 +233,7 @@ func listForAccount(auth iam.Authenticator) (results chan *api.Stub, err error) go func() { defer close(results) var response ListPoliciesResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/account/%s/policies", accountID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/account/%s/policies", auth.EndpointURL(), accountID), 200, false, &response); err != nil { return } @@ -326,7 +336,7 @@ func (me *PolicyServiceClient) Delete(ctx context.Context, id string) error { return err } - _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/policies/%s", levelType, levelID, uuid), 204, false) + _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/repo/%s/%s/policies/%s", me.endpointURL, levelType, levelID, uuid), 204, false) return err } diff --git a/dynatrace/api/iam/users/service.go b/dynatrace/api/iam/users/service.go index 071d68010..740d66646 100644 --- a/dynatrace/api/iam/users/service.go +++ b/dynatrace/api/iam/users/service.go @@ -17,6 +17,8 @@ type UserServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *UserServiceClient) ClientID() string { @@ -31,13 +33,21 @@ func (me *UserServiceClient) ClientSecret() string { return me.clientSecret } -func NewUserService(clientID string, accountID string, clientSecret string) *UserServiceClient { - return &UserServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *UserServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *UserServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewUserService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *UserServiceClient { + return &UserServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*users.User] { - return &UserServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &UserServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *UserServiceClient) SchemaID() string { @@ -48,7 +58,7 @@ func (me *UserServiceClient) Create(ctx context.Context, user *users.User) (*api var err error client := iam.NewIAMClient(me) - if _, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), user, 201, false); err != nil { + if _, err = client.POST(fmt.Sprintf("%s/iam/v1/accounts/%s/users", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), user, 201, false); err != nil { if err.Error() == "User already exists" { if err = me.Update(ctx, user.Email, user); err != nil { return nil, err @@ -62,7 +72,7 @@ func (me *UserServiceClient) Create(ctx context.Context, user *users.User) (*api if len(user.Groups) > 0 { groups = user.Groups } - if _, err = client.PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users/%s/groups", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), user.Email), groups, 200, false); err != nil { + if _, err = client.PUT(fmt.Sprintf("%s/iam/v1/accounts/%s/users/%s/groups", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), user.Email), groups, 200, false); err != nil { return nil, err } @@ -85,7 +95,7 @@ func (me *UserServiceClient) Get(ctx context.Context, email string, v *users.Use client := iam.NewIAMClient(me) - if responseBytes, err = client.GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users/%s", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), email), 200, false); err != nil { + if responseBytes, err = client.GET(fmt.Sprintf("%s/iam/v1/accounts/%s/users/%s", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), email), 200, false); err != nil { if err != nil && strings.Contains(err.Error(), fmt.Sprintf("User %s not found", email)) { return rest.Error{Code: 404, Message: err.Error()} } @@ -113,7 +123,7 @@ func (me *UserServiceClient) Update(ctx context.Context, email string, user *use if len(user.Groups) > 0 { groups = user.Groups } - if _, err = iam.NewIAMClient(me).PUT(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users/%s/groups", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), user.Email), groups, 200, false); err != nil { + if _, err = iam.NewIAMClient(me).PUT(fmt.Sprintf("%s/iam/v1/accounts/%s/users/%s/groups", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), user.Email), groups, 200, false); err != nil { return err } @@ -134,7 +144,7 @@ func (me *UserServiceClient) List(ctx context.Context) (api.Stubs, error) { var err error var responseBytes []byte - if responseBytes, err = iam.NewIAMClient(me).GET(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { + if responseBytes, err = iam.NewIAMClient(me).GET(fmt.Sprintf("%s/iam/v1/accounts/%s/users", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false); err != nil { return nil, err } @@ -150,7 +160,7 @@ func (me *UserServiceClient) List(ctx context.Context) (api.Stubs, error) { } func (me *UserServiceClient) Delete(ctx context.Context, email string) error { - _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/accounts/%s/users/%s", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), email), 200, false) + _, err := iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/accounts/%s/users/%s", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:"), email), 200, false) if err != nil && strings.Contains(err.Error(), fmt.Sprintf("User %s not found", email)) { return nil } diff --git a/dynatrace/api/iam/v2bindings/service.go b/dynatrace/api/iam/v2bindings/service.go index 33bf51c97..bd89923b6 100644 --- a/dynatrace/api/iam/v2bindings/service.go +++ b/dynatrace/api/iam/v2bindings/service.go @@ -16,6 +16,8 @@ type BindingServiceClient struct { clientID string accountID string clientSecret string + tokenURL string + endpointURL string } func (me *BindingServiceClient) ClientID() string { @@ -30,12 +32,20 @@ func (me *BindingServiceClient) ClientSecret() string { return me.clientSecret } -func NewPolicyService(clientID string, accountID string, clientSecret string) *BindingServiceClient { - return &BindingServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret} +func (me *BindingServiceClient) TokenURL() string { + return me.tokenURL +} + +func (me *BindingServiceClient) EndpointURL() string { + return me.endpointURL +} + +func NewPolicyService(clientID string, accountID string, clientSecret string, tokenURL string, endpointURL string) *BindingServiceClient { + return &BindingServiceClient{clientID: clientID, accountID: accountID, clientSecret: clientSecret, tokenURL: tokenURL, endpointURL: endpointURL} } func Service(credentials *settings.Credentials) settings.CRUDService[*bindings.PolicyBinding] { - return &BindingServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret} + return &BindingServiceClient{clientID: credentials.IAM.ClientID, accountID: credentials.IAM.AccountID, clientSecret: credentials.IAM.ClientSecret, tokenURL: credentials.IAM.TokenURL, endpointURL: credentials.IAM.EndpointURL} } func (me *BindingServiceClient) SchemaID() string { @@ -102,7 +112,7 @@ func (me *BindingServiceClient) Get(ctx context.Context, id string, v *bindings. policyUUIDStruct := struct { PolicyUuids []string `json:"policyUuids"` }{} - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/groups/%s", levelType, levelID, groupID), 200, false, &policyUUIDStruct); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/groups/%s", me.endpointURL, levelType, levelID, groupID), 200, false, &policyUUIDStruct); err != nil { return err } if levelType == "account" { @@ -115,7 +125,7 @@ func (me *BindingServiceClient) Get(ctx context.Context, id string, v *bindings. for _, policyID := range policyUUIDStruct.PolicyUuids { var bindingsResponse BindingsResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/%s/%s", levelType, levelID, policyID, groupID), 200, false, &bindingsResponse); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/%s/%s", me.endpointURL, levelType, levelID, policyID, groupID), 200, false, &bindingsResponse); err != nil { return err } if len(bindingsResponse.PolicyBindings) == 0 { @@ -173,7 +183,7 @@ func (me *BindingServiceClient) Update(ctx context.Context, id string, v *bindin for _, policy := range policiesList { policyUUID, _, _, _ := policies.SplitID(policy.ID, levelType, levelID) - if _, err = client.DELETE_MULTI_RESPONSE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/%s/%s", levelType, levelID, policyUUID, groupID), []int{204, 400, 404}, false); err != nil { + if _, err = client.DELETE_MULTI_RESPONSE(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/%s/%s", me.endpointURL, levelType, levelID, policyUUID, groupID), []int{204, 400, 404}, false); err != nil { return err } } @@ -186,8 +196,12 @@ func (me *BindingServiceClient) Update(ctx context.Context, id string, v *bindin Parameters: policy.Parameters, Metadata: policy.Metadata, } - if _, err = client.POST(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/%s/%s", levelType, levelID, policyUUID, groupID), payload, 204, false); err != nil { - return err + retries := 0 + for retries < 10 { + if _, err = client.POST(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/%s/%s", me.endpointURL, levelType, levelID, policyUUID, groupID), payload, 204, false); err != nil { + return err + } + break } } @@ -221,7 +235,7 @@ func (me *BindingServiceClient) FetchAccountBindings() chan *api.Stub { var response ListPolicyBindingsResponse client := iam.NewIAMClient(me) - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/account/%s/bindings", strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/account/%s/bindings", me.endpointURL, strings.TrimPrefix(me.AccountID(), "urn:dtaccount:")), 200, false, &response); err != nil { return } @@ -260,7 +274,7 @@ func (me *BindingServiceClient) FetchEnvironmentBindings() chan *api.Stub { var stubs api.Stubs for _, environmentID := range environmentIDs { var response ListPolicyBindingsResponse - if err = iam.GET(client, fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/environment/%s/bindings", environmentID), 200, false, &response); err != nil { + if err = iam.GET(client, fmt.Sprintf("%s/iam/v1/repo/environment/%s/bindings", me.endpointURL, environmentID), 200, false, &response); err != nil { return } @@ -336,7 +350,7 @@ func (me *BindingServiceClient) Delete(ctx context.Context, id string) error { policyUUIDs[policyUUID] = policyUUID } for policyUUID := range policyUUIDs { - if _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("https://api.dynatrace.com/iam/v1/repo/%s/%s/bindings/%s/%s", levelType, levelID, policyUUID, groupID), 204, false); err != nil { + if _, err = iam.NewIAMClient(me).DELETE(fmt.Sprintf("%s/iam/v1/repo/%s/%s/bindings/%s/%s", me.endpointURL, levelType, levelID, policyUUID, groupID), 204, false); err != nil { return err } } diff --git a/dynatrace/api/v2/hub/extension/config/service.go b/dynatrace/api/v2/hub/extension/config/service.go index fb1d2abdf..b44c0fb1d 100644 --- a/dynatrace/api/v2/hub/extension/config/service.go +++ b/dynatrace/api/v2/hub/extension/config/service.go @@ -58,8 +58,19 @@ func (me *service) List(ctx context.Context) (api.Stubs, error) { var extensionsList ExtensionsList client := rest.DefaultClient(me.credentials.URL, me.credentials.Token) - if err := client.Get("/api/v2/extensions/info", 200).Finish(&extensionsList); err != nil { - return stubs, err + nextPageKey := "first" + for len(nextPageKey) > 0 { + extensionsList = ExtensionsList{} + query := "" + if len(nextPageKey) > 0 && nextPageKey != "first" { + query = "?nextPageKey=" + url.QueryEscape(nextPageKey) + } else { + query = "?pageSize=100" + } + if err := client.Get("/api/v2/extensions/info"+query, 200).Finish(&extensionsList); err != nil { + return stubs, err + } + nextPageKey = extensionsList.NextPageKey } for _, extension := range extensionsList.Extensions { nextPageKey := "first" @@ -263,6 +274,7 @@ type ExtensionsList struct { Version string `json:"version"` ActiveVersion string `json:"activeVersion"` } `json:"extensions"` + NextPageKey string `json:"nextPageKey"` } type MonitoringConfigurationList struct { diff --git a/dynatrace/export.go b/dynatrace/export.go index ccc5229ab..9cb1cc25c 100644 --- a/dynatrace/export.go +++ b/dynatrace/export.go @@ -24,9 +24,10 @@ import ( "time" "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace/export" + "github.com/dynatrace-oss/terraform-provider-dynatrace/provider/config" ) -func Export(args []string) bool { +func Export(args []string, cfgGetter config.Getter) bool { if len(args) == 1 { return false } @@ -61,13 +62,13 @@ func Export(args []string) bool { } // defer export.CleanUp.Finish() - if err := runExport(); err != nil { + if err := runExport(cfgGetter); err != nil { fmt.Println(err.Error()) } return true } -func runExport() (err error) { +func runExport(cfgGetter config.Getter) (err error) { start := time.Now() defer func() { fmt.Printf("... finished after %v seconds\n", int64(time.Since(start).Seconds())) @@ -75,7 +76,7 @@ func runExport() (err error) { os.Remove("terraform-provider-dynatrace.export.log") os.Remove("terraform-provider-dynatrace.warnings.log") var environment *export.Environment - if environment, err = export.Initialize(); err != nil { + if environment, err = export.Initialize(cfgGetter); err != nil { return err } diff --git a/dynatrace/export/initialize.go b/dynatrace/export/initialize.go index df11ed255..9872711bc 100644 --- a/dynatrace/export/initialize.go +++ b/dynatrace/export/initialize.go @@ -18,6 +18,7 @@ package export import ( + "context" "errors" "flag" "fmt" @@ -26,9 +27,10 @@ import ( "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace/settings" "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace/settings/services/cache" + "github.com/dynatrace-oss/terraform-provider-dynatrace/provider/config" ) -func Initialize() (environment *Environment, err error) { +func Initialize(cfgGetter config.Getter) (environment *Environment, err error) { flags, tailArgs := createFlags() if flags.FlagMigrationOutput && flags.FollowReferences { return nil, errors.New("-ref and -migrate are mutually exclusive") @@ -161,7 +163,9 @@ func Initialize() (environment *Environment, err error) { } var credentials *settings.Credentials - if credentials, err = settings.CreateExportCredentials(); err != nil { + + configResult, _ := config.ProviderConfigureGeneric(context.Background(), cfgGetter) + if credentials, err = config.Credentials(configResult, config.CredValNone); err != nil { return nil, err } diff --git a/dynatrace/settings/credentials.go b/dynatrace/settings/credentials.go index c009d673b..ccd09b928 100644 --- a/dynatrace/settings/credentials.go +++ b/dynatrace/settings/credentials.go @@ -17,31 +17,15 @@ package settings -import ( - "errors" - "fmt" - "os" - "regexp" - "strings" -) - const ( ProdTokenURL = "https://sso.dynatrace.com/sso/oauth2/token" - SprintTokenURL = "https://sso.dynatracelabs.com/sso/oauth2/token" + SprintTokenURL = "https://sso-sprint.dynatracelabs.com/sso/oauth2/token" DevTokenURL = "https://sso-dev.dynatracelabs.com/sso/oauth2/token" -) -func GetEnv(names ...string) string { - if len(names) == 0 { - return "" - } - for _, name := range names { - if value := os.Getenv(name); len(value) > 0 { - return value - } - } - return "" -} + ProdIAMEndpointURL = "https://api.dynatrace.com" + SprintIAMEndpointURL = "https://api-hardening.internal.dynatracelabs.com" + DevIAMEndpointURL = "https://api-dev.internal.dynatracelabs.com" +) type Credentials struct { URL string @@ -50,6 +34,8 @@ type Credentials struct { ClientID string AccountID string ClientSecret string + TokenURL string + EndpointURL string } Automation struct { ClientID string @@ -62,182 +48,3 @@ type Credentials struct { Token string } } - -func getEnv(names ...string) string { - if len(names) == 0 { - return "" - } - for _, name := range names { - if value := os.Getenv(name); len(value) > 0 { - return value - } - } - return "" -} - -func CreateExportCredentials() (*Credentials, error) { - environmentURL := os.Getenv("DT_SOURCE_ENV_URL") - if len(environmentURL) == 0 { - environmentURL = os.Getenv("DYNATRACE_SOURCE_ENV_URL") - } - if environmentURL == "" { - environmentURL = os.Getenv("DYNATRACE_ENV_URL") - } - environmentURL = strings.TrimSuffix(strings.TrimSuffix(environmentURL, " "), "/") - if len(environmentURL) != 0 { - re := regexp.MustCompile(`https:\/\/(.*).(live|apps).dynatrace.com`) - if match := re.FindStringSubmatch(environmentURL); len(match) > 0 { - environmentURL = fmt.Sprintf("https://%s.live.dynatrace.com", match[1]) - } - } - - apiToken := os.Getenv("DT_SOURCE_API_TOKEN") - if len(apiToken) == 0 { - apiToken = os.Getenv("DYNATRACE_SOURCE_API_TOKEN") - } - if apiToken == "" { - apiToken = os.Getenv("DYNATRACE_API_TOKEN") - } - automationEnvironmentURL := os.Getenv("DT_AUTOMATION_ENVIRONMENT_URL") - if len(automationEnvironmentURL) == 0 { - automationEnvironmentURL = os.Getenv("DYNATRACE_AUTOMATION_ENVIRONMENT_URL") - } - automationTokenURL := os.Getenv("DT_AUTOMATION_TOKEN_URL") - if len(automationTokenURL) == 0 { - automationTokenURL = os.Getenv("DYNATRACE_AUTOMATION_TOKEN_URL") - } - if len(automationEnvironmentURL) == 0 { - re := regexp.MustCompile(`https:\/\/(.*).(live|apps).dynatrace.com`) - if match := re.FindStringSubmatch(environmentURL); len(match) > 0 { - automationEnvironmentURL = fmt.Sprintf("https://%s.apps.dynatrace.com", match[1]) - automationTokenURL = "https://sso.dynatrace.com/sso/oauth2/token" - } - } - if len(automationTokenURL) == 0 { - if strings.Contains(automationEnvironmentURL, ".live.dynatrace.com") || strings.Contains(automationEnvironmentURL, ".apps.dynatrace.com") { - automationTokenURL = ProdTokenURL - } else if strings.Contains(automationEnvironmentURL, ".sprint.dynatracelabs.com") || strings.Contains(automationEnvironmentURL, ".sprint.apps.dynatracelabs.com") { - automationTokenURL = SprintTokenURL - } else if strings.Contains(automationEnvironmentURL, ".dev.dynatracelabs.com") || strings.Contains(automationEnvironmentURL, ".dev.apps.dynatracelabs.com") { - automationTokenURL = DevTokenURL - } - } - - client_id := getEnv("DT_CLIENT_ID", "DYNATRACE_CLIENT_ID") - client_secret := getEnv("DYNATRACE_CLIENT_SECRET", "DT_CLIENT_SECRET") - account_id := getEnv("DT_ACCOUNT_ID", "DYNATRACE_ACCOUNT_ID") - - iam_client_id := getEnv("IAM_CLIENT_ID", "DYNATRACE_IAM_CLIENT_ID", "DT_IAM_CLIENT_ID", "DT_CLIENT_ID", "DYNATRACE_CLIENT_ID") - if len(iam_client_id) == 0 { - iam_client_id = client_id - } - iam_account_id := getEnv("IAM_ACCOUNT_ID", "DYNATRACE_IAM_ACCOUNT_ID", "DT_IAM_ACCOUNT_ID", "DT_ACCOUNT_ID", "DYNATRACE_ACCOUNT_ID") - if len(iam_account_id) == 0 { - iam_account_id = account_id - } - iam_client_secret := getEnv("IAM_CLIENT_SECRET", "DYNATRACE_IAM_CLIENT_SECRET", "DT_IAM_CLIENT_SECRET", "DYNATRACE_CLIENT_SECRET", "DT_CLIENT_SECRET") - if len(iam_client_secret) == 0 { - iam_client_secret = client_secret - } - - automation_client_id := getEnv("AUTOMATION_CLIENT_ID", "DYNATRACE_AUTOMATION_CLIENT_ID", "DT_AUTOMATION_CLIENT_ID", "DT_CLIENT_ID", "DYNATRACE_CLIENT_ID") - if len(automation_client_id) == 0 { - automation_client_id = client_id - } - automation_client_secret := getEnv("AUTOMATION_CLIENT_SECRET", "DYNATRACE_AUTOMATION_CLIENT_SECRET", "DT_AUTOMATION_CLIENT_SECRET", "DYNATRACE_CLIENT_SECRET", "DT_CLIENT_SECRET") - if len(automation_client_secret) == 0 { - automation_client_secret = client_secret - } - - clusterURL := getEnv("DYNATRACE_CLUSTER_URL", "DT_CLUSTER_URL") - for strings.HasSuffix(clusterURL, "/") { - clusterURL = strings.TrimSuffix(clusterURL, "/") - } - clusterAPIToken := getEnv("DYNATRACE_CLUSTER_API_TOKEN", "DT_CLUSTER_API_TOKEN") - - if environmentURL == "" && clusterURL == "" && iam_account_id == "" { - return nil, errors.New("the environment variable DYNATRACE_ENV_URL or DYNATRACE_SOURCE_ENV_URL needs to be set") - } - if apiToken == "" && clusterAPIToken == "" && iam_client_id == "" && iam_client_secret == "" { - return nil, errors.New("the environment variable DYNATRACE_API_TOKEN or DYNATRACE_SOURCE_API_TOKEN needs to be set") - } - - credentials := &Credentials{ - URL: environmentURL, - Token: apiToken, - IAM: struct { - ClientID string - AccountID string - ClientSecret string - }{ - ClientID: iam_client_id, - AccountID: iam_account_id, - ClientSecret: iam_client_secret, - }, - Automation: struct { - ClientID string - ClientSecret string - TokenURL string - EnvironmentURL string - }{ - ClientID: automation_client_id, - ClientSecret: automation_client_secret, - EnvironmentURL: automationEnvironmentURL, - TokenURL: automationTokenURL, - }, - Cluster: struct { - URL string - Token string - }{ - URL: clusterURL, - Token: clusterAPIToken, - }, - } - return credentials, nil -} - -func CreateCredentials() (*Credentials, error) { - environmentURL := os.Getenv("DYNATRACE_ENV_URL") - if environmentURL == "" { - return nil, errors.New("the environment variable DYNATRACE_ENV_URL needs to be set") - } - environmentURL = strings.TrimSuffix(strings.TrimSuffix(environmentURL, " "), "/") - apiToken := os.Getenv("DYNATRACE_API_TOKEN") - if apiToken == "" { - return nil, errors.New("the environment variable DYNATRACE_API_TOKEN needs to be set") - } - automationEnvironmentURL := os.Getenv("DT_AUTOMATION_ENVIRONMENT_URL") - automationTokenURL := os.Getenv("DT_AUTOMATION_TOKEN_URL") - if len(automationEnvironmentURL) == 0 { - re := regexp.MustCompile(`https:\/\/(.*).(live|apps).dynatrace.com`) - if match := re.FindStringSubmatch(environmentURL); len(match) > 0 { - automationEnvironmentURL = fmt.Sprintf("https://%s.apps.dynatrace.com", match[1]) - automationTokenURL = "https://sso.dynatrace.com/sso/oauth2/token" - } - } - credentials := &Credentials{ - URL: environmentURL, - Token: apiToken, - IAM: struct { - ClientID string - AccountID string - ClientSecret string - }{ - ClientID: os.Getenv("DT_CLIENT_ID"), - AccountID: os.Getenv("DT_ACCOUNT_ID"), - ClientSecret: os.Getenv("DT_CLIENT_SECRET"), - }, - Automation: struct { - ClientID string - ClientSecret string - TokenURL string - EnvironmentURL string - }{ - ClientID: os.Getenv("DT_AUTOMATION_CLIENT_ID"), - ClientSecret: os.Getenv("DT_AUTOMATION_CLIENT_SECRET"), - EnvironmentURL: automationEnvironmentURL, - TokenURL: automationTokenURL, - }, - } - return credentials, nil -} diff --git a/main.go b/main.go index c1a7fb441..ba179e683 100644 --- a/main.go +++ b/main.go @@ -24,90 +24,15 @@ import ( "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace" "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace/export" "github.com/dynatrace-oss/terraform-provider-dynatrace/provider" + "github.com/dynatrace-oss/terraform-provider-dynatrace/provider/config" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" ) -// func tf2json(args []string) bool { -// if len(args) == 1 { -// return false -// } -// if strings.TrimSpace(args[1]) != "-tf2json" { -// return false -// } - -// flag.Bool("tf2json", true, "") -// flag.Parse() -// tailArgs := flag.Args() - -// if len(tailArgs) == 0 { -// fmt.Println("Usage: terraform-provider-dynatrace -tf2json ") -// return true -// } - -// for _, filePath := range tailArgs { -// fileInfo, err := os.Stat(filePath) -// if err != nil { -// fmt.Println(err) -// return true -// } -// if !fileInfo.IsDir() { -// if err := tf2jsonFile(filePath, fileInfo, nil); err != nil { -// fmt.Println(err) -// return true -// } -// continue -// } - -// filepath.Walk(filePath, tf2jsonFile) -// } - -// return true -// } - -// func tf2jsonFile(childPath string, info os.FileInfo, err error) error { -// if err != nil { -// return err -// } -// if info.IsDir() { -// return nil -// } -// if !strings.HasSuffix(info.Name(), ".tf") { -// return nil -// } -// if info.Name() == "data_source.tf" { -// return nil -// } -// if info.Name() == "providers.tf" { -// return nil -// } -// if info.Name() == "main.tf" { -// return nil -// } -// if strings.Contains(childPath, ".flawed") { -// return nil -// } -// jsonPath := strings.TrimSuffix(childPath, info.Name()) + info.Name() + ".json" -// var module *hcl2json.Module -// if module, err = hcl2json.HCL2Config(childPath); err != nil { -// fmt.Println(err) -// return nil -// } -// var data []byte -// if data, err = json.MarshalIndent(module, "", " "); err != nil { -// return err -// } -// os.Remove(jsonPath) -// if err = os.WriteFile(jsonPath, data, 0644); err != nil { -// return err -// } -// return nil -// } - func main() { defer export.CleanUp.Finish() - if dynatrace.Export(os.Args) { + if dynatrace.Export(os.Args, config.ConfigGetter{Provider: provider.Provider()}) { return } diff --git a/provider/config/config.go b/provider/config/config.go index 1fe8e3c50..07b6705e8 100644 --- a/provider/config/config.go +++ b/provider/config/config.go @@ -36,6 +36,8 @@ type IAM struct { ClientID string AccountID string ClientSecret string + TokenURL string + EndpointURL string } type Automation struct { @@ -57,43 +59,46 @@ func validateCredentials(conf *ProviderConfiguration, CredentialValidation int) switch CredentialValidation { case CredValDefault: if len(conf.EnvironmentURL) == 0 { - return fmt.Errorf("No Environment URL has been specified. Use either the environment variable `DYNATRACE_ENV_URL` or the configuration attribute `dt_env_url` of the provider for that.") + return fmt.Errorf(" No Environment URL has been specified. Use either the environment variable `DYNATRACE_ENV_URL` or the configuration attribute `dt_env_url` of the provider for that") } if !strings.HasPrefix(conf.EnvironmentURL, "https://") && !strings.HasPrefix(conf.EnvironmentURL, "http://") { - return fmt.Errorf("The Environment URL `%s` neither starts with `https://` nor with `http://`. Please check your configuration.\nFor SaaS environments: `https://######.live.dynatrace.com`.\nFor Managed environments: `https://############/e/########-####-####-####-############`", conf.EnvironmentURL) + return fmt.Errorf(" The Environment URL `%s` neither starts with `https://` nor with `http://`. Please check your configuration.\nFor SaaS environments: `https://######.live.dynatrace.com`.\nFor Managed environments: `https://############/e/########-####-####-####-############`", conf.EnvironmentURL) } if len(conf.APIToken) == 0 { - return fmt.Errorf("No API Token has been specified. Use either the environment variable `DYNATRACE_API_TOKEN` or the configuration attribute `dt_api_token` of the provider for that.") + return fmt.Errorf(" No API Token has been specified. Use either the environment variable `DYNATRACE_API_TOKEN` or the configuration attribute `dt_api_token` of the provider for that") } case CredValIAM: if len(conf.IAM.AccountID) == 0 { - return fmt.Errorf("No OAuth Account ID has been specified. Use either the environment variable `DT_ACCOUNT_ID` or the configuration attribute `iam_account_id` of the provider for that.") + return fmt.Errorf(" No OAuth Account ID has been specified. Use either the environment variable `DT_ACCOUNT_ID` or the configuration attribute `iam_account_id` of the provider for that") } if len(conf.IAM.ClientID) == 0 { - return fmt.Errorf("No OAuth Client ID has been specified. Use either the environment variable `DT_CLIENT_ID` or the configuration attribute `iam_client_id` of the provider for that.") + return fmt.Errorf(" No OAuth Client ID has been specified. Use either the environment variable `DT_CLIENT_ID` or the configuration attribute `iam_client_id` of the provider for that") } if len(conf.IAM.ClientSecret) == 0 { - return fmt.Errorf("No OAuth Client Secret has been specified. Use either the environment variable `DT_CLIENT_SECRET` or the configuration attribute `iam_client_secret` of the provider for that.") + return fmt.Errorf(" No OAuth Client Secret has been specified. Use either the environment variable `DT_CLIENT_SECRET` or the configuration attribute `iam_client_secret` of the provider for that") + } + if len(conf.IAM.TokenURL) == 0 { + return fmt.Errorf(" No OAuth TokenURL has been specified. Use either the environment variable `DT_TOKEN_URL` or the configuration attribute `iam_token_url` of the provider for that") } case CredValCluster: if len(conf.ClusterAPIToken) == 0 { - return fmt.Errorf("No Cluster API Token has been specified. Use either the environment variable `DT_CLUSTER_API_TOKEN` or the configuration attribute `dt_cluster_api_token` of the provider for that.") + return fmt.Errorf(" No Cluster API Token has been specified. Use either the environment variable `DT_CLUSTER_API_TOKEN` or the configuration attribute `dt_cluster_api_token` of the provider for that") } if len(conf.ClusterAPIV2URL) == 0 { - return fmt.Errorf("No Cluster URL has been specified. Use either the environment variable `DT_CLUSTER_URL` or the configuration attribute `dt_cluster_url` of the provider for that.") + return fmt.Errorf(" No Cluster URL has been specified. Use either the environment variable `DT_CLUSTER_URL` or the configuration attribute `dt_cluster_url` of the provider for that") } case CredValAutomation: if len(conf.Automation.ClientID) == 0 { - return fmt.Errorf("No OAuth Client ID for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_CLIENT_ID` or the configuration attribute `automation_client_id` of the provider for that.") + return fmt.Errorf(" No OAuth Client ID for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_CLIENT_ID` or the configuration attribute `automation_client_id` of the provider for that") } if len(conf.Automation.ClientSecret) == 0 { - return fmt.Errorf("No OAuth Client Secret for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_CLIENT_SECRET` or the configuration attribute `automation_client_secret` of the provider for that.") + return fmt.Errorf(" No OAuth Client Secret for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_CLIENT_SECRET` or the configuration attribute `automation_client_secret` of the provider for that") } if len(conf.Automation.TokenURL) == 0 { - return fmt.Errorf("No Token URL for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_TOKEN_URL` or the configuration attribute `automation_token_url` of the provider for that.") + return fmt.Errorf(" No Token URL for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_TOKEN_URL` or the configuration attribute `automation_token_url` of the provider for that") } if len(conf.Automation.EnvironmentURL) == 0 { - return fmt.Errorf("No Environment URL for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_ENVIRONMENT_URL` or the configuration attribute `automation_env_url` of the provider for that.") + return fmt.Errorf(" No Environment URL for the Automation API has been specified. Use either the environment variable `DT_AUTOMATION_ENVIRONMENT_URL` or the configuration attribute `automation_env_url` of the provider for that") } } return nil @@ -162,39 +167,42 @@ func ProviderConfigureGeneric(ctx context.Context, d Getter) (any, diag.Diagnost fullNonConfigURL := dtEnvURL + "/api/v1" fullApiV2URL := dtEnvURL + "/api/v2" - automationEnvironmentURL := getString(d, "automation_env_url") - automationTokenURL := getString(d, "automation_token_url") - if len(automationEnvironmentURL) == 0 { + automation_environment_url := getString(d, "automation_env_url") + automation_token_url := getString(d, "automation_token_url") + if len(automation_environment_url) == 0 { if match := regexpSaasTenant.FindStringSubmatch(dtEnvURL); len(match) > 0 { - automationEnvironmentURL = fmt.Sprintf("https://%s.apps.dynatrace.com", match[1]) - automationTokenURL = "https://sso.dynatrace.com/sso/oauth2/token" + automation_environment_url = fmt.Sprintf("https://%s.apps.dynatrace.com", match[1]) + automation_token_url = settings.ProdTokenURL } if match := regexpSprintTenant.FindStringSubmatch(dtEnvURL); len(match) > 0 { - automationEnvironmentURL = fmt.Sprintf("https://%s.sprint.apps.dynatracelabs.com", match[1]) - automationTokenURL = "https://sso-sprint.dynatracelabs.com/sso/oauth2/token" + automation_environment_url = fmt.Sprintf("https://%s.sprint.apps.dynatracelabs.com", match[1]) + automation_token_url = settings.SprintTokenURL } if match := regexpDevTenant.FindStringSubmatch(dtEnvURL); len(match) > 0 { - automationEnvironmentURL = fmt.Sprintf("https://%s.dev.apps.dynatracelabs.com", match[1]) - automationTokenURL = "https://sso-dev.dynatracelabs.com/sso/oauth2/token" + automation_environment_url = fmt.Sprintf("https://%s.dev.apps.dynatracelabs.com", match[1]) + automation_token_url = settings.DevTokenURL } } client_id := getString(d, "client_id") client_secret := getString(d, "client_secret") account_id := getString(d, "account_id") + token_url := getString(d, "token_url") - iam_client_id := getString(d, "iam_client_id") - if len(iam_client_id) == 0 { - iam_client_id = client_id + oauth_endpoint_url := "https://api.dynatrace.com" + if strings.Contains(dtEnvURL, ".live.dynatrace.com") || strings.Contains(dtEnvURL, ".apps.dynatrace.com") { + oauth_endpoint_url = settings.ProdIAMEndpointURL + } else if strings.Contains(dtEnvURL, ".sprint.dynatracelabs.com") || strings.Contains(dtEnvURL, ".sprint.apps.dynatracelabs.com") { + oauth_endpoint_url = settings.SprintIAMEndpointURL + } else if strings.Contains(dtEnvURL, ".dev.dynatracelabs.com") || strings.Contains(dtEnvURL, ".dev.apps.dynatracelabs.com") { + oauth_endpoint_url = settings.DevIAMEndpointURL } + + iam_client_id := getString(d, "iam_client_id") iam_account_id := getString(d, "iam_account_id") - if len(iam_account_id) == 0 { - iam_account_id = account_id - } iam_client_secret := getString(d, "iam_client_secret") - if len(iam_client_secret) == 0 { - iam_client_secret = client_secret - } + iam_token_url := strings.TrimSuffix(strings.TrimSpace(getString(d, "iam_token_url")), "/") + iam_endpoint_url := strings.TrimSuffix(strings.TrimSpace(getString(d, "iam_endpoint_url")), "/") automation_client_id := getString(d, "automation_client_id") if len(automation_client_id) == 0 { @@ -205,6 +213,16 @@ func ProviderConfigureGeneric(ctx context.Context, d Getter) (any, diag.Diagnost automation_client_secret = client_secret } + automation_client_id = streamlineOAuthCreds(automation_client_id, client_id, iam_client_id) + automation_client_secret = streamlineOAuthCreds(automation_client_secret, client_secret, iam_client_secret) + automation_token_url = streamlineOAuthCreds(automation_token_url, token_url, iam_token_url) + + iam_client_id = streamlineOAuthCreds(iam_client_id, client_id, automation_client_id) + iam_client_secret = streamlineOAuthCreds(iam_client_secret, client_secret, automation_client_secret) + iam_token_url = streamlineOAuthCreds(iam_token_url, token_url, automation_token_url) + iam_account_id = streamlineOAuthCreds(iam_account_id, account_id) + iam_endpoint_url = streamlineOAuthCreds(iam_endpoint_url, oauth_endpoint_url) + var diags diag.Diagnostics return &ProviderConfiguration{ @@ -218,20 +236,53 @@ func ProviderConfigureGeneric(ctx context.Context, d Getter) (any, diag.Diagnost IAM: IAM{ ClientID: iam_client_id, AccountID: iam_account_id, - ClientSecret: getString(d, "iam_client_secret"), + ClientSecret: iam_client_secret, + TokenURL: iam_token_url, + EndpointURL: iam_endpoint_url, }, Automation: Automation{ - ClientID: getString(d, "automation_client_id"), - ClientSecret: getString(d, "automation_client_secret"), - TokenURL: automationTokenURL, - EnvironmentURL: automationEnvironmentURL, + ClientID: automation_client_id, + ClientSecret: automation_client_secret, + TokenURL: automation_token_url, + EnvironmentURL: automation_environment_url, }, }, diags } +func streamlineOAuthCreds(values ...string) string { + if len(values) == 0 { + return "" + } + for _, value := range values { + if len(value) != 0 { + return value + } + } + return "" +} + func getString(d Getter, key string) string { if value := d.Get(key); value != nil { return value.(string) } return "" } + +type ConfigGetter struct { + Provider *schema.Provider +} + +func (me ConfigGetter) Get(key string) any { + schema, found := me.Provider.Schema[key] + if !found { + return "" + } + if schema.DefaultFunc == nil { + return "" + } + result, _ := schema.DefaultFunc() + if result == nil { + return "" + } + return result +} diff --git a/provider/provider.go b/provider/provider.go index a6678bd7a..e278ddbd0 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -104,7 +104,7 @@ func Provider() *schema.Provider { "dt_env_url": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"DYNATRACE_ENV_URL", "DT_ENV_URL"}, nil), + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"DYNATRACE_ENV_URL", "DT_ENV_URL", "DYNATRACE_ENVIRONMENT_URL", "DT_ENVIRONMENT_URL"}, nil), }, "dt_api_token": { Type: schema.TypeString, @@ -160,6 +160,18 @@ func Provider() *schema.Provider { Sensitive: true, DefaultFunc: schema.MultiEnvDefaultFunc([]string{"IAM_CLIENT_SECRET", "DYNATRACE_IAM_CLIENT_SECRET", "DT_IAM_CLIENT_SECRET", "DYNATRACE_CLIENT_SECRET", "DT_CLIENT_SECRET"}, nil), }, + "iam_endpoint_url": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"IAM_ENDPOINT_URL", "DYNATRACE_IAM_ENDPOINT_URL", "DT_IAM_ENDPOINT_URL", "DYNATRACE_ENDPOINT_URL", "DT_ENDPOINT_URL"}, nil), + }, + "iam_token_url": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"IAM_TOKEN_URL", "DYNATRACE_IAM_TOKEN_URL", "DT_IAM_TOKEN_URL", "DYNATRACE_TOKEN_URL", "DT_TOKEN_URL"}, nil), + }, "automation_client_id": { Type: schema.TypeString, Optional: true, diff --git a/provider/provider_test.go b/provider/provider_test.go new file mode 100644 index 000000000..817e0cbc5 --- /dev/null +++ b/provider/provider_test.go @@ -0,0 +1,205 @@ +/** +* @license +* Copyright 2020 Dynatrace LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +package provider_test + +import ( + "context" + "os" + "testing" + + "github.com/dynatrace-oss/terraform-provider-dynatrace/dynatrace/settings" + "github.com/dynatrace-oss/terraform-provider-dynatrace/provider" + "github.com/dynatrace-oss/terraform-provider-dynatrace/provider/config" + "github.com/google/uuid" +) + +func TestIAMClientID(t *testing.T) { + assert := Assert{t} + provider := provider.Provider() + + for _, envVarName := range []string{ + "IAM_CLIENT_ID", "DYNATRACE_IAM_CLIENT_ID", "DT_IAM_CLIENT_ID", "DT_CLIENT_ID", "DYNATRACE_CLIENT_ID", + "AUTOMATION_CLIENT_ID", "DYNATRACE_AUTOMATION_CLIENT_ID", "DT_AUTOMATION_CLIENT_ID", + } { + t.Run(envVarName, func(t *testing.T) { + iam_client_id := uuid.NewString() + os.Setenv(envVarName, iam_client_id) + defer os.Unsetenv(envVarName) + + credentials := createCredentials(&config.ConfigGetter{provider}) + if credentials == nil { + return + } + if !assert.Equal(iam_client_id, credentials.IAM.ClientID, "credentials.IAM.ClientID") { + return + } + if !assert.Equal(iam_client_id, credentials.Automation.ClientID, "credentials.Automation.ClientID") { + return + } + }) + } +} + +func TestIAMClientSecret(t *testing.T) { + assert := Assert{t} + provider := provider.Provider() + + for _, envVarName := range []string{ + "IAM_CLIENT_SECRET", "DYNATRACE_IAM_CLIENT_SECRET", "DT_IAM_CLIENT_SECRET", "DYNATRACE_CLIENT_SECRET", "DT_CLIENT_SECRET", + "AUTOMATION_CLIENT_SECRET", "DYNATRACE_AUTOMATION_CLIENT_SECRET", "DT_AUTOMATION_CLIENT_SECRET", + } { + t.Run(envVarName, func(t *testing.T) { + iam_client_secret := uuid.NewString() + os.Setenv(envVarName, iam_client_secret) + defer os.Unsetenv(envVarName) + + credentials := createCredentials(&config.ConfigGetter{provider}) + if credentials == nil { + return + } + if !assert.Equal(iam_client_secret, credentials.IAM.ClientSecret, "credentials.IAM.ClientSecret") { + return + } + if !assert.Equal(iam_client_secret, credentials.Automation.ClientSecret, "credentials.Automation.ClientSecret") { + return + } + }) + } +} +func TestSSOTokenURL(t *testing.T) { + assert := Assert{t} + provider := provider.Provider() + + for _, envURL := range []string{ + "https://foo.live.dynatrace.com", + "https://foo.apps.dynatrace.com", + "https://foo.live.dynatrace.com ", + "https://foo.apps.dynatrace.com ", + "https://foo.live.dynatrace.com/", + "https://foo.apps.dynatrace.com/", + "https://foo.live.dynatrace.com/ ", + "https://foo.apps.dynatrace.com/ ", + } { + t.Run(envURL, func(t *testing.T) { + os.Setenv("DYNATRACE_ENV_URL", envURL) + defer os.Unsetenv("DYNATRACE_ENV_URL") + + credentials := createCredentials(&config.ConfigGetter{provider}) + if credentials == nil { + return + } + if !assert.Equal(settings.ProdTokenURL, credentials.IAM.TokenURL, "credentials.IAM.TokenURL") { + return + } + if !assert.Equal(settings.ProdIAMEndpointURL, credentials.IAM.EndpointURL, "credentials.IAM.EndpointURL") { + return + } + if !assert.Equal(settings.ProdTokenURL, credentials.Automation.TokenURL, "credentials.Automation.TokenURL") { + return + } + }) + } + for _, envURL := range []string{ + "https://foo.sprint.dynatracelabs.com", + "https://foo.sprint.apps.dynatracelabs.com", + "https://foo.sprint.dynatracelabs.com ", + "https://foo.sprint.apps.dynatracelabs.com ", + "https://foo.sprint.dynatracelabs.com/", + "https://foo.sprint.apps.dynatracelabs.com/", + "https://foo.sprint.dynatracelabs.com/ ", + "https://foo.sprint.apps.dynatracelabs.com/ ", + } { + t.Run(envURL, func(t *testing.T) { + os.Setenv("DYNATRACE_ENV_URL", envURL) + defer os.Unsetenv("DYNATRACE_ENV_URL") + + credentials := createCredentials(&config.ConfigGetter{provider}) + if credentials == nil { + return + } + if !assert.Equal(settings.SprintTokenURL, credentials.IAM.TokenURL, "credentials.IAM.TokenURL") { + return + } + if !assert.Equal(settings.SprintIAMEndpointURL, credentials.IAM.EndpointURL, "credentials.IAM.EndpointURL") { + return + } + if !assert.Equal(settings.SprintTokenURL, credentials.Automation.TokenURL, "credentials.Automation.TokenURL") { + return + } + }) + } + for _, envURL := range []string{ + "https://foo.dev.dynatracelabs.com", + "https://foo.dev.apps.dynatracelabs.com", + "https://foo.dev.dynatracelabs.com/", + "https://foo.dev.apps.dynatracelabs.com/", + "https://foo.dev.dynatracelabs.com ", + "https://foo.dev.apps.dynatracelabs.com ", + "https://foo.dev.dynatracelabs.com/ ", + "https://foo.dev.apps.dynatracelabs.com/ ", + } { + t.Run(envURL, func(t *testing.T) { + os.Setenv("DYNATRACE_ENV_URL", envURL) + defer os.Unsetenv("DYNATRACE_ENV_URL") + + credentials := createCredentials(&config.ConfigGetter{provider}) + if credentials == nil { + return + } + if !assert.Equal(settings.DevTokenURL, credentials.IAM.TokenURL, "credentials.IAM.TokenURL") { + return + } + if !assert.Equal(settings.DevIAMEndpointURL, credentials.IAM.EndpointURL, "credentials.IAM.EndpointURL") { + return + } + if !assert.Equal(settings.DevTokenURL, credentials.Automation.TokenURL, "credentials.Automation.TokenURL") { + return + } + }) + } +} + +func createCredentials(getter config.Getter) *settings.Credentials { + configResult, _ := config.ProviderConfigureGeneric(context.Background(), getter) + creds, _ := config.Credentials(configResult, config.CredValNone) + return creds +} + +type Assert struct { + t *testing.T +} + +func (a Assert) Bool(expected bool, message string) bool { + a.t.Helper() + if !expected { + a.t.Error(message) + a.t.Fail() + return false + } + return true +} + +func (a Assert) Equal(expected any, actual any, key string) bool { + a.t.Helper() + if expected != actual { + a.t.Errorf("value '%v' of '%s' differs from expected value '%v'", actual, key, expected) + a.t.Fail() + return false + } + return true +}