From a238edf7e8d1db5fa56ac632e9df13b366e782e4 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Wed, 25 Mar 2020 23:45:21 -0700 Subject: [PATCH] support environment masking --- environ/provider/combine.go | 14 ++--- environ/provider/combine_test.go | 23 +++++--- environ/provider/external.go | 12 ++++- environ/provider/external_test.go | 16 ++++-- environ/provider/provider.go | 9 +++- environ/provider/provider_test.go | 4 +- environ/provider/static.go | 4 +- environ/provider/static_test.go | 21 +++++--- environ/provider/util.go | 52 ++++++++++++++++++ environ/provider/util_test.go | 87 +++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 5 ++ 12 files changed, 215 insertions(+), 34 deletions(-) create mode 100644 environ/provider/util.go create mode 100644 environ/provider/util_test.go diff --git a/environ/provider/combine.go b/environ/provider/combine.go index 94521fa..0307b00 100644 --- a/environ/provider/combine.go +++ b/environ/provider/combine.go @@ -4,11 +4,7 @@ package provider -import ( - "context" - - "github.com/drone/runner-go/environ" -) +import "context" // Combine returns a new combined environment provider, // capable of sourcing environment variables from multiple @@ -21,16 +17,14 @@ type combined struct { sources []Provider } -func (p *combined) List(ctx context.Context, in *Request) (map[string]string, error) { - out := map[string]string{} +func (p *combined) List(ctx context.Context, in *Request) ([]*Variable, error) { + var out []*Variable 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) - } + out = append(out, got...) } return out, nil } diff --git a/environ/provider/combine_test.go b/environ/provider/combine_test.go index 0497223..d4e44c6 100644 --- a/environ/provider/combine_test.go +++ b/environ/provider/combine_test.go @@ -7,6 +7,8 @@ package provider import ( "errors" "testing" + + "github.com/google/go-cmp/cmp" ) func TestCombine(t *testing.T) { @@ -15,20 +17,29 @@ func TestCombine(t *testing.T) { aa := Static(a) bb := Static(b) p := Combine(aa, bb) - out, err := p.List(noContext, nil) + got, err := p.List(noContext, nil) if err != nil { t.Error(err) return } - if len(out) != 2 { + if len(got) != 2 { t.Errorf("Expect combined variable output") return } - if out["a"] != "b" { - t.Errorf("Missing variable") + want := []*Variable{ + { + Name: "a", + Data: "b", + Mask: false, + }, + { + Name: "c", + Data: "d", + Mask: false, + }, } - if out["c"] != "d" { - t.Errorf("Missing variable") + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf(diff) } } diff --git a/environ/provider/external.go b/environ/provider/external.go index faa7127..61e8bd5 100644 --- a/environ/provider/external.go +++ b/environ/provider/external.go @@ -40,7 +40,7 @@ type external struct { client environ.Plugin } -func (p *external) List(ctx context.Context, in *Request) (map[string]string, error) { +func (p *external) List(ctx context.Context, in *Request) ([]*Variable, error) { if p.client == nil { return nil, nil } @@ -74,5 +74,13 @@ func (p *external) List(ctx context.Context, in *Request) (map[string]string, er logger.Trace("environment: external: environment variable list returned") - return res, nil + var out []*Variable + for _, v := range res { + out = append(out, &Variable{ + Name: v.Name, + Data: v.Data, + Mask: v.Mask, + }) + } + return out, nil } diff --git a/environ/provider/external_test.go b/environ/provider/external_test.go index 673860a..e29e2cf 100644 --- a/environ/provider/external_test.go +++ b/environ/provider/external_test.go @@ -19,9 +19,17 @@ func TestExternal(t *testing.T) { Build: &drone.Build{Event: drone.EventPush}, Repo: &drone.Repo{Private: false}, } - want := map[string]string{"a": "b"} + res := []*environ.Variable{ + { + Name: "a", + Data: "b", + Mask: true, + }, + } + + want := []*Variable{{Name: "a", Data: "b", Mask: true}} provider := External("http://localhost", "secret", false) - provider.(*external).client = &mockPlugin{out: want} + provider.(*external).client = &mockPlugin{out: res} got, err := provider.List(noContext, req) if err != nil { t.Error(err) @@ -91,10 +99,10 @@ func TestMultiExternal(t *testing.T) { } type mockPlugin struct { - out map[string]string + out []*environ.Variable err error } -func (m *mockPlugin) List(context.Context, *environ.Request) (map[string]string, error) { +func (m *mockPlugin) List(context.Context, *environ.Request) ([]*environ.Variable, error) { return m.out, m.err } diff --git a/environ/provider/provider.go b/environ/provider/provider.go index d3dbe73..51b963a 100644 --- a/environ/provider/provider.go +++ b/environ/provider/provider.go @@ -19,9 +19,16 @@ type Request struct { Build *drone.Build } +// Variable defines an environment variable. +type Variable struct { + Name string + Data string + Mask bool +} + // 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) + List(context.Context, *Request) ([]*Variable, error) } diff --git a/environ/provider/provider_test.go b/environ/provider/provider_test.go index c440d2f..8212f52 100644 --- a/environ/provider/provider_test.go +++ b/environ/provider/provider_test.go @@ -11,10 +11,10 @@ import ( var noContext = context.Background() type mockProvider struct { - out map[string]string + out []*Variable err error } -func (p *mockProvider) List(context.Context, *Request) (map[string]string, error) { +func (p *mockProvider) List(context.Context, *Request) ([]*Variable, error) { return p.out, p.err } diff --git a/environ/provider/static.go b/environ/provider/static.go index ac80178..3129c3f 100644 --- a/environ/provider/static.go +++ b/environ/provider/static.go @@ -17,6 +17,6 @@ type static struct { params map[string]string } -func (p *static) List(context.Context, *Request) (map[string]string, error) { - return p.params, nil +func (p *static) List(context.Context, *Request) ([]*Variable, error) { + return ToSlice(p.params), nil } diff --git a/environ/provider/static_test.go b/environ/provider/static_test.go index b78e9f0..f7205c1 100644 --- a/environ/provider/static_test.go +++ b/environ/provider/static_test.go @@ -5,19 +5,28 @@ package provider import ( - "reflect" "testing" + + "github.com/google/go-cmp/cmp" ) func TestStatic(t *testing.T) { - a := map[string]string{"a": "b"} - p := Static(a) - b, err := p.List(noContext, nil) + in := map[string]string{"a": "b"} + + got, err := Static(in).List(noContext, nil) if err != nil { t.Error(err) return } - if !reflect.DeepEqual(a, b) { - t.Errorf("Unexpected environment variable output") + + want := []*Variable{ + { + Name: "a", + Data: "b", + }, + } + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf(diff) } } diff --git a/environ/provider/util.go b/environ/provider/util.go new file mode 100644 index 0000000..1ddbf88 --- /dev/null +++ b/environ/provider/util.go @@ -0,0 +1,52 @@ +// 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 + +// ToMap is a helper function that converts a list of +// variables to a map. +func ToMap(src []*Variable) map[string]string { + dst := map[string]string{} + for _, v := range src { + dst[v.Name] = v.Data + } + return dst +} + +// ToSlice is a helper function that converts a map of +// environment variables to a slice. +func ToSlice(src map[string]string) []*Variable { + var dst []*Variable + for k, v := range src { + dst = append(dst, &Variable{ + Name: k, + Data: v, + }) + } + return dst +} + +// FilterMasked is a helper function that filters a list of +// variable to return a list of masked variables only. +func FilterMasked(v []*Variable) []*Variable { + var filtered []*Variable + for _, vv := range v { + if vv.Mask { + filtered = append(filtered, vv) + } + } + return filtered +} + +// FilterUnmasked is a helper function that filters a list of +// variable to return a list of masked variables only. +func FilterUnmasked(v []*Variable) []*Variable { + var filtered []*Variable + for _, vv := range v { + if vv.Mask == false { + filtered = append(filtered, vv) + } + } + return filtered +} diff --git a/environ/provider/util_test.go b/environ/provider/util_test.go new file mode 100644 index 0000000..3086edf --- /dev/null +++ b/environ/provider/util_test.go @@ -0,0 +1,87 @@ +// 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 ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestToMap(t *testing.T) { + in := []*Variable{ + { + Name: "foo", + Data: "bar", + }, + } + want := map[string]string{ + "foo": "bar", + } + got := ToMap(in) + if diff := cmp.Diff(want, got); diff != "" { + t.Log(diff) + t.Errorf("Unexpected map value") + } +} + +func TestFromMap(t *testing.T) { + in := map[string]string{ + "foo": "bar", + } + want := []*Variable{ + { + Name: "foo", + Data: "bar", + }, + } + got := ToSlice(in) + if diff := cmp.Diff(want, got); diff != "" { + t.Log(diff) + t.Errorf("Unexpected variable list") + } +} + +func TestFilterMasked(t *testing.T) { + in := []*Variable{ + { + Name: "foo", + Data: "bar", + Mask: false, + }, + { + Name: "baz", + Data: "qux", + Mask: true, + }, + } + want := in[1:] + got := FilterMasked(in) + if diff := cmp.Diff(want, got); diff != "" { + t.Log(diff) + t.Errorf("Unexpected variable list") + } +} + +func TestFilterUnmasked(t *testing.T) { + in := []*Variable{ + { + Name: "foo", + Data: "bar", + Mask: true, + }, + { + Name: "baz", + Data: "qux", + Mask: false, + }, + } + want := in[1:] + got := FilterUnmasked(in) + if diff := cmp.Diff(want, got); diff != "" { + t.Log(diff) + t.Errorf("Unexpected variable list") + } +} diff --git a/go.mod b/go.mod index d214be8..0072aa1 100644 --- a/go.mod +++ b/go.mod @@ -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.1.1-0.20191119212130-1d2e07e87e79 + github.com/drone/drone-go v1.2.1-0.20200326064413-195394da1018 github.com/drone/envsubst v1.0.2 github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-multierror v1.0.0 diff --git a/go.sum b/go.sum index f8880df..edbc9ab 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,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.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/drone/drone-go v1.2.1-0.20200326061744-0158580ce4ea h1:bMFm53oPjGvjpdO7+mfK6Qak4+O5ri5UlmlvN6J/qvo= +github.com/drone/drone-go v1.2.1-0.20200326061744-0158580ce4ea/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4= +github.com/drone/drone-go v1.2.1-0.20200326064413-195394da1018 h1:aHRv4GohqzHXZEGks/Qyrd8kI7hkCdLhJO1QoYtQMjU= +github.com/drone/drone-go v1.2.1-0.20200326064413-195394da1018/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4= +github.com/drone/drone-go v1.3.1 h1:D4KXbauJtbT/zXk19TcMpu36F8GPOUcndIw7pOWsX6k= github.com/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo= github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=