mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
🐛 Gitlab: separate gitlab client and provider (#2259)
* Gitlab: separate gitlab client and provider Signed-off-by: Dominik Zeiger <dominik@zeiger.biz> * Gitlab: cleanup Signed-off-by: Dominik Zeiger <dominik@zeiger.biz> * Gitlab: formatter Signed-off-by: Dominik Zeiger <dominik@zeiger.biz> * fix: lint / goheader Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Dominik Zeiger <dominik@zeiger.biz> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
parent
4478fccf9b
commit
035ff38172
3 changed files with 232 additions and 211 deletions
|
@ -27,7 +27,6 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/find"
|
||||
|
@ -52,8 +51,8 @@ const (
|
|||
)
|
||||
|
||||
// https://github.com/external-secrets/external-secrets/issues/644
|
||||
var _ esv1beta1.SecretsClient = &Gitlab{}
|
||||
var _ esv1beta1.Provider = &Gitlab{}
|
||||
var _ esv1beta1.SecretsClient = &gitlabBase{}
|
||||
var _ esv1beta1.Provider = &Provider{}
|
||||
|
||||
type ProjectsClient interface {
|
||||
ListProjectsGroups(pid interface{}, opt *gitlab.ListProjectGroupOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectGroup, *gitlab.Response, error)
|
||||
|
@ -69,27 +68,6 @@ type GroupVariablesClient interface {
|
|||
ListVariables(gid interface{}, opt *gitlab.ListGroupVariablesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.GroupVariable, *gitlab.Response, error)
|
||||
}
|
||||
|
||||
// Gitlab Provider struct with reference to GitLab clients, a projectID and groupIDs.
|
||||
type Gitlab struct {
|
||||
projectsClient ProjectsClient
|
||||
projectVariablesClient ProjectVariablesClient
|
||||
groupVariablesClient GroupVariablesClient
|
||||
url string
|
||||
projectID string
|
||||
inheritFromGroups bool
|
||||
groupIDs []string
|
||||
environment string
|
||||
}
|
||||
|
||||
// gClient for interacting with kubernetes cluster...?
|
||||
type gClient struct {
|
||||
kube kclient.Client
|
||||
store *esv1beta1.GitlabProvider
|
||||
namespace string
|
||||
storeKind string
|
||||
credentials []byte
|
||||
}
|
||||
|
||||
type ProjectGroupPathSorter []*gitlab.ProjectGroup
|
||||
|
||||
func (a ProjectGroupPathSorter) Len() int { return len(a) }
|
||||
|
@ -98,124 +76,60 @@ func (a ProjectGroupPathSorter) Less(i, j int) bool { return len(a[i].FullPath)
|
|||
|
||||
var log = ctrl.Log.WithName("provider").WithName("gitlab")
|
||||
|
||||
func init() {
|
||||
esv1beta1.Register(&Gitlab{}, &esv1beta1.SecretStoreProvider{
|
||||
Gitlab: &esv1beta1.GitlabProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
// Set gClient credentials to Access Token.
|
||||
func (c *gClient) setAuth(ctx context.Context) error {
|
||||
// Set gitlabBase credentials to Access Token.
|
||||
func (g *gitlabBase) getAuth(ctx context.Context) ([]byte, error) {
|
||||
credentialsSecret := &corev1.Secret{}
|
||||
credentialsSecretName := c.store.Auth.SecretRef.AccessToken.Name
|
||||
credentialsSecretName := g.store.Auth.SecretRef.AccessToken.Name
|
||||
if credentialsSecretName == "" {
|
||||
return fmt.Errorf(errGitlabCredSecretName)
|
||||
return nil, fmt.Errorf(errGitlabCredSecretName)
|
||||
}
|
||||
objectKey := types.NamespacedName{
|
||||
Name: credentialsSecretName,
|
||||
Namespace: c.namespace,
|
||||
Namespace: g.namespace,
|
||||
}
|
||||
// only ClusterStore is allowed to set namespace (and then it's required)
|
||||
if c.storeKind == esv1beta1.ClusterSecretStoreKind {
|
||||
if c.store.Auth.SecretRef.AccessToken.Namespace == nil {
|
||||
return fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
|
||||
if g.storeKind == esv1beta1.ClusterSecretStoreKind {
|
||||
if g.store.Auth.SecretRef.AccessToken.Namespace == nil {
|
||||
return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
|
||||
}
|
||||
objectKey.Namespace = *c.store.Auth.SecretRef.AccessToken.Namespace
|
||||
objectKey.Namespace = *g.store.Auth.SecretRef.AccessToken.Namespace
|
||||
}
|
||||
|
||||
err := c.kube.Get(ctx, objectKey, credentialsSecret)
|
||||
err := g.kube.Get(ctx, objectKey, credentialsSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errFetchSAKSecret, err)
|
||||
return nil, fmt.Errorf(errFetchSAKSecret, err)
|
||||
}
|
||||
|
||||
c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.AccessToken.Key]
|
||||
if c.credentials == nil || len(c.credentials) == 0 {
|
||||
return fmt.Errorf(errMissingSAK)
|
||||
credentials := credentialsSecret.Data[g.store.Auth.SecretRef.AccessToken.Key]
|
||||
if len(credentials) == 0 {
|
||||
return nil, fmt.Errorf(errMissingSAK)
|
||||
}
|
||||
return nil
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// Function newGitlabProvider returns a reference to a new instance of a 'Gitlab' struct.
|
||||
func NewGitlabProvider() *Gitlab {
|
||||
return &Gitlab{}
|
||||
}
|
||||
|
||||
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
|
||||
func (g *Gitlab) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||
return esv1beta1.SecretStoreReadOnly
|
||||
}
|
||||
|
||||
// Method on Gitlab Provider to set up projectVariablesClient with credentials, populate projectID and environment.
|
||||
func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
|
||||
return nil, fmt.Errorf("no store type or wrong store type")
|
||||
}
|
||||
storeSpecGitlab := storeSpec.Provider.Gitlab
|
||||
|
||||
cliStore := gClient{
|
||||
kube: kube,
|
||||
store: storeSpecGitlab,
|
||||
namespace: namespace,
|
||||
storeKind: store.GetObjectKind().GroupVersionKind().Kind,
|
||||
}
|
||||
|
||||
if err := cliStore.setAuth(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Create projectVariablesClient options
|
||||
var opts []gitlab.ClientOptionFunc
|
||||
if cliStore.store.URL != "" {
|
||||
opts = append(opts, gitlab.WithBaseURL(cliStore.store.URL))
|
||||
}
|
||||
|
||||
// ClientOptionFunc from the gitlab package can be mapped with the CRD
|
||||
// in a similar way to extend functionality of the provider
|
||||
|
||||
// Create a new Gitlab Client using credentials and options
|
||||
gitlabClient, err := gitlab.NewClient(string(cliStore.credentials), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.projectsClient = gitlabClient.Projects
|
||||
g.projectVariablesClient = gitlabClient.ProjectVariables
|
||||
g.groupVariablesClient = gitlabClient.GroupVariables
|
||||
g.projectID = cliStore.store.ProjectID
|
||||
g.inheritFromGroups = cliStore.store.InheritFromGroups
|
||||
g.groupIDs = cliStore.store.GroupIDs
|
||||
g.environment = cliStore.store.Environment
|
||||
g.url = cliStore.store.URL
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
|
||||
func (g *gitlabBase) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Not Implemented PushSecret.
|
||||
func (g *Gitlab) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
|
||||
func (g *gitlabBase) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// GetAllSecrets syncs all gitlab project and group variables into a single Kubernetes Secret.
|
||||
func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
func (g *gitlabBase) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
if utils.IsNil(g.projectVariablesClient) {
|
||||
return nil, fmt.Errorf(errUninitializedGitlabProvider)
|
||||
}
|
||||
var effectiveEnvironment = g.store.Environment
|
||||
if ref.Tags != nil {
|
||||
environment, err := ExtractTag(ref.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isEmptyOrWildcard(g.environment) && !isEmptyOrWildcard(environment) {
|
||||
if !isEmptyOrWildcard(effectiveEnvironment) && !isEmptyOrWildcard(environment) {
|
||||
return nil, fmt.Errorf(errEnvironmentIsConstricted)
|
||||
}
|
||||
g.environment = environment
|
||||
effectiveEnvironment = environment
|
||||
}
|
||||
if ref.Path != nil {
|
||||
return nil, fmt.Errorf(errPathNotImplemented)
|
||||
|
@ -240,7 +154,7 @@ func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
|
|||
|
||||
var gopts = &gitlab.ListGroupVariablesOptions{PerPage: 100}
|
||||
secretData := make(map[string][]byte)
|
||||
for _, groupID := range g.groupIDs {
|
||||
for _, groupID := range g.store.GroupIDs {
|
||||
for groupPage := 1; ; groupPage++ {
|
||||
gopts.Page = groupPage
|
||||
groupVars, response, err := g.groupVariablesClient.ListVariables(groupID, gopts)
|
||||
|
@ -249,7 +163,7 @@ func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
|
|||
return nil, err
|
||||
}
|
||||
for _, data := range groupVars {
|
||||
matching, key, isWildcard := matchesFilter(g.environment, data.EnvironmentScope, data.Key, matcher)
|
||||
matching, key, isWildcard := matchesFilter(effectiveEnvironment, data.EnvironmentScope, data.Key, matcher)
|
||||
if !matching && !isWildcard {
|
||||
continue
|
||||
}
|
||||
|
@ -264,14 +178,14 @@ func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
|
|||
var popts = &gitlab.ListProjectVariablesOptions{PerPage: 100}
|
||||
for projectPage := 1; ; projectPage++ {
|
||||
popts.Page = projectPage
|
||||
projectData, response, err := g.projectVariablesClient.ListVariables(g.projectID, popts)
|
||||
projectData, response, err := g.projectVariablesClient.ListVariables(g.store.ProjectID, popts)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabProjectListVariables, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, data := range projectData {
|
||||
matching, key, isWildcard := matchesFilter(g.environment, data.EnvironmentScope, data.Key, matcher)
|
||||
matching, key, isWildcard := matchesFilter(effectiveEnvironment, data.EnvironmentScope, data.Key, matcher)
|
||||
|
||||
if !matching {
|
||||
continue
|
||||
|
@ -301,7 +215,7 @@ func ExtractTag(tags map[string]string) (string, error) {
|
|||
return environmentScope, nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
func (g *gitlabBase) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
if utils.IsNil(g.projectVariablesClient) || utils.IsNil(g.groupVariablesClient) {
|
||||
return nil, fmt.Errorf(errUninitializedGitlabProvider)
|
||||
}
|
||||
|
@ -318,15 +232,15 @@ func (g *Gitlab) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
// "environment_scope": "*"
|
||||
// }
|
||||
var vopts *gitlab.GetProjectVariableOptions
|
||||
if g.environment != "" {
|
||||
vopts = &gitlab.GetProjectVariableOptions{Filter: &gitlab.VariableFilter{EnvironmentScope: g.environment}}
|
||||
if g.store.Environment != "" {
|
||||
vopts = &gitlab.GetProjectVariableOptions{Filter: &gitlab.VariableFilter{EnvironmentScope: g.store.Environment}}
|
||||
}
|
||||
|
||||
data, resp, err := g.projectVariablesClient.GetVariable(g.projectID, ref.Key, vopts)
|
||||
data, resp, err := g.projectVariablesClient.GetVariable(g.store.ProjectID, ref.Key, vopts)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabProjectVariableGet, err)
|
||||
if !isEmptyOrWildcard(g.environment) && resp.StatusCode == http.StatusNotFound {
|
||||
if !isEmptyOrWildcard(g.store.Environment) && resp.StatusCode == http.StatusNotFound {
|
||||
vopts.Filter.EnvironmentScope = "*"
|
||||
data, resp, err = g.projectVariablesClient.GetVariable(g.projectID, ref.Key, vopts)
|
||||
data, resp, err = g.projectVariablesClient.GetVariable(g.store.ProjectID, ref.Key, vopts)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabProjectVariableGet, err)
|
||||
}
|
||||
|
||||
|
@ -344,8 +258,8 @@ func (g *Gitlab) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
|||
result, err = extractVariable(ref, data.Value)
|
||||
}
|
||||
|
||||
for i := len(g.groupIDs) - 1; i >= 0; i-- {
|
||||
groupID := g.groupIDs[i]
|
||||
for i := len(g.store.GroupIDs) - 1; i >= 0; i-- {
|
||||
groupID := g.store.GroupIDs[i]
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
@ -386,7 +300,7 @@ func extractVariable(ref esv1beta1.ExternalSecretDataRemoteRef, value string) ([
|
|||
return []byte(val.String()), nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
func (g *gitlabBase) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
// Gets a secret as normal, expecting secret value to be a json object
|
||||
data, err := g.GetSecret(ctx, ref)
|
||||
if err != nil {
|
||||
|
@ -428,30 +342,47 @@ func matchesFilter(environment, varEnvironment, key string, matcher *find.Matche
|
|||
return true, key, isWildcard
|
||||
}
|
||||
|
||||
func (g *Gitlab) Close(ctx context.Context) error {
|
||||
func (g *gitlabBase) Close(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gitlabBase) ResolveGroupIds() error {
|
||||
if g.store.InheritFromGroups {
|
||||
projectGroups, resp, err := g.projectsClient.ListProjectsGroups(g.store.ProjectID, nil)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabListProjectsGroups, err)
|
||||
if resp.StatusCode >= 400 && err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(ProjectGroupPathSorter(projectGroups))
|
||||
discoveredIds := make([]string, len(projectGroups))
|
||||
for i, group := range projectGroups {
|
||||
discoveredIds[i] = strconv.Itoa(group.ID)
|
||||
}
|
||||
g.store.GroupIDs = discoveredIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate will use the gitlab projectVariablesClient/groupVariablesClient to validate the gitlab provider using the ListVariable call to ensure get permissions without needing a specific key.
|
||||
func (g *Gitlab) Validate() (esv1beta1.ValidationResult, error) {
|
||||
if g.projectID != "" {
|
||||
_, resp, err := g.projectVariablesClient.ListVariables(g.projectID, nil)
|
||||
func (g *gitlabBase) Validate() (esv1beta1.ValidationResult, error) {
|
||||
if g.store.ProjectID != "" {
|
||||
_, resp, err := g.projectVariablesClient.ListVariables(g.store.ProjectID, nil)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabProjectListVariables, err)
|
||||
if err != nil {
|
||||
return esv1beta1.ValidationResultError, fmt.Errorf(errList, err)
|
||||
} else if resp == nil || resp.StatusCode != http.StatusOK {
|
||||
return esv1beta1.ValidationResultError, fmt.Errorf(errProjectAuth, g.projectID)
|
||||
return esv1beta1.ValidationResultError, fmt.Errorf(errProjectAuth, g.store.ProjectID)
|
||||
}
|
||||
|
||||
err = g.ResolveGroupIds()
|
||||
if err != nil {
|
||||
return esv1beta1.ValidationResultError, fmt.Errorf(errList, err)
|
||||
}
|
||||
log.V(1).Info("discovered project groups", "name", g.groupIDs)
|
||||
log.V(1).Info("discovered project groups", "name", g.store.GroupIDs)
|
||||
}
|
||||
|
||||
if len(g.groupIDs) > 0 {
|
||||
for _, groupID := range g.groupIDs {
|
||||
if len(g.store.GroupIDs) > 0 {
|
||||
for _, groupID := range g.store.GroupIDs {
|
||||
_, resp, err := g.groupVariablesClient.ListVariables(groupID, nil)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabGroupListVariables, err)
|
||||
if err != nil {
|
||||
|
@ -464,48 +395,3 @@ func (g *Gitlab) Validate() (esv1beta1.ValidationResult, error) {
|
|||
|
||||
return esv1beta1.ValidationResultReady, nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) ResolveGroupIds() error {
|
||||
if g.inheritFromGroups {
|
||||
projectGroups, resp, err := g.projectsClient.ListProjectsGroups(g.projectID, nil)
|
||||
metrics.ObserveAPICall(metrics.ProviderGitLab, metrics.CallGitLabListProjectsGroups, err)
|
||||
if resp.StatusCode >= 400 && err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(ProjectGroupPathSorter(projectGroups))
|
||||
discoveredIds := make([]string, len(projectGroups))
|
||||
for i, group := range projectGroups {
|
||||
discoveredIds[i] = strconv.Itoa(group.ID)
|
||||
}
|
||||
g.groupIDs = discoveredIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
storeSpec := store.GetSpec()
|
||||
gitlabSpec := storeSpec.Provider.Gitlab
|
||||
accessToken := gitlabSpec.Auth.SecretRef.AccessToken
|
||||
err := utils.ValidateSecretSelector(store, accessToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gitlabSpec.ProjectID == "" && len(gitlabSpec.GroupIDs) == 0 {
|
||||
return fmt.Errorf("projectID and groupIDs must not both be empty")
|
||||
}
|
||||
|
||||
if gitlabSpec.InheritFromGroups && len(gitlabSpec.GroupIDs) > 0 {
|
||||
return fmt.Errorf("defining groupIDs and inheritFromGroups = true is not allowed")
|
||||
}
|
||||
|
||||
if accessToken.Key == "" {
|
||||
return fmt.Errorf("accessToken.key cannot be empty")
|
||||
}
|
||||
|
||||
if accessToken.Name == "" {
|
||||
return fmt.Errorf("accessToken.name cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -432,13 +432,14 @@ func TestGetSecret(t *testing.T) {
|
|||
makeValidSecretManagerTestCaseCustom(setNilMockClient),
|
||||
}
|
||||
|
||||
sm := Gitlab{}
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
for k, v := range successCases {
|
||||
sm.projectVariablesClient = v.mockProjectVarClient
|
||||
sm.groupVariablesClient = v.mockGroupVarClient
|
||||
sm.projectID = v.projectID
|
||||
sm.groupIDs = v.groupIDs
|
||||
sm.environment = v.apiInputEnv
|
||||
sm.store.ProjectID = v.projectID
|
||||
sm.store.GroupIDs = v.groupIDs
|
||||
sm.store.Environment = v.apiInputEnv
|
||||
out, err := sm.GetSecret(context.Background(), *v.ref)
|
||||
if !ErrorContains(err, v.expectError) {
|
||||
t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
||||
|
@ -451,16 +452,17 @@ func TestGetSecret(t *testing.T) {
|
|||
|
||||
func TestResolveGroupIds(t *testing.T) {
|
||||
v := makeValidSecretManagerTestCaseCustom()
|
||||
sm := Gitlab{}
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
sm.projectsClient = v.mockProjectsClient
|
||||
sm.projectID = v.projectID
|
||||
sm.inheritFromGroups = true
|
||||
sm.store.ProjectID = v.projectID
|
||||
sm.store.InheritFromGroups = true
|
||||
err := sm.ResolveGroupIds()
|
||||
if err != nil {
|
||||
t.Errorf(defaultErrorMessage, 0, err.Error(), "")
|
||||
}
|
||||
if !reflect.DeepEqual(sm.groupIDs, []string{"1", "10", "100"}) {
|
||||
t.Errorf("unexpected groupIds: %s, expected %s", sm.groupIDs, []string{"1", "10", "100"})
|
||||
if !reflect.DeepEqual(sm.store.GroupIDs, []string{"1", "10", "100"}) {
|
||||
t.Errorf("unexpected groupIds: %s, expected %s", sm.store.GroupIDs, []string{"1", "10", "100"})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,12 +644,13 @@ func TestGetAllSecrets(t *testing.T) {
|
|||
makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
|
||||
}
|
||||
|
||||
sm := Gitlab{}
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
for k, v := range cases {
|
||||
sm.environment = v.apiInputEnv
|
||||
sm.projectVariablesClient = v.mockProjectVarClient
|
||||
sm.groupVariablesClient = v.mockGroupVarClient
|
||||
sm.groupIDs = v.groupIDs
|
||||
sm.store.Environment = v.apiInputEnv
|
||||
sm.store.GroupIDs = v.groupIDs
|
||||
if v.expectedSecret != "" {
|
||||
v.expectedData = map[string][]byte{testKey: []byte(v.expectedSecret)}
|
||||
}
|
||||
|
@ -701,13 +704,14 @@ func TestGetAllSecretsWithGroups(t *testing.T) {
|
|||
makeValidSecretManagerGetAllTestCaseCustom(groupAndProjectWithDifferentEnvSecrets),
|
||||
}
|
||||
|
||||
sm := Gitlab{}
|
||||
sm.environment = environment
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
sm.store.Environment = environment
|
||||
for k, v := range cases {
|
||||
sm.projectVariablesClient = v.mockProjectVarClient
|
||||
sm.groupVariablesClient = v.mockGroupVarClient
|
||||
sm.projectID = v.projectID
|
||||
sm.groupIDs = v.groupIDs
|
||||
sm.store.ProjectID = v.projectID
|
||||
sm.store.GroupIDs = v.groupIDs
|
||||
out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
|
||||
if !ErrorContains(err, v.expectError) {
|
||||
t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
||||
|
@ -735,14 +739,15 @@ func TestValidate(t *testing.T) {
|
|||
makeValidSecretManagerTestCaseCustom(setGroupListAPIRespNil),
|
||||
makeValidSecretManagerTestCaseCustom(setGroupListAPIRespBadCode),
|
||||
}
|
||||
sm := Gitlab{}
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
for k, v := range successCases {
|
||||
sm.projectsClient = v.mockProjectsClient
|
||||
sm.projectVariablesClient = v.mockProjectVarClient
|
||||
sm.groupVariablesClient = v.mockGroupVarClient
|
||||
sm.projectID = v.projectID
|
||||
sm.groupIDs = v.groupIDs
|
||||
sm.inheritFromGroups = v.inheritFromGroups
|
||||
sm.store.ProjectID = v.projectID
|
||||
sm.store.GroupIDs = v.groupIDs
|
||||
sm.store.InheritFromGroups = v.inheritFromGroups
|
||||
t.Logf("%+v", v)
|
||||
validationResult, err := sm.Validate()
|
||||
if !ErrorContains(err, v.expectError) {
|
||||
|
@ -751,8 +756,8 @@ func TestValidate(t *testing.T) {
|
|||
if validationResult != v.expectedValidationResult {
|
||||
t.Errorf("[%d], unexpected validationResult: [%s], expected: [%s]", k, validationResult, v.expectedValidationResult)
|
||||
}
|
||||
if sm.inheritFromGroups && sm.groupIDs[0] != "1" {
|
||||
t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.groupIDs[0])
|
||||
if sm.store.InheritFromGroups && sm.store.GroupIDs[0] != "1" {
|
||||
t.Errorf("[%d], unexpected groupID: [%s], expected [1]", k, sm.store.GroupIDs[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -777,7 +782,8 @@ func TestGetSecretMap(t *testing.T) {
|
|||
makeValidSecretManagerTestCaseCustom(setAPIErr),
|
||||
}
|
||||
|
||||
sm := Gitlab{}
|
||||
sm := gitlabBase{}
|
||||
sm.store = &esv1beta1.GitlabProvider{}
|
||||
for k, v := range successCases {
|
||||
sm.projectVariablesClient = v.mockProjectVarClient
|
||||
sm.groupVariablesClient = v.mockGroupVarClient
|
||||
|
@ -791,18 +797,6 @@ func TestGetSecretMap(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func ErrorContains(out error, want string) bool {
|
||||
if out == nil {
|
||||
return want == ""
|
||||
}
|
||||
if want == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
|
||||
|
||||
func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
|
@ -877,7 +871,7 @@ func TestValidateStore(t *testing.T) {
|
|||
err: nil,
|
||||
},
|
||||
}
|
||||
p := Gitlab{}
|
||||
p := Provider{}
|
||||
for _, tc := range testCases {
|
||||
err := p.ValidateStore(tc.store)
|
||||
if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
|
||||
|
@ -889,3 +883,15 @@ func TestValidateStore(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorContains(out error, want string) bool {
|
||||
if out == nil {
|
||||
return want == ""
|
||||
}
|
||||
if want == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
|
||||
|
|
129
pkg/provider/gitlab/provider.go
Normal file
129
pkg/provider/gitlab/provider.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
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 gitlab
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/xanzy/go-gitlab"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
// Provider satisfies the provider interface.
|
||||
type Provider struct{}
|
||||
|
||||
// gitlabBase satisfies the provider.SecretsClient interface.
|
||||
type gitlabBase struct {
|
||||
kube kclient.Client
|
||||
store *esv1beta1.GitlabProvider
|
||||
storeKind string
|
||||
namespace string
|
||||
|
||||
projectsClient ProjectsClient
|
||||
projectVariablesClient ProjectVariablesClient
|
||||
groupVariablesClient GroupVariablesClient
|
||||
}
|
||||
|
||||
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
|
||||
func (g *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||
return esv1beta1.SecretStoreReadOnly
|
||||
}
|
||||
|
||||
// Method on Gitlab Provider to set up projectVariablesClient with credentials, populate projectID and environment.
|
||||
func (g *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
|
||||
return nil, fmt.Errorf("no store type or wrong store type")
|
||||
}
|
||||
storeSpecGitlab := storeSpec.Provider.Gitlab
|
||||
|
||||
gl := &gitlabBase{
|
||||
kube: kube,
|
||||
store: storeSpecGitlab,
|
||||
namespace: namespace,
|
||||
storeKind: store.GetObjectKind().GroupVersionKind().Kind,
|
||||
}
|
||||
|
||||
client, err := gl.getClient(ctx, storeSpecGitlab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gl.projectsClient = client.Projects
|
||||
gl.projectVariablesClient = client.ProjectVariables
|
||||
gl.groupVariablesClient = client.GroupVariables
|
||||
|
||||
return gl, nil
|
||||
}
|
||||
|
||||
func (g *gitlabBase) getClient(ctx context.Context, provider *esv1beta1.GitlabProvider) (*gitlab.Client, error) {
|
||||
credentials, err := g.getAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create projectVariablesClient options
|
||||
var opts []gitlab.ClientOptionFunc
|
||||
if provider.URL != "" {
|
||||
opts = append(opts, gitlab.WithBaseURL(provider.URL))
|
||||
}
|
||||
|
||||
// ClientOptionFunc from the gitlab package can be mapped with the CRD
|
||||
// in a similar way to extend functionality of the provider
|
||||
|
||||
// Create a new Gitlab Client using credentials and options
|
||||
client, err := gitlab.NewClient(string(credentials), opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (g *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
storeSpec := store.GetSpec()
|
||||
gitlabSpec := storeSpec.Provider.Gitlab
|
||||
accessToken := gitlabSpec.Auth.SecretRef.AccessToken
|
||||
err := utils.ValidateSecretSelector(store, accessToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gitlabSpec.ProjectID == "" && len(gitlabSpec.GroupIDs) == 0 {
|
||||
return fmt.Errorf("projectID and groupIDs must not both be empty")
|
||||
}
|
||||
|
||||
if gitlabSpec.InheritFromGroups && len(gitlabSpec.GroupIDs) > 0 {
|
||||
return fmt.Errorf("defining groupIDs and inheritFromGroups = true is not allowed")
|
||||
}
|
||||
|
||||
if accessToken.Key == "" {
|
||||
return fmt.Errorf("accessToken.key cannot be empty")
|
||||
}
|
||||
|
||||
if accessToken.Name == "" {
|
||||
return fmt.Errorf("accessToken.name cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
Gitlab: &esv1beta1.GitlabProvider{},
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue