Skip to content

Commit

Permalink
Force to use only one retriever at the time (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaspoignant authored Jan 13, 2021
1 parent 74d825d commit 33e35be
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 116 deletions.
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ First, you need to initialize the `ffclient` with the location of your backend f
```go
err := ffclient.Init(ffclient.Config{
PollInterval: 3,
HTTPRetriever: &ffClient.HTTPRetriever{
Retriever: &ffclient.HTTPRetriever{
URL: "http://example.com/test.yaml",
},
})
defer ffclient.Close()
```
*This example will load a file from an HTTP endpoint and will refresh the flags every 3 seconds (if you omit the
PollInterval, default value is 60s).*
PollInterval, the default value is 60 seconds).*

Now you can evalute your flags anywhere in your code.
Now you can evaluate your flags anywhere in your code.

```go
user := ffuser.NewUser("user-unique-key")
Expand Down Expand Up @@ -70,9 +70,10 @@ ffclient.Init(ffclient.Config{

| | |
|---|---|
|`PollInterval` | Number of seconds to wait before refreshing the flags. The default value is 60 seconds.|
|`Logger` | Logger used to log what `go-feature-flag` is doing. If no logger provided no log will be output.|
|`Context` | The context used by the retriever. The default value is `context.Background()`.|
|`PollInterval` | Number of seconds to wait before refreshing the flags.<br />The default value is 60 seconds.|
|`Logger` | Logger used to log what `go-feature-flag` is doing.<br />If no logger is provided the module will not log anything.|
|`Context` | The context used by the retriever.<br />The default value is `context.Background()`.|
|`Retriever` | The configuration retriever you want to use to get your flag file *(see [Where do I store my flags file](#where-do-i-store-my-flags-file) for the configuration details)*.|

## Where do I store my flags file
`go-feature-flags` support different ways of retrieving the flag file.
Expand All @@ -83,7 +84,7 @@ consideration.
```go
err := ffclient.Init(ffclient.Config{
PollInterval: 3,
GithubRetriever: &ffClient.GithubRetriever{
Retriever: &ffclient.GithubRetriever{
RepositorySlug: "thomaspoignant/go-feature-flag",
Branch: "main",
FilePath: "testdata/test.yaml",
Expand All @@ -100,13 +101,13 @@ To configure the access to your GitHub file:
- **GithubToken**: Github token is used to access a private repository, you need the `repo` permission *([how to create a GitHub token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token))*.
- **Timeout**: Timeout for the HTTP call (default is 10 seconds).

**Warning**: GitHub has rate limits, so be sure to not reach them when setting your `PollInterval`.
:warning: GitHub has rate limits, so be sure to not reach them when setting your `PollInterval`.

### From an HTTP endpoint
```go
err := ffclient.Init(ffclient.Config{
PollInterval: 3,
HTTPRetriever: &ffClient.HTTPRetriever{
Retriever: &ffclient.HTTPRetriever{
URL: "http://example.com/test.yaml",
Timeout: 2 * time.Second,
},
Expand All @@ -125,7 +126,7 @@ To configure your HTTP endpoint:
```go
err := ffclient.Init(ffclient.Config{
PollInterval: 3,
S3Retriever: &ffClient.S3Retriever{
Retriever: &ffclient.S3Retriever{
Bucket: "tpoi-test",
Item: "test.yaml",
AwsConfig: aws.Config{
Expand All @@ -145,11 +146,16 @@ To configure your S3 file location:
```go
err := ffclient.Init(ffclient.Config{
PollInterval: 3,
LocalFile: "file-example.yaml",
Retriever: &ffclient.FileRetriever{
Path: "file-example.yaml",
},
})
defer ffclient.Close()
```

To configure your File retriever:
- **Path**: location of your file. **MANDATORY**

*I will not recommend using a file to store your flags except if it is in a shared folder for all your services.*


Expand Down
114 changes: 58 additions & 56 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,28 @@ type Config struct {
PollInterval int // Poll every X seconds
Logger *log.Logger
Context context.Context // default is context.Background()
LocalFile string
HTTPRetriever *HTTPRetriever
S3Retriever *S3Retriever
GithubRetriever *GithubRetriever
Retriever Retriever
}

// GetRetriever returns a retriever.FlagRetriever configure with the retriever available in the config.
func (c *Config) GetRetriever() (retriever.FlagRetriever, error) {
if c.Retriever == nil {
return nil, errors.New("no retriever in the configuration, impossible to get the flags")
}
return c.Retriever.getFlagRetriever()
}

type Retriever interface {
getFlagRetriever() (retriever.FlagRetriever, error)
}

// FileRetriever is a configuration struct for a local flat file.
type FileRetriever struct {
Path string
}

func (r *FileRetriever) getFlagRetriever() (retriever.FlagRetriever, error) { // nolint: unparam
return retriever.NewLocalRetriever(r.Path), nil
}

// HTTPRetriever is a configuration struct for an HTTP endpoint retriever.
Expand All @@ -36,13 +54,46 @@ type HTTPRetriever struct {
Timeout time.Duration
}

func (r *HTTPRetriever) getFlagRetriever() (retriever.FlagRetriever, error) {
timeout := r.Timeout
if timeout <= 0 {
timeout = 10 * time.Second
}

return retriever.NewHTTPRetriever(
&http.Client{
Timeout: timeout,
},
r.URL,
r.Method,
r.Body,
r.Header,
), nil
}

// S3Retriever is a configuration struct for a S3 retriever.
type S3Retriever struct {
Bucket string
Item string
AwsConfig aws.Config
}

func (r *S3Retriever) getFlagRetriever() (retriever.FlagRetriever, error) {
// Create an AWS session
sess, err := session.NewSession(&r.AwsConfig)
if err != nil {
return nil, err
}

// Create a new AWS S3 downloader
downloader := s3manager.NewDownloader(sess)
return retriever.NewS3Retriever(
downloader,
r.Bucket,
r.Item,
), nil
}

// GithubRetriever is a configuration struct for a GitHub retriever.
type GithubRetriever struct {
RepositorySlug string
Expand All @@ -52,40 +103,7 @@ type GithubRetriever struct {
Timeout time.Duration // default is 10 seconds
}

// GetRetriever is used to get the retriever we will use to load the flags file.
func (c *Config) GetRetriever() (retriever.FlagRetriever, error) {
if c.GithubRetriever != nil {
return initGithubRetriever(*c.GithubRetriever)
}

if c.S3Retriever != nil {
// Create an AWS session
sess, err := session.NewSession(&c.S3Retriever.AwsConfig)
if err != nil {
return nil, err
}

// Create a new AWS S3 downloader
downloader := s3manager.NewDownloader(sess)
return retriever.NewS3Retriever(
downloader,
c.S3Retriever.Bucket,
c.S3Retriever.Item,
), nil
}

if c.HTTPRetriever != nil {
return initHTTPRetriever(*c.HTTPRetriever)
}

if c.LocalFile != "" {
return retriever.NewLocalRetriever(c.LocalFile), nil
}
return nil, errors.New("please add a config to get the flag config file")
}

// initGithubRetriever creates a HTTP retriever that allows to get changes from Github.
func initGithubRetriever(r GithubRetriever) (retriever.FlagRetriever, error) {
func (r *GithubRetriever) getFlagRetriever() (retriever.FlagRetriever, error) {
// default branch is main
branch := r.Branch
if branch == "" {
Expand All @@ -104,28 +122,12 @@ func initGithubRetriever(r GithubRetriever) (retriever.FlagRetriever, error) {
branch,
r.FilePath)

return initHTTPRetriever(HTTPRetriever{
httpRetriever := HTTPRetriever{
URL: URL,
Method: http.MethodGet,
Header: header,
Timeout: r.Timeout,
})
}

// initHttpRetriever creates a HTTP retriever
func initHTTPRetriever(r HTTPRetriever) (retriever.FlagRetriever, error) {
timeout := r.Timeout
if timeout <= 0 {
timeout = 10 * time.Second
}

return retriever.NewHTTPRetriever(
&http.Client{
Timeout: timeout,
},
r.URL,
r.Method,
r.Body,
r.Header,
), nil
return httpRetriever.getFlagRetriever()
}
55 changes: 8 additions & 47 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ import (

func TestConfig_GetRetriever(t *testing.T) {
type fields struct {
PollInterval int
LocalFile string
HTTPRetriever *ffClient.HTTPRetriever
S3Retriever *ffClient.S3Retriever
GithubRetriever *ffClient.GithubRetriever
PollInterval int
Retriever ffClient.Retriever
}
tests := []struct {
name string
Expand All @@ -28,7 +25,7 @@ func TestConfig_GetRetriever(t *testing.T) {
name: "File retriever",
fields: fields{
PollInterval: 3,
LocalFile: "file-example.yaml",
Retriever: &ffClient.FileRetriever{Path: "file-example.yaml"},
},
want: "*retriever.localRetriever",
wantErr: false,
Expand All @@ -37,7 +34,7 @@ func TestConfig_GetRetriever(t *testing.T) {
name: "S3 retriever",
fields: fields{
PollInterval: 3,
S3Retriever: &ffClient.S3Retriever{
Retriever: &ffClient.S3Retriever{
Bucket: "tpoi-test",
Item: "test.yaml",
AwsConfig: aws.Config{
Expand All @@ -52,7 +49,7 @@ func TestConfig_GetRetriever(t *testing.T) {
name: "HTTP retriever",
fields: fields{
PollInterval: 3,
HTTPRetriever: &ffClient.HTTPRetriever{
Retriever: &ffClient.HTTPRetriever{
URL: "http://example.com/test.yaml",
Method: http.MethodGet,
},
Expand All @@ -64,7 +61,7 @@ func TestConfig_GetRetriever(t *testing.T) {
name: "Github retriever",
fields: fields{
PollInterval: 3,
GithubRetriever: &ffClient.GithubRetriever{
Retriever: &ffClient.GithubRetriever{
RepositorySlug: "thomaspoignant/go-feature-flag",
FilePath: "testdata/test.yaml",
GithubToken: "XXX",
Expand All @@ -74,39 +71,6 @@ func TestConfig_GetRetriever(t *testing.T) {
want: "*retriever.httpRetriever",
wantErr: false,
},
{
name: "Priority to S3",
fields: fields{
PollInterval: 3,
HTTPRetriever: &ffClient.HTTPRetriever{
URL: "http://example.com/test.yaml",
Method: http.MethodGet,
},
S3Retriever: &ffClient.S3Retriever{
Bucket: "tpoi-test",
Item: "test.yaml",
AwsConfig: aws.Config{
Region: aws.String("eu-west-1"),
},
},
LocalFile: "file-example.yaml",
},
want: "*retriever.s3Retriever",
wantErr: false,
},
{
name: "Priority to HTTP",
fields: fields{
PollInterval: 3,
HTTPRetriever: &ffClient.HTTPRetriever{
URL: "http://example.com/test.yaml",
Method: http.MethodGet,
},
LocalFile: "file-example.yaml",
},
want: "*retriever.httpRetriever",
wantErr: false,
},
{
name: "No retriever",
fields: fields{
Expand All @@ -118,11 +82,8 @@ func TestConfig_GetRetriever(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &ffClient.Config{
PollInterval: tt.fields.PollInterval,
LocalFile: tt.fields.LocalFile,
HTTPRetriever: tt.fields.HTTPRetriever,
S3Retriever: tt.fields.S3Retriever,
GithubRetriever: tt.fields.GithubRetriever,
PollInterval: tt.fields.PollInterval,
Retriever: tt.fields.Retriever,
}
got, err := c.GetRetriever()
assert.Equal(t, tt.wantErr, err != nil)
Expand Down
4 changes: 2 additions & 2 deletions feature_flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestValidUseCase(t *testing.T) {
// Valid use case
err := ffclient.Init(ffclient.Config{
PollInterval: 0,
LocalFile: "testdata/test.yaml",
Retriever: &ffclient.FileRetriever{Path: "testdata/test.yaml"},
})

assert.NoError(t, err)
Expand All @@ -37,7 +37,7 @@ func TestS3RetrieverReturnError(t *testing.T) {
// Valid use case
err := ffclient.Init(ffclient.Config{
PollInterval: 0,
S3Retriever: &ffclient.S3Retriever{
Retriever: &ffclient.S3Retriever{
Bucket: "unknown-bucket",
Item: "unknown-item",
AwsConfig: aws.Config{},
Expand Down

0 comments on commit 33e35be

Please sign in to comment.