helpers for sourcing global environment variables

pull/1/head
Brad Rydzewski 5 years ago
parent 005c105d56
commit 77885dcc75

@ -34,7 +34,7 @@ func System(system *drone.System) map[string]string {
}
// Repo returns a set of environment variables containing
// repostiory metadata.
// repository metadata.
func Repo(repo *drone.Repo) map[string]string {
return map[string]string{
"DRONE_REPO": repo.Slug,

@ -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")
}
}

@ -8,7 +8,7 @@ require (
github.com/buildkite/yaml v2.1.0+incompatible
github.com/coreos/go-semver v0.3.0
github.com/docker/go-units v0.4.0
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba
github.com/drone/drone-go v1.1.1-0.20191119212130-1d2e07e87e79
github.com/google/go-cmp v0.3.0
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4

@ -14,6 +14,11 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba h1:GKiT4UPBligLXJAP1zRllHvTUygAAlgS3t9LM9aasp0=
github.com/drone/drone-go v1.0.5-0.20190504210458-4d6116b897ba/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4=
github.com/drone/drone-go v1.1.0 h1:2mritc5b7PhQWvILNyzaImZMRWVbMmmZ5Q0UDwwO7SI=
github.com/drone/drone-go v1.1.1-0.20191116205420-0558dc8f86d4 h1:3Oh/CK+UmQOJAsTdl/aLT+oUSZI5CbQgeSZjhlQ6qeY=
github.com/drone/drone-go v1.1.1-0.20191116205420-0558dc8f86d4/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4=
github.com/drone/drone-go v1.1.1-0.20191119212130-1d2e07e87e79 h1:jW+dJ8HrZ1CbazlsYoriOOCQnVJ2NkfNczLHs6UMU6I=
github.com/drone/drone-go v1.1.1-0.20191119212130-1d2e07e87e79/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=

Loading…
Cancel
Save