Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gitlab retriever #696

Merged
merged 7 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cmd/relayproxy/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import (
)

var DefaultRetriever = struct {
Timeout time.Duration
HTTPMethod string
GithubBranch string
Timeout time.Duration
HTTPMethod string
GitBranch string
}{
Timeout: 10 * time.Second,
HTTPMethod: http.MethodGet,
GithubBranch: "main",
Timeout: 10 * time.Second,
HTTPMethod: http.MethodGet,
GitBranch: "main",
}

var DefaultExporter = struct {
Expand Down
13 changes: 11 additions & 2 deletions cmd/relayproxy/config/retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type RetrieverConf struct {
Branch string `mapstructure:"branch"`
Path string `mapstructure:"path"`
GithubToken string `mapstructure:"githubToken"`
GitlabToken string `mapstructure:"gitlabToken"`
URL string `mapstructure:"url"`
Timeout int64 `mapstructure:"timeout"`
HTTPMethod string `mapstructure:"method"`
Expand All @@ -31,6 +32,12 @@ func (c *RetrieverConf) IsValid() error {
if c.Kind == GitHubRetriever && c.RepositorySlug == "" {
return fmt.Errorf("invalid retriever: no \"repositorySlug\" property found for kind \"%s\"", c.Kind)
}
if c.Kind == GitlabRetriever && c.URL == "" {
return fmt.Errorf("invalid retriever: no \"URL\" property found for kind \"%s\"", c.Kind)
}
if c.Kind == GitlabRetriever && c.RepositorySlug == "" {
return fmt.Errorf("invalid retriever: no \"repositorySlug\" property found for kind \"%s\"", c.Kind)
}
if c.Kind == S3Retriever && c.Item == "" {
return fmt.Errorf("invalid retriever: no \"item\" property found for kind \"%s\"", c.Kind)
}
Expand All @@ -40,7 +47,7 @@ func (c *RetrieverConf) IsValid() error {
if c.Kind == GoogleStorageRetriever && c.Object == "" {
return fmt.Errorf("invalid retriever: no \"object\" property found for kind \"%s\"", c.Kind)
}
if (c.Kind == GitHubRetriever || c.Kind == FileRetriever) && c.Path == "" {
if (c.Kind == GitHubRetriever || c.Kind == FileRetriever || c.Kind == GitlabRetriever) && c.Path == "" {
return fmt.Errorf("invalid retriever: no \"path\" property found for kind \"%s\"", c.Kind)
}
if (c.Kind == S3Retriever || c.Kind == GoogleStorageRetriever) && c.Bucket == "" {
Expand All @@ -64,6 +71,7 @@ type RetrieverKind string
const (
HTTPRetriever RetrieverKind = "http"
GitHubRetriever RetrieverKind = "github"
GitlabRetriever RetrieverKind = "gitlab"
S3Retriever RetrieverKind = "s3"
FileRetriever RetrieverKind = "file"
GoogleStorageRetriever RetrieverKind = "googleStorage"
Expand All @@ -73,7 +81,8 @@ const (
// IsValid is checking if the value is part of the enum
func (r RetrieverKind) IsValid() error {
switch r {
case HTTPRetriever, GitHubRetriever, S3Retriever, FileRetriever, GoogleStorageRetriever, KubernetesRetriever:
case HTTPRetriever, GitHubRetriever, GitlabRetriever, S3Retriever,
FileRetriever, GoogleStorageRetriever, KubernetesRetriever:
return nil
}
return fmt.Errorf("invalid retriever: kind \"%s\" is not supported", r)
Expand Down
27 changes: 27 additions & 0 deletions cmd/relayproxy/config/retriever_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ func TestRetrieverConf_IsValid(t *testing.T) {
wantErr: true,
errValue: "invalid retriever: no \"repositorySlug\" property found for kind \"github\"",
},
{
name: "kind GitlabRetriever without URL slug",
fields: config.RetrieverConf{
Kind: "gitlab",
},
wantErr: true,
errValue: "invalid retriever: no \"URL\" property found for kind \"gitlab\"",
},
{
name: "kind GitlabRetriever, with URL but without path",
fields: config.RetrieverConf{
Kind: "gitlab",
URL: "XXX",
},
wantErr: true,
errValue: "invalid retriever: no \"repositorySlug\" property found for kind \"gitlab\"",
},
{
name: "kind GitlabRetriever, with URL but without path",
fields: config.RetrieverConf{
Kind: "gitlab",
RepositorySlug: "aaa/bbb",
URL: "XXX",
},
wantErr: true,
errValue: "invalid retriever: no \"path\" property found for kind \"gitlab\"",
},
{
name: "kind S3Retriever without item",
fields: config.RetrieverConf{
Expand Down
18 changes: 16 additions & 2 deletions cmd/relayproxy/service/gofeatureflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/thomaspoignant/go-feature-flag/retriever/fileretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/gcstorageretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/githubretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/httpretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/k8sretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/s3retriever"
Expand Down Expand Up @@ -93,15 +94,28 @@ func initRetriever(c *config.RetrieverConf) (retriever.Retriever, error) {
RepositorySlug: c.RepositorySlug,
Branch: func() string {
if c.Branch == "" {
return config.DefaultRetriever.GithubBranch
return config.DefaultRetriever.GitBranch
}
return c.Branch
}(),
FilePath: c.Path,
GithubToken: c.GithubToken,
Timeout: retrieverTimeout,
}, nil

case config.GitlabRetriever:
return &gitlabretriever.Retriever{
URL: c.URL,
Branch: func() string {
if c.Branch == "" {
return config.DefaultRetriever.GitBranch
}
return c.Branch
}(),
FilePath: c.Path,
GitlabToken: c.GitlabToken,
RepositorySlug: c.RepositorySlug,
Timeout: retrieverTimeout,
}, nil
case config.FileRetriever:
return &fileretriever.Retriever{
Path: c.Path,
Expand Down
25 changes: 22 additions & 3 deletions cmd/relayproxy/service/gofeatureflag_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package service

import (
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
ffclient "github.com/thomaspoignant/go-feature-flag"
"github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/config"
Expand All @@ -13,11 +17,9 @@ import (
"github.com/thomaspoignant/go-feature-flag/retriever/fileretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/gcstorageretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/githubretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/httpretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/s3retriever"
"net/http"
"testing"
"time"
)

func Test_initRetriever(t *testing.T) {
Expand All @@ -44,6 +46,23 @@ func Test_initRetriever(t *testing.T) {
Timeout: 20 * time.Millisecond,
},
},
{
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved
name: "Convert Gitlab Retriever",
wantErr: assert.NoError,
conf: &config.RetrieverConf{
Kind: "gitlab",
URL: "http://localhost",
Path: "flag-config.yaml",
Timeout: 20,
},
want: &gitlabretriever.Retriever{
URL: "http://localhost",
Branch: "main",
FilePath: "flag-config.yaml",
GitlabToken: "",
Timeout: 20 * time.Millisecond,
},
},
{
name: "Convert File Retriever",
wantErr: assert.NoError,
Expand Down
14 changes: 14 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/thomaspoignant/go-feature-flag/retriever"
"github.com/thomaspoignant/go-feature-flag/retriever/fileretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/githubretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/httpretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/s3retriever"

Expand Down Expand Up @@ -78,6 +79,19 @@ func TestConfig_GetRetrievers(t *testing.T) {
want: "*githubretriever.Retriever",
wantErr: false,
},
{
name: "Gitlab retriever",
fields: fields{
PollingInterval: 3 * time.Second,
Retriever: &gitlabretriever.Retriever{
URL: "https://gitlab.com/ruairi2/go-feature-flags-config",
FilePath: "flag-config.yaml",
GitlabToken: "XXX",
},
},
want: "*gitlabretriever.Retriever",
wantErr: false,
},
{
name: "No retriever",
fields: fields{
Expand Down
74 changes: 74 additions & 0 deletions retriever/gitlabretriever/retriever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package gitlabretriever

import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"

httpretriever "github.com/thomaspoignant/go-feature-flag/retriever/httpretriever"

"github.com/thomaspoignant/go-feature-flag/internal"
)

// Retriever is a configuration struct for a GitHub retriever.
type Retriever struct {
Branch string // default is main
FilePath string
GitlabToken string
RepositorySlug string
URL string // https://gitlab.com
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved
Timeout time.Duration // default is 10 seconds

// httpClient is the http.Client if you want to override it.
httpClient internal.HTTPClient
}

func (r *Retriever) Retrieve(ctx context.Context) ([]byte, error) {
if r.FilePath == "" || r.URL == "" {
return nil, fmt.Errorf("missing mandatory information URL=%s, FilePath=%s", r.URL, r.FilePath)
}

// default branch is main
branch := r.Branch
if branch == "" {
branch = "main"
}
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved

URL := r.URL
if URL == "" {
URL = "https://gitlab.com"
}
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved

// add header for Gitlab Token if specified
header := http.Header{}
if r.GitlabToken != "" {
header.Add("PRIVATE-TOKEN", r.GitlabToken)
}

URL = strings.Trim(r.URL, "/")
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved
slug := strings.Trim(r.RepositorySlug, "/")
path := strings.Trim(r.FilePath, "/")
reqURL := fmt.Sprintf("%s/api/v4/projects/%s/repository/files/%s/raw?ref=%s", URL, url.QueryEscape(slug), url.QueryEscape(path), r.Branch)
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved

httpRetriever := httpretriever.Retriever{
URL: reqURL,
Method: http.MethodGet,
Header: header,
Timeout: r.Timeout,
}

if r.httpClient != nil {
httpRetriever.SetHTTPClient(r.httpClient)
}

return httpRetriever.Retrieve(ctx)
}

// SetHTTPClient is here if you want to override the default http.Client we are using.
// It is also used for the tests.
func (r *Retriever) SetHTTPClient(client internal.HTTPClient) {
r.httpClient = client
}
Loading