Browse Source

Implement validate for gitlab provider

Corey Hinkle 4 years ago
parent
commit
fae1f80e0c
3 changed files with 71 additions and 10 deletions
  1. 12 3
      pkg/provider/gitlab/fake/fake.go
  2. 12 5
      pkg/provider/gitlab/gitlab.go
  3. 47 2
      pkg/provider/gitlab/gitlab_test.go

+ 12 - 3
pkg/provider/gitlab/fake/fake.go

@@ -18,14 +18,19 @@ import (
 )
 
 type GitlabMockClient struct {
-	getVariable func(pid interface{}, key string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error)
+	getVariable   func(pid interface{}, key string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error)
+	listVariables func(pid interface{}, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectVariable, *gitlab.Response, error)
 }
 
 func (mc *GitlabMockClient) GetVariable(pid interface{}, key string, opt *gitlab.GetProjectVariableOptions, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error) {
 	return mc.getVariable(pid, key, nil)
 }
 
-func (mc *GitlabMockClient) WithValue(projectIDinput, keyInput string, output *gitlab.ProjectVariable, err error) {
+func (mc *GitlabMockClient) ListVariables(pid interface{}, opt *gitlab.ListProjectVariablesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectVariable, *gitlab.Response, error) {
+	return mc.listVariables(pid)
+}
+
+func (mc *GitlabMockClient) WithValue(projectIDinput, keyInput string, output *gitlab.ProjectVariable, response *gitlab.Response, err error) {
 	if mc != nil {
 		mc.getVariable = func(pid interface{}, key string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error) {
 			// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields
@@ -33,7 +38,11 @@ func (mc *GitlabMockClient) WithValue(projectIDinput, keyInput string, output *g
 			// if !cmp.Equal(paramReq, input, cmpopts.IgnoreUnexported(gitlab.ProjectVariable{})) {
 			// 	return nil, nil, fmt.Errorf("unexpected test argument")
 			// }
-			return output, nil, err
+			return output, response, err
+		}
+
+		mc.listVariables = func(pid interface{}, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectVariable, *gitlab.Response, error) {
+			return []*gitlab.ProjectVariable{output}, response, err
 		}
 	}
 }

+ 12 - 5
pkg/provider/gitlab/gitlab.go

@@ -17,8 +17,8 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"net/http"
 	"strings"
-	"time"
 
 	"github.com/tidwall/gjson"
 	gitlab "github.com/xanzy/go-gitlab"
@@ -38,6 +38,8 @@ const (
 	errInvalidClusterStoreMissingSAKNamespace = "invalid clusterStore missing SAK namespace"
 	errFetchSAKSecret                         = "couldn't find secret on cluster: %w"
 	errMissingSAK                             = "missing credentials while setting auth"
+	errList                                   = "could not verify if the client is valid: %w"
+	errAuth                                   = "client is not allowed to get secrets"
 	errUninitalizedGitlabProvider             = "provider gitlab is not initialized"
 	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
 )
@@ -48,6 +50,7 @@ var _ esv1beta1.Provider = &Gitlab{}
 
 type Client interface {
 	GetVariable(pid interface{}, key string, opt *gitlab.GetProjectVariableOptions, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error)
+	ListVariables(pid interface{}, opt *gitlab.ListProjectVariablesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectVariable, *gitlab.Response, error)
 }
 
 // Gitlab Provider struct with reference to a GitLab client and a projectID.
@@ -222,11 +225,15 @@ func (g *Gitlab) Close(ctx context.Context) error {
 	return nil
 }
 
+// Validate will use the gitlab client to validate the gitlab provider using the ListVariable call to ensure get permissions without needing a specific key.
 func (g *Gitlab) Validate() error {
-	timeout := 15 * time.Second
-	url := g.url
-
-	return utils.NetworkValidate(url, timeout)
+	_, resp, err := g.client.ListVariables(g.projectID, nil)
+	if err != nil {
+		return fmt.Errorf(errList, err)
+	} else if resp == nil || resp.StatusCode != http.StatusOK {
+		return fmt.Errorf(errAuth)
+	}
+	return nil
 }
 
 func (g *Gitlab) ValidateStore(store esv1beta1.GenericStore) error {

+ 47 - 2
pkg/provider/gitlab/gitlab_test.go

@@ -16,6 +16,7 @@ package gitlab
 import (
 	"context"
 	"fmt"
+	"net/http"
 	"reflect"
 	"strings"
 	"testing"
@@ -31,6 +32,7 @@ type secretManagerTestCase struct {
 	apiInputProjectID string
 	apiInputKey       string
 	apiOutput         *gitlab.ProjectVariable
+	apiResponse       *gitlab.Response
 	ref               *esv1beta1.ExternalSecretDataRemoteRef
 	projectID         *string
 	apiErr            error
@@ -48,12 +50,13 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
 		ref:               makeValidRef(),
 		projectID:         nil,
 		apiOutput:         makeValidAPIOutput(),
+		apiResponse:       makeValidAPIResponse(),
 		apiErr:            nil,
 		expectError:       "",
 		expectedSecret:    "",
 		expectedData:      map[string][]byte{},
 	}
-	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiErr)
+	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
 	return &smtc
 }
 
@@ -72,6 +75,14 @@ func makeValidAPIInputKey() string {
 	return "testKey"
 }
 
+func makeValidAPIResponse() *gitlab.Response {
+	return &gitlab.Response{
+		Response: &http.Response{
+			StatusCode: http.StatusOK,
+		},
+	}
+}
+
 func makeValidAPIOutput() *gitlab.ProjectVariable {
 	return &gitlab.ProjectVariable{
 		Key:   "testKey",
@@ -84,7 +95,7 @@ func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTest
 	for _, fn := range tweaks {
 		fn(smtc)
 	}
-	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiErr)
+	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
 	return smtc
 }
 
@@ -95,6 +106,22 @@ var setAPIErr = func(smtc *secretManagerTestCase) {
 	smtc.expectError = "oh no"
 }
 
+var setListAPIErr = func(smtc *secretManagerTestCase) {
+	err := fmt.Errorf("oh no")
+	smtc.apiErr = err
+	smtc.expectError = fmt.Errorf(errList, err).Error()
+}
+
+var setListAPIRespNil = func(smtc *secretManagerTestCase) {
+	smtc.apiResponse = nil
+	smtc.expectError = errAuth
+}
+
+var setListAPIRespBadCode = func(smtc *secretManagerTestCase) {
+	smtc.apiResponse.StatusCode = http.StatusUnauthorized
+	smtc.expectError = errAuth
+}
+
 var setNilMockClient = func(smtc *secretManagerTestCase) {
 	smtc.mockClient = nil
 	smtc.expectError = errUninitalizedGitlabProvider
@@ -134,6 +161,24 @@ func TestGitlabSecretManagerGetSecret(t *testing.T) {
 	}
 }
 
+func TestValidate(t *testing.T) {
+	successCases := []*secretManagerTestCase{
+		makeValidSecretManagerTestCaseCustom(),
+		makeValidSecretManagerTestCaseCustom(setListAPIErr),
+		makeValidSecretManagerTestCaseCustom(setListAPIRespNil),
+		makeValidSecretManagerTestCaseCustom(setListAPIRespBadCode),
+	}
+	sm := Gitlab{}
+	for k, v := range successCases {
+		sm.client = v.mockClient
+		t.Logf("%+v", v)
+		err := sm.Validate()
+		if !ErrorContains(err, v.expectError) {
+			t.Errorf("[%d], unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+		}
+	}
+}
+
 func TestGetSecretMap(t *testing.T) {
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *secretManagerTestCase) {