diff --git a/go.mod b/go.mod index 34c89bb..b2b26c7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/hewlettpackard/hpegl-provider-lib go 1.18 require ( + github.com/davecgh/go-spew v1.1.1 github.com/golang/mock v1.6.0 github.com/golangci/golangci-lint v1.43.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 @@ -35,7 +36,6 @@ require ( github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348 // indirect github.com/daixiang0/gci v0.9.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingajkin/go-header v0.4.2 // indirect github.com/esimonov/ifshort v1.0.4 // indirect github.com/ettle/strcase v0.1.1 // indirect diff --git a/pkg/mocks/IdentityAPI_mocks.go b/pkg/mocks/IdentityAPI_mocks.go index a939148..2a95b86 100644 --- a/pkg/mocks/IdentityAPI_mocks.go +++ b/pkg/mocks/IdentityAPI_mocks.go @@ -35,16 +35,16 @@ func (m *MockIdentityAPI) EXPECT() *MockIdentityAPIMockRecorder { } // GenerateToken mocks base method. -func (m *MockIdentityAPI) GenerateToken(arg0 context.Context, arg1, arg2, arg3 string) (string, error) { +func (m *MockIdentityAPI) GenerateToken(arg0 context.Context, arg1, arg2, arg3, arg4 string) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GenerateToken", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "GenerateToken", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // GenerateToken indicates an expected call of GenerateToken. -func (mr *MockIdentityAPIMockRecorder) GenerateToken(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockIdentityAPIMockRecorder) GenerateToken(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateToken", reflect.TypeOf((*MockIdentityAPI)(nil).GenerateToken), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateToken", reflect.TypeOf((*MockIdentityAPI)(nil).GenerateToken), arg0, arg1, arg2, arg3, arg4) } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 87ef0a8..f48070b 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package provider @@ -11,6 +11,16 @@ import ( "github.com/hewlettpackard/hpegl-provider-lib/pkg/registration" ) +const ( + // IAMVersionGLCS is the IAM version for GLCS + IAMVersionGLCS = "glcs" + // IAMVersionGLP is the IAM version for GLP + IAMVersionGLP = "glp" +) + +// Update this list with any new IAM versions +var iamVersionList = [...]string{IAMVersionGLCS, IAMVersionGLP} + // ConfigureFunc is a type definition of a function that returns a ConfigureContextFunc object // A function of this type is passed in to NewProviderFunc below type ConfigureFunc func(p *schema.Provider) schema.ConfigureContextFunc @@ -81,9 +91,18 @@ func Schema() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("HPEGL_IAM_SERVICE_URL", "https://client.greenlake.hpe.com/api/iam"), - Description: `The IAM service URL to be used to generate tokens. In the case of API-vended API clients - (the default) then this should be set to the "issuer url" for the client. In the case of non-API-vended - API clients use the appropriate GL "client" URL. Can be set by HPEGL_IAM_SERVICE_URL env-var`, + Description: `The IAM service URL to be used to generate tokens. In the case of GLCS API clients + (the default) then this should be set to the "issuer url" for the client. In the case of GLP + API clients use the appropriate "Token URL" from the API screen. Can be set by HPEGL_IAM_SERVICE_URL env-var`, + } + + providerSchema["iam_version"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("HPEGL_IAM_VERSION", IAMVersionGLCS), + ValidateFunc: ValidateIAMVersion, + Description: `The IAM version to be used. Can be set by HPEGL_IAM_VERSION env-var. Valid values are: + ` + fmt.Sprintf("%v", iamVersionList) + `The default is ` + IAMVersionGLCS + `.`, } providerSchema["api_vended_service_client"] = &schema.Schema{ @@ -98,7 +117,7 @@ func Schema() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("HPEGL_TENANT_ID", ""), - Description: "The tenant-id to be used, can be set by HPEGL_TENANT_ID env-var", + Description: "The tenant-id to be used for GLCS IAM, can be set by HPEGL_TENANT_ID env-var", } providerSchema["user_id"] = &schema.Schema{ @@ -137,3 +156,23 @@ func convertToTypeSet(r *schema.Resource) *schema.Schema { Elem: r, } } + +// ValidateIAMVersion is a ValidateFunc for the "iam_version" field in the provider schema +func ValidateIAMVersion(v interface{}, k string) ([]string, []error) { + // check that v is in iamVersionList + found := false + for _, version := range iamVersionList { + if version == v.(string) { + found = true + break + } + } + + // add error if not found + es := make([]error, 0) + if !found { + es = append(es, fmt.Errorf("IAM version must be one of %v", iamVersionList)) + } + + return []string{}, es +} diff --git a/pkg/provider/provider_test.go b/pkg/provider/provider_test.go index 866d9ba..9beea4f 100644 --- a/pkg/provider/provider_test.go +++ b/pkg/provider/provider_test.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package provider @@ -8,8 +8,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hewlettpackard/hpegl-provider-lib/pkg/registration" "github.com/stretchr/testify/assert" + + "github.com/hewlettpackard/hpegl-provider-lib/pkg/registration" ) func testResource() *schema.Resource { @@ -170,3 +171,41 @@ func TestNewProviderFunc(t *testing.T) { }) } } + +func TestValidateIAMVersion(t *testing.T) { + t.Parallel() + testcases := []struct { + name string + version string + hasError bool + }{ + { + name: "valid IAM version GLCS", + version: IAMVersionGLCS, + hasError: false, + }, + { + name: "valid IAM version GLP", + version: IAMVersionGLP, + hasError: false, + }, + { + name: "invalid IAM version", + version: "invalid", + hasError: true, + }, + } + + for _, testcase := range testcases { + tc := testcase + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + _, es := ValidateIAMVersion(tc.version, "iam_version") + if tc.hasError { + assert.NotEmpty(t, es) + } else { + assert.Empty(t, es) + } + }) + } +} diff --git a/pkg/token/httpclient/httpclient.go b/pkg/token/httpclient/httpclient.go index b1f696a..e9327bb 100644 --- a/pkg/token/httpclient/httpclient.go +++ b/pkg/token/httpclient/httpclient.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package httpclient @@ -33,11 +33,12 @@ func New(identityServiceURL string, vendedServiceClient bool, passedInToken stri } } -func (c *Client) GenerateToken(ctx context.Context, tenantID, clientID, clientSecret string) (string, error) { +func (c *Client) GenerateToken(ctx context.Context, tenantID, clientID, clientSecret, iamVersion string) (string, error) { // we don't have a passed-in token, so we need to actually generate a token if c.passedInToken == "" { if c.vendedServiceClient { - token, err := issuertoken.GenerateToken(ctx, tenantID, clientID, clientSecret, c.identityServiceURL, c.httpClient) + token, err := issuertoken.GenerateToken( + ctx, clientID, clientSecret, c.identityServiceURL, iamVersion, c.httpClient) return token, err } diff --git a/pkg/token/httpclient/httpclient_test.go b/pkg/token/httpclient/httpclient_test.go index d76d94c..f31c1ce 100644 --- a/pkg/token/httpclient/httpclient_test.go +++ b/pkg/token/httpclient/httpclient_test.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP //nolint:structcheck package httpclient @@ -11,9 +11,11 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + + "github.com/hewlettpackard/hpegl-provider-lib/pkg/provider" "github.com/hewlettpackard/hpegl-provider-lib/pkg/token/identitytoken" "github.com/hewlettpackard/hpegl-provider-lib/pkg/token/issuertoken" - "github.com/stretchr/testify/assert" ) type testCaseIssuer struct { @@ -205,7 +207,7 @@ func TestGenerateToken(t *testing.T) { c = createTestClient(tc.url, "", tc.statusCode, tc.token, true) - token, err := c.GenerateToken(tc.ctx, "", "", "") + token, err := c.GenerateToken(tc.ctx, "", "", "", provider.IAMVersionGLCS) if tc.err != nil { assert.EqualError(t, err, tc.err.Error()) } @@ -219,7 +221,7 @@ func TestGenerateToken(t *testing.T) { c = createTestClient(tc.url, "", tc.statusCode, tc.token, false) - token, err := c.GenerateToken(tc.ctx, "", "", "") + token, err := c.GenerateToken(tc.ctx, "", "", "", provider.IAMVersionGLCS) if tc.err != nil { assert.EqualError(t, err, tc.err.Error()) } @@ -232,7 +234,7 @@ func TestGenerateTokenPassedInToken(t *testing.T) { t.Parallel() c := createTestClient("", "testToken", http.StatusAccepted, nil, true) - token, err := c.GenerateToken(context.Background(), "", "", "") + token, err := c.GenerateToken(context.Background(), "", "", "", "") assert.Equal(t, "testToken", token) assert.NoError(t, err) } diff --git a/pkg/token/issuertoken/issuertoken.go b/pkg/token/issuertoken/issuertoken.go index 708bfec..aed65d7 100644 --- a/pkg/token/issuertoken/issuertoken.go +++ b/pkg/token/issuertoken/issuertoken.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021-2023 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package issuertoken @@ -11,6 +11,7 @@ import ( "net/url" "strings" + "github.com/hewlettpackard/hpegl-provider-lib/pkg/provider" tokenutil "github.com/hewlettpackard/hpegl-provider-lib/pkg/token/token-util" ) @@ -27,25 +28,26 @@ type TokenResponse struct { func GenerateToken( ctx context.Context, - tenantID, clientID, clientSecret string, identityServiceURL string, + iamVersion string, httpClient tokenutil.HttpClient, ) (string, error) { - params := url.Values{} - params.Add("client_id", clientID) - params.Add("client_secret", clientSecret) - params.Add("grant_type", "client_credentials") - params.Add("scope", "hpe-tenant") + // Generate the parameters and URL for the request + params, clientURL, err := generateParamsAndURL(clientID, clientSecret, identityServiceURL, iamVersion) + if err != nil { + return "", err + } - url := fmt.Sprintf("%s/v1/token", identityServiceURL) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(params.Encode())) + // Create the request + req, err := http.NewRequestWithContext(ctx, http.MethodPost, clientURL, strings.NewReader(params.Encode())) if err != nil { return "", err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + // Execute the request, with retries resp, err := tokenutil.DoRetries(func() (*http.Response, error) { return httpClient.Do(req) }, retryLimit) @@ -73,3 +75,29 @@ func GenerateToken( return token.AccessToken, nil } + +// generateParamsAndURL generates the parameters and URL for the request +func generateParamsAndURL(clientID, clientSecret, identityServiceURL, iamVersion string) (url.Values, string, error) { + params := url.Values{} + + // Add common parameters for an API Client + params.Add("client_id", clientID) + params.Add("client_secret", clientSecret) + params.Add("grant_type", "client_credentials") + + // Add specific parameters and generate URL for the IAM version + var clientURL string + switch iamVersion { + case provider.IAMVersionGLCS: + params.Add("scope", "hpe-tenant") + clientURL = fmt.Sprintf("%s/v1/token", identityServiceURL) + + case provider.IAMVersionGLP: + clientURL = identityServiceURL + + default: + return nil, "", fmt.Errorf("invalid IAM version") + } + + return params, clientURL, nil +} diff --git a/pkg/token/issuertoken/issuertoken_test.go b/pkg/token/issuertoken/issuertoken_test.go new file mode 100644 index 0000000..071c2f5 --- /dev/null +++ b/pkg/token/issuertoken/issuertoken_test.go @@ -0,0 +1,49 @@ +// (C) Copyright 2024 Hewlett Packard Enterprise Development LP + +package issuertoken + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/hewlettpackard/hpegl-provider-lib/pkg/provider" +) + +func TestGenerateParamsAndURL(t *testing.T) { + t.Parallel() + testcases := []struct { + name string + iamVersion string + hasError bool + }{ + { + name: "valid IAM version GLCS", + iamVersion: provider.IAMVersionGLCS, + hasError: false, + }, + { + name: "valid IAM version GLP", + iamVersion: provider.IAMVersionGLP, + hasError: false, + }, + { + name: "invalid IAM version", + iamVersion: "invalid", + hasError: true, + }, + } + + for _, testcase := range testcases { + tc := testcase + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + _, _, err := generateParamsAndURL("clientID", "clientSecret", "identityServiceURL", tc.iamVersion) + if tc.hasError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} diff --git a/pkg/token/serviceclient/handler.go b/pkg/token/serviceclient/handler.go index 6527a5b..a87dbe1 100644 --- a/pkg/token/serviceclient/handler.go +++ b/pkg/token/serviceclient/handler.go @@ -1,10 +1,11 @@ -// (C) Copyright 2021 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package serviceclient import ( "context" "errors" + "github.com/davecgh/go-spew/spew" "net" "time" @@ -20,7 +21,7 @@ var _ common.TokenChannelInterface = (*Handler)(nil) //go:generate mockgen -build_flags=-mod=mod -destination=../../mocks/IdentityAPI_mocks.go -package=mocks github.com/hewlettpackard/hpegl-provider-lib/pkg/token/serviceclient IdentityAPI type IdentityAPI interface { - GenerateToken(context.Context, string, string, string) (string, error) + GenerateToken(context.Context, string, string, string, string) (string, error) } // Handler the handler for service-client creds @@ -30,6 +31,7 @@ type Handler struct { tenantID string clientID string clientSecret string + iamVersion string vendedServiceClient bool numRetries int client IdentityAPI @@ -54,12 +56,14 @@ type resourceData interface { // NewHandler creates a new handler and returns the common.TokenChannelInterface interface // Param resourceData can be *schema.ResourceData or any model which implements resourceData +// //nolint:forcetypeassert func NewHandler(d resourceData, opts ...CreateOpt) (common.TokenChannelInterface, error) { h := new(Handler) // set Handler fields h.iamServiceURL = d.Get("iam_service_url").(string) + h.iamVersion = d.Get("iam_version").(string) h.tenantID = d.Get("tenant_id").(string) h.clientID = d.Get("user_id").(string) h.clientSecret = d.Get("user_secret").(string) @@ -149,6 +153,7 @@ func (h *Handler) retrieveToken() common.Result { } // If token is about to expire in TimeToTokenExpiry seconds or less generate a new one + spew.Dump(tokenDetails) if tokenDetails.Expiry-now <= common.TimeToTokenExpiry { token, retry, err := h.generateToken() if retry { @@ -178,7 +183,7 @@ func (h *Handler) generateToken() (string, bool, error) { var err error // TODO pass a context down to here - token, err = h.client.GenerateToken(context.Background(), h.tenantID, h.clientID, h.clientSecret) + token, err = h.client.GenerateToken(context.Background(), h.tenantID, h.clientID, h.clientSecret, h.iamVersion) // If this is a retryable error check to see if we've reached our retryLimit or not, if we can retry again // return true diff --git a/pkg/token/serviceclient/handler_test.go b/pkg/token/serviceclient/handler_test.go index 5c4ca37..1401b3a 100644 --- a/pkg/token/serviceclient/handler_test.go +++ b/pkg/token/serviceclient/handler_test.go @@ -1,4 +1,4 @@ -// (C) Copyright 2021-2023 Hewlett Packard Enterprise Development LP +// (C) Copyright 2021-2024 Hewlett Packard Enterprise Development LP package serviceclient_test @@ -103,7 +103,7 @@ func TestHandler(t *testing.T) { assert.NoError(t, err) testToken := generateTestToken(600) - mock.EXPECT().GenerateToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(testToken, tc.err).MaxTimes(8) + mock.EXPECT().GenerateToken(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(testToken, tc.err).MaxTimes(8) handler, err := serviceclient.NewHandler(d, serviceclient.WithIdentityAPI(mock)) assert.NoError(t, err)