helpers for sourcing global environment variables
parent
005c105d56
commit
77885dcc75
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/drone/runner-go/environ"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Combine returns a new combined environment provider,
|
||||||
|
// capable of sourcing environment variables from multiple
|
||||||
|
// providers.
|
||||||
|
func Combine(sources ...Provider) Provider {
|
||||||
|
return &combined{sources}
|
||||||
|
}
|
||||||
|
|
||||||
|
type combined struct {
|
||||||
|
sources []Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *combined) List(ctx context.Context, in *Request) (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
for _, source := range p.sources {
|
||||||
|
got, err := source.List(ctx, in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
out = environ.Combine(got, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCombine(t *testing.T) {
|
||||||
|
a := map[string]string{"a": "b"}
|
||||||
|
b := map[string]string{"c": "d"}
|
||||||
|
aa := Static(a)
|
||||||
|
bb := Static(b)
|
||||||
|
p := Combine(aa, bb)
|
||||||
|
out, err := p.List(noContext, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(out) != 2 {
|
||||||
|
t.Errorf("Expect combined variable output")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out["a"] != "b" {
|
||||||
|
t.Errorf("Missing variable")
|
||||||
|
}
|
||||||
|
if out["c"] != "d" {
|
||||||
|
t.Errorf("Missing variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombineError(t *testing.T) {
|
||||||
|
e := errors.New("not found")
|
||||||
|
m := mockProvider{err: e}
|
||||||
|
p := Combine(&m)
|
||||||
|
_, err := p.List(noContext, nil)
|
||||||
|
if err != e {
|
||||||
|
t.Errorf("Expect error")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drone/drone-go/plugin/environ"
|
||||||
|
"github.com/drone/runner-go/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// External returns a new external environment variable
|
||||||
|
// provider. This provider makes an external API call to
|
||||||
|
// list and return environment variables.
|
||||||
|
func External(endpoint, token string, insecure bool) Provider {
|
||||||
|
provider := &external{}
|
||||||
|
if endpoint != "" {
|
||||||
|
provider.client = environ.Client(endpoint, token, insecure)
|
||||||
|
}
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
type external struct {
|
||||||
|
client environ.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *external) List(ctx context.Context, in *Request) (map[string]string, error) {
|
||||||
|
if p.client == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := logger.FromContext(ctx)
|
||||||
|
|
||||||
|
// include a timeout to prevent an API call from
|
||||||
|
// hanging the build process indefinitely. The
|
||||||
|
// external service must return a request within
|
||||||
|
// one minute.
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &environ.Request{
|
||||||
|
Repo: *in.Repo,
|
||||||
|
Build: *in.Build,
|
||||||
|
}
|
||||||
|
res, err := p.client.List(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Debug("environment: external: cannot get environment variable list")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no error is returned and the list is empty,
|
||||||
|
// this indicates the client returned No Content,
|
||||||
|
// and we should exit with no credentials, but no error.
|
||||||
|
if len(res) == 0 {
|
||||||
|
logger.Trace("environment: external: environment variable list is empty")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Trace("environment: external: environment variable list returned")
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone-go/drone"
|
||||||
|
"github.com/drone/drone-go/plugin/environ"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExternal(t *testing.T) {
|
||||||
|
req := &Request{
|
||||||
|
Build: &drone.Build{Event: drone.EventPush},
|
||||||
|
Repo: &drone.Repo{Private: false},
|
||||||
|
}
|
||||||
|
want := map[string]string{"a": "b"}
|
||||||
|
provider := External("http://localhost", "secret", false)
|
||||||
|
provider.(*external).client = &mockPlugin{out: want}
|
||||||
|
got, err := provider.List(noContext, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want); diff != "" {
|
||||||
|
t.Errorf(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that if the remote API call to the
|
||||||
|
// external plugin returns an error, the provider returns the
|
||||||
|
// error to the caller.
|
||||||
|
func TestExternal_ClientError(t *testing.T) {
|
||||||
|
req := &Request{
|
||||||
|
Build: &drone.Build{Event: drone.EventPush},
|
||||||
|
Repo: &drone.Repo{Private: false},
|
||||||
|
}
|
||||||
|
want := errors.New("Not Found")
|
||||||
|
provider := External("http://localhost", "secret", false)
|
||||||
|
provider.(*external).client = &mockPlugin{err: want}
|
||||||
|
_, got := provider.List(noContext, req)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Want error %s, got %s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that if no endpoint is configured the
|
||||||
|
// provider exits immediately and returns a nil slice and nil
|
||||||
|
// error.
|
||||||
|
func TestExternal_NoEndpoint(t *testing.T) {
|
||||||
|
provider := External("", "", false)
|
||||||
|
res, err := provider.List(noContext, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expect nil error, provider disabled")
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
t.Errorf("Expect nil secret, provider disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that nil credentials and a nil error
|
||||||
|
// are returned if the registry endpoint returns no content.
|
||||||
|
func TestExternal_NotFound(t *testing.T) {
|
||||||
|
req := &Request{
|
||||||
|
Repo: &drone.Repo{},
|
||||||
|
Build: &drone.Build{},
|
||||||
|
}
|
||||||
|
provider := External("http://localhost", "secret", false)
|
||||||
|
provider.(*external).client = &mockPlugin{}
|
||||||
|
res, err := provider.List(noContext, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expect nil error, registry list empty")
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
t.Errorf("Expect nil registry credentials")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPlugin struct {
|
||||||
|
out map[string]string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPlugin) List(context.Context, *environ.Request) (map[string]string, error) {
|
||||||
|
return m.out, m.err
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package provider provides environment variables to
|
||||||
|
// a pipeline.
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/drone/drone-go/drone"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request provides arguments for requesting a environment
|
||||||
|
// variables from an environment Provider.
|
||||||
|
type Request struct {
|
||||||
|
Repo *drone.Repo
|
||||||
|
Build *drone.Build
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider is the interface that must be implemented by an
|
||||||
|
// environment provider.
|
||||||
|
type Provider interface {
|
||||||
|
// List returns a list of environment variables.
|
||||||
|
List(context.Context, *Request) (map[string]string, error)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noContext = context.Background()
|
||||||
|
|
||||||
|
type mockProvider struct {
|
||||||
|
out map[string]string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *mockProvider) List(context.Context, *Request) (map[string]string, error) {
|
||||||
|
return p.out, p.err
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// Static returns a new static environment variable provider.
|
||||||
|
// The static provider finds and returns the static list
|
||||||
|
// of static environment variables.
|
||||||
|
func Static(params map[string]string) Provider {
|
||||||
|
return &static{params}
|
||||||
|
}
|
||||||
|
|
||||||
|
type static struct {
|
||||||
|
params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *static) List(context.Context, *Request) (map[string]string, error) {
|
||||||
|
return p.params, nil
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Polyform License
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatic(t *testing.T) {
|
||||||
|
a := map[string]string{"a": "b"}
|
||||||
|
p := Static(a)
|
||||||
|
b, err := p.List(noContext, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a, b) {
|
||||||
|
t.Errorf("Unexpected environment variable output")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue