Skip to content

Commit

Permalink
V3: Add Credentials provider (#612)
Browse files Browse the repository at this point in the history
# Description
Add credentials provider to manages Exoscale secret from different
environment and time.

## Checklist
(For exoscale contributors)

V3 alpha development

---------

Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com>
  • Loading branch information
pierre-emmanuelJ authored Feb 13, 2024
1 parent cf4abaf commit be2da0e
Show file tree
Hide file tree
Showing 716 changed files with 318,723 additions and 1,267 deletions.
35 changes: 27 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,44 @@ require (
github.com/deepmap/oapi-codegen v1.9.1
github.com/go-playground/validator/v10 v10.9.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/uuid v1.3.1
github.com/google/uuid v1.4.0
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/pb33f/libopenapi v0.11.0
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/viper v1.18.2
github.com/stretchr/objx v0.5.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
75 changes: 57 additions & 18 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion v3/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (c Client) Validate(s any) error {
if len(validationErrors) > 0 {
e := validationErrors[0]
errorString := fmt.Sprintf(
"Request validation error: '%s' = '%v' does not validate ",
"request validation error: '%s' = '%v' does not validate ",
e.StructNamespace(),
e.Value(),
)
Expand Down
29 changes: 10 additions & 19 deletions v3/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions v3/credentials/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package credentials

import (
"errors"
"fmt"
)

var (
ErrNoValidCredentialProviders = errors.New("no valid credential providers")
)

// A ChainProvider will search for a provider which returns credentials
// and cache that provider until Retrieve is called again.
type ChainProvider struct {
Providers []Provider
current Provider
}

// NewChainCredentials returns a pointer to a new Credentials object
// wrapping a chain of providers.
func NewChainCredentials(providers []Provider) *Credentials {
return NewCredentials(&ChainProvider{
Providers: append([]Provider{}, providers...),
})
}

// Retrieve returns the first provider in the chain that succeeds,
// or error if no provider returned.
//
// If a provider is found it will be cached and any calls to IsExpired()
// will return the expired state of the cached provider.
func (c *ChainProvider) Retrieve() (Value, error) {
var errs = ErrNoValidCredentialProviders

for _, p := range c.Providers {
creds, err := p.Retrieve()
if err == nil {
c.current = p
return creds, nil
}

errs = fmt.Errorf("%v: %w", errs, err)
}
c.current = nil

return Value{}, fmt.Errorf("chain provider: %w", errs)
}

// IsExpired will returned the expired state of the currently cached provider
// if there is one. If there is no current provider, true will be returned.
func (c *ChainProvider) IsExpired() bool {
if c.current != nil {
return c.current.IsExpired()
}

return true
}
89 changes: 89 additions & 0 deletions v3/credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package credentials

import (
"errors"
"sync"
)

var (
ErrMissingIncomplete = errors.New("missing or incomplete API credentials")
)

type Value struct {
APIKey string
APISecret string
}

// IsSet returns true if the credentials Value has both APIKey and APISecret.
func (v Value) IsSet() bool {
return v.APIKey != "" && v.APISecret != ""
}

type Provider interface {
// Retrieve returns nil if it successfully retrieved the value.
// Error is returned if the value were not obtainable, or empty.
Retrieve() (Value, error)

// IsExpired returns if the credentials are no longer valid, and need
// to be retrieved.
IsExpired() bool
}

type Credentials struct {
credentials Value
provider Provider

sync.RWMutex
}

func NewCredentials(provider Provider) *Credentials {
creds := &Credentials{
provider: provider,
}

return creds
}

func (c *Credentials) Expire() {
c.Lock()
defer c.Unlock()

c.credentials = Value{}
}

func (c *Credentials) Get() (Value, error) {
if c.IsExpired() {
if err := c.retrieve(); err != nil {
return Value{}, err
}
}
c.RLock()
defer c.RUnlock()

if !c.credentials.IsSet() {
return Value{}, ErrMissingIncomplete
}

return c.credentials, nil
}

func (c *Credentials) IsExpired() bool {
c.RLock()
defer c.RUnlock()

return (!c.credentials.IsSet() || c.provider.IsExpired())
}

func (c *Credentials) retrieve() error {
c.Lock()
defer c.Unlock()

v, err := c.provider.Retrieve()
if err != nil {
return err
}

c.credentials = v

return nil
}
34 changes: 34 additions & 0 deletions v3/credentials/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package credentials

import "os"

type EnvProvider struct {
retrieved bool
}

func NewEnvCredentials() *Credentials {
return NewCredentials(&EnvProvider{})
}

// Retrieve retrieves the keys from the environment.
func (e *EnvProvider) Retrieve() (Value, error) {
e.retrieved = false

v := Value{
APIKey: os.Getenv("EXOSCALE_API_KEY"),
APISecret: os.Getenv("EXOSCALE_API_SECRET"),
}

if !v.IsSet() {
return Value{}, ErrMissingIncomplete
}

e.retrieved = true

return v, nil
}

// IsExpired returns if the credentials have been retrieved.
func (e *EnvProvider) IsExpired() bool {
return !e.retrieved
}
Loading

0 comments on commit be2da0e

Please # to comment.