From a131d7d0c18b343f7ebe71f8022bd97350f75b48 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Wed, 9 Sep 2020 13:10:17 -0700 Subject: [PATCH 01/38] add windows CI build Co-authored-by: Harsimran Singh Maan --- .gitattributes | 2 ++ .github/workflows/ci.yaml | 47 ++++++++++++++++++++++++++--- .gitignore | 1 + Makefile | 16 +++++++--- internal/commands/component_test.go | 11 ++++--- internal/commands/fmt_test.go | 8 ++--- internal/commands/setup_test.go | 3 +- internal/eval/eval.go | 15 ++++++--- internal/eval/eval_test.go | 3 +- internal/model/app_test.go | 8 +++-- internal/testutil/testutil.go | 14 +++++++++ internal/vm/helm_test.go | 2 +- internal/vm/vm_test.go | 5 +-- 13 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 .gitattributes create mode 100644 internal/testutil/testutil.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..ce76443f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Ensure that git would checkout files with lf instead of crlf on windows +* text eol=lf diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7b51b147..7191f5b2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,18 +5,24 @@ on: branches: - master jobs: - build: + build-linux: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.14 - uses: actions/setup-go@v1 + uses: actions/setup-go@v2 with: go-version: 1.14 id: go + - name: Install helm + uses: azure/setup-helm@v1 + with: + version: "v3.3.1" # default is latest stable + id: helm + - name: Check out code into the Go module directory - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: Install command dependencies run: | @@ -25,7 +31,7 @@ jobs: echo "::set-env name=GOPATH::$HOME/go" echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" echo "::add-path::$HOME/go/bin" - make install-ci install + make install-ci create-cluster install - name: Build run: make build @@ -34,7 +40,7 @@ jobs: run: make lint - name: Test - run: TEST_FLAGS="-tags integration" make test + run: make test TEST_FLAGS="-tags integration" - name: Coverage Report run: make publish-coverage @@ -47,3 +53,34 @@ jobs: args: release --snapshot --skip-publish --rm-dist --release-notes .release-notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-windows: + name: Build(Windows) + runs-on: windows-latest + steps: + - name: Set up Go 1.14 + uses: actions/setup-go@v2 + with: + go-version: 1.14 + id: go + + - name: Install helm + uses: azure/setup-helm@v1 + with: + version: "v3.3.1" # default is latest stable + id: helm + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Install command dependencies + run: | + echo "::set-env name=GOPATH::$HOME/go" + echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" + echo "::add-path::$HOME/go/bin" + + - name: Build + run: make build + + - name: Test + run: make test diff --git a/.gitignore b/.gitignore index 213b275b..e0413b13 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ coverage.txt dist/ helm.tar.gz linux-amd64/ +darwin-amd64/ bin/ .release-notes.md .tools/ diff --git a/Makefile b/Makefile index cfa14929..42c01d29 100644 --- a/Makefile +++ b/Makefile @@ -56,12 +56,20 @@ check-format: $(foreach file,$(unformatted),$(\n) gofmt -w $(file))$(\n)),\ @echo All files are well formatted.\ ) + + .PHONY: install-ci -install-ci: .tools/kind - curl -sSL -o helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz - tar -xvzf helm.tar.gz - mv linux-amd64/helm $(GOPATH)/bin/ +install-ci: HELM_VERSION := 3.3.1 +install-ci: HELM_PLATFORM := $(shell uname| tr '[:upper:]' '[:lower:]') +install-ci: + # Refactor helm install into a separate step + # curl -sSL -o helm.tar.gz https://get.helm.sh/helm-v${HELM_VERSION}-${HELM_PLATFORM}-amd64.tar.gz + # tar -xvzf helm.tar.gz + # mv ${HELM_PLATFORM}-amd64/helm $(GOPATH)/bin/ curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.21.0 + +.PHONY: create-cluster +create-cluster: .tools/kind .tools/kind create cluster .PHONY: install diff --git a/internal/commands/component_test.go b/internal/commands/component_test.go index bec98737..3fe624f4 100644 --- a/internal/commands/component_test.go +++ b/internal/commands/component_test.go @@ -17,6 +17,7 @@ package commands import ( + "path/filepath" "regexp" "strings" "testing" @@ -34,9 +35,9 @@ func TestComponentListBasic(t *testing.T) { a := assert.New(t) a.Equal(4, len(lines)) s.assertOutputLineMatch(regexp.MustCompile(`COMPONENT\s+FILE`)) - s.assertOutputLineMatch(regexp.MustCompile(`cluster-objects\s+components/cluster-objects.yaml`)) - s.assertOutputLineMatch(regexp.MustCompile(`service2\s+components/service2.jsonnet`)) - s.assertOutputLineMatch(regexp.MustCompile(`test-job\s+components/test-job.yaml`)) + s.assertOutputLineMatch(regexp.MustCompile(`cluster-objects\s+` + regexp.QuoteMeta(filepath.FromSlash("components/cluster-objects.yaml")))) + s.assertOutputLineMatch(regexp.MustCompile(`service2\s+` + regexp.QuoteMeta(filepath.FromSlash("components/service2.jsonnet")))) + s.assertOutputLineMatch(regexp.MustCompile(`test-job\s+` + regexp.QuoteMeta(filepath.FromSlash("components/test-job.yaml")))) } func TestComponentListYAML(t *testing.T) { @@ -76,7 +77,7 @@ func TestComponentDiffBasic(t *testing.T) { require.NoError(t, err) s.assertOutputLineMatch(regexp.MustCompile(`--- baseline`)) s.assertOutputLineMatch(regexp.MustCompile(`\+\+\+ environment: dev`)) - s.assertOutputLineMatch(regexp.MustCompile(`\+service2\s+components/service2.jsonnet`)) + s.assertOutputLineMatch(regexp.MustCompile(`\+service2\s+` + regexp.QuoteMeta(filepath.FromSlash("components/service2.jsonnet")))) } func TestComponentDiff2Envs(t *testing.T) { @@ -86,7 +87,7 @@ func TestComponentDiff2Envs(t *testing.T) { require.NoError(t, err) s.assertOutputLineMatch(regexp.MustCompile(`--- environment: dev`)) s.assertOutputLineMatch(regexp.MustCompile(`\+\+\+ environment: prod`)) - s.assertOutputLineMatch(regexp.MustCompile(`\++service1\s+components/service1.jsonnet`)) + s.assertOutputLineMatch(regexp.MustCompile(`\++service1\s+` + regexp.QuoteMeta(filepath.FromSlash("components/service1.jsonnet")))) } func TestComponentDiff2EnvObjects(t *testing.T) { diff --git a/internal/commands/fmt_test.go b/internal/commands/fmt_test.go index 2137ed01..368c98d6 100644 --- a/internal/commands/fmt_test.go +++ b/internal/commands/fmt_test.go @@ -9,6 +9,8 @@ import ( "regexp" "testing" + "github.com/splunk/qbec/internal/testutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -95,7 +97,7 @@ func TestDoFmt(t *testing.T) { expectedErr string }{ {[]string{}, fmtCommandConfig{check: true, write: true}, `check and write are not supported together`}, - {[]string{"nonexistentfile"}, fmtCommandConfig{}, `stat nonexistentfile: no such file or directory`}, + {[]string{"nonexistentfile"}, fmtCommandConfig{}, testutil.FileNotFoundMessage}, {[]string{"testdata/qbec.yaml"}, fmtCommandConfig{formatYaml: true, config: &config{stdout: &b}}, ""}, {[]string{"testdata/components"}, fmtCommandConfig{formatJsonnet: true, config: &config{stdout: &b}}, ""}, } @@ -107,9 +109,7 @@ func TestDoFmt(t *testing.T) { require.Nil(t, err) } else { require.NotNil(t, err) - if test.expectedErr != err.Error() { - t.Errorf("Expected %v but got %v", test.expectedErr, err.Error()) - } + assert.Contains(t, err.Error(), test.expectedErr) } }) } diff --git a/internal/commands/setup_test.go b/internal/commands/setup_test.go index 03a316da..e1bd35aa 100644 --- a/internal/commands/setup_test.go +++ b/internal/commands/setup_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/splunk/qbec/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -132,7 +133,7 @@ func TestSetupEnvironments(t *testing.T) { fn: func(t *testing.T, s *scaffold) { err := s.executeCommand("env", "list", "-E", "testdata/extra-env2.yaml") require.NotNil(t, err) - assert.Contains(t, err.Error(), "no such file or directory") + assert.Contains(t, err.Error(), testutil.FileNotFoundMessage) }, }, { diff --git a/internal/eval/eval.go b/internal/eval/eval.go index ac67fddd..064cd74c 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "path/filepath" "sort" "strings" "sync" @@ -209,10 +210,14 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca jvm := ctx.vm(c.TopLevelVars) var inputCode string var contextFile string + var canonicalFiles []string + for _, f := range c.Files { + canonicalFiles = append(canonicalFiles, filepath.ToSlash(f)) + } switch { - case len(c.Files) > 1: + case len(canonicalFiles) > 1: var lines []string - for _, file := range c.Files { + for _, file := range canonicalFiles { code, _, err := evaluationCode(file) if err != nil { return nil, errors.Wrap(err, "eval code for "+file) @@ -223,9 +228,9 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca contextFile = "multi-file-loader.jsonnet" default: var err error - inputCode, contextFile, err = evaluationCode(c.Files[0]) + inputCode, contextFile, err = evaluationCode(canonicalFiles[0]) if err != nil { - return nil, errors.Wrap(err, "eval code for "+c.Files[0]) + return nil, errors.Wrap(err, "eval code for "+canonicalFiles[0]) } } evalCode, err := jvm.EvaluateSnippet(contextFile, inputCode) @@ -234,7 +239,7 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca } var data interface{} if err := json.Unmarshal([]byte(evalCode), &data); err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("unexpected unmarshal '%s'", c.Files[0])) + return nil, errors.Wrap(err, fmt.Sprintf("unexpected unmarshal '%s'", canonicalFiles[0])) } objs, err := walk(data) diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 990bcd8b..0df577f9 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/splunk/qbec/internal/model" + "github.com/splunk/qbec/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -200,7 +201,7 @@ func TestEvalComponentsEdges(t *testing.T) { }, asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { require.NotNil(t, err) - assert.Contains(t, err.Error(), "no such file") + assert.Contains(t, err.Error(), testutil.FileNotFoundMessage) }, }, { diff --git a/internal/model/app_test.go b/internal/model/app_test.go index 13109f32..4ae0d066 100644 --- a/internal/model/app_test.go +++ b/internal/model/app_test.go @@ -18,6 +18,7 @@ package model import ( "bytes" + "fmt" "os" "path/filepath" "strings" @@ -25,6 +26,7 @@ import ( "github.com/ghodss/yaml" "github.com/splunk/qbec/internal/sio" + "github.com/splunk/qbec/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -328,7 +330,7 @@ func TestAppNegative(t *testing.T) { { file: "non-existent.yaml", asserter: func(t *testing.T, err error) { - assert.Contains(t, err.Error(), "no such file or directory") + assert.Contains(t, err.Error(), testutil.FileNotFoundMessage) }, }, { @@ -370,7 +372,7 @@ func TestAppNegative(t *testing.T) { { file: "bad-comps.yaml", asserter: func(t *testing.T, err error) { - assert.Contains(t, err.Error(), "duplicate component a, found bad-comps/a.json and bad-comps/a.yaml") + assert.Contains(t, err.Error(), fmt.Sprintf("duplicate component a, found %s and %s", filepath.FromSlash("bad-comps/a.json"), filepath.FromSlash("bad-comps/a.yaml"))) }, }, { @@ -413,7 +415,7 @@ func TestAppNegative(t *testing.T) { { file: "bad-missing-env-file.yaml", asserter: func(t *testing.T, err error) { - assert.Contains(t, err.Error(), "missing-env.yaml: no such file or directory") + assert.Contains(t, err.Error(), "missing-env.yaml: "+testutil.FileNotFoundMessage) }, }, { diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go new file mode 100644 index 00000000..810acd29 --- /dev/null +++ b/internal/testutil/testutil.go @@ -0,0 +1,14 @@ +package testutil + +import ( + "runtime" +) + +// FileNotFoundMessage is the string to be used in test code for comparing to file not found error messages. +var FileNotFoundMessage = "no such file or directory" + +func init() { + if runtime.GOOS == "windows" { + FileNotFoundMessage = "The system cannot find the file specified." + } +} diff --git a/internal/vm/helm_test.go b/internal/vm/helm_test.go index a45e84d1..bc912d63 100644 --- a/internal/vm/helm_test.go +++ b/internal/vm/helm_test.go @@ -54,7 +54,7 @@ expandHelmTemplate( }, { namespace: 'my-ns', - name: 'my-name', + nameTemplate: 'my-name', thisFile: std.thisFile, verbose: true, } diff --git a/internal/vm/vm_test.go b/internal/vm/vm_test.go index ff4ce007..de38efcb 100644 --- a/internal/vm/vm_test.go +++ b/internal/vm/vm_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/spf13/cobra" + "github.com/splunk/qbec/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -297,7 +298,7 @@ func TestVMNegative(t *testing.T) { args: []string{"show", "--vm:tla-str-file=foo=bar"}, asserter: func(a *assert.Assertions, err error) { require.NotNil(t, err) - a.Contains(err.Error(), "open bar: no such file or directory") + a.Contains(err.Error(), "open bar: "+testutil.FileNotFoundMessage) }, }, { @@ -313,7 +314,7 @@ func TestVMNegative(t *testing.T) { args: []string{"show", "--vm:ext-str-list=no-such-file"}, asserter: func(a *assert.Assertions, err error) { require.NotNil(t, err) - a.Contains(err.Error(), "no such file or directory") + a.Contains(err.Error(), testutil.FileNotFoundMessage) }, }, { From bc0d072884621e41523f245f939fd24c7a98ea32 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Wed, 9 Sep 2020 14:41:49 -0700 Subject: [PATCH 02/38] add support for importing a glob of files with either import or importstr semantics (#153) This change introduces the ability to import a bag of data or libsonnet files. It provides the following import syntax: import 'glob-import:*.json' and import 'glob-importstr:*.yaml' The first form looks for all files that match the wildcard and returns an object keyed by relative file name with values importing the actual files. i.e. something like: { 'a.json': import 'a.json', 'b.json': import 'b.json', } The second form is very similar except that it uses 'importstr' for inner imports. The returned code looks like: { 'a.yaml': importstr 'a.yaml', 'b.yaml': importstr 'b.yaml', } This allows for loading optional overlay files, user data files and such without having to write out an import statement in the calling code for each file imported. This importer is enabled by default in qbec. --- .../{base.libsonnet => _.libsonnet} | 0 examples/test-app/environments/dev.libsonnet | 2 +- examples/test-app/environments/prod.libsonnet | 2 +- examples/test-app/lib/globutil.libsonnet | 61 ++++++ examples/test-app/params.libsonnet | 13 +- internal/vm/importers/api.go | 10 + internal/vm/importers/composite.go | 28 +++ internal/vm/importers/composite_test.go | 36 ++++ internal/vm/importers/file.go | 18 ++ internal/vm/importers/glob.go | 180 ++++++++++++++++++ internal/vm/importers/glob_test.go | 151 +++++++++++++++ .../vm/importers/testdata/example1/a.json | 3 + .../vm/importers/testdata/example1/b.json | 3 + .../vm/importers/testdata/example1/z.json | 3 + .../importers/testdata/example2/inc1/a.json | 3 + .../importers/testdata/example2/inc2/a.json | 3 + internal/vm/vm.go | 13 +- 17 files changed, 515 insertions(+), 14 deletions(-) rename examples/test-app/environments/{base.libsonnet => _.libsonnet} (100%) create mode 100644 examples/test-app/lib/globutil.libsonnet create mode 100644 internal/vm/importers/api.go create mode 100644 internal/vm/importers/composite.go create mode 100644 internal/vm/importers/composite_test.go create mode 100644 internal/vm/importers/file.go create mode 100644 internal/vm/importers/glob.go create mode 100644 internal/vm/importers/glob_test.go create mode 100644 internal/vm/importers/testdata/example1/a.json create mode 100644 internal/vm/importers/testdata/example1/b.json create mode 100644 internal/vm/importers/testdata/example1/z.json create mode 100644 internal/vm/importers/testdata/example2/inc1/a.json create mode 100644 internal/vm/importers/testdata/example2/inc2/a.json diff --git a/examples/test-app/environments/base.libsonnet b/examples/test-app/environments/_.libsonnet similarity index 100% rename from examples/test-app/environments/base.libsonnet rename to examples/test-app/environments/_.libsonnet diff --git a/examples/test-app/environments/dev.libsonnet b/examples/test-app/environments/dev.libsonnet index 19d51695..d3f8c88b 100644 --- a/examples/test-app/environments/dev.libsonnet +++ b/examples/test-app/environments/dev.libsonnet @@ -1,4 +1,4 @@ -local base = import './base.libsonnet'; +local base = import '_.libsonnet'; base { components +: { diff --git a/examples/test-app/environments/prod.libsonnet b/examples/test-app/environments/prod.libsonnet index 8ea8ed7c..12ab5f0c 100644 --- a/examples/test-app/environments/prod.libsonnet +++ b/examples/test-app/environments/prod.libsonnet @@ -1,4 +1,4 @@ -local base = import './base.libsonnet'; +local base = import '_.libsonnet'; base { components +: { diff --git a/examples/test-app/lib/globutil.libsonnet b/examples/test-app/lib/globutil.libsonnet new file mode 100644 index 00000000..cca87cb7 --- /dev/null +++ b/examples/test-app/lib/globutil.libsonnet @@ -0,0 +1,61 @@ +local len = std.length; +local split = std.split; +local join = std.join; + +// keepDirs returns a key mapping function for the number of directories to be retained +local keepDirs = function(num=0) function(s) ( + if num < 0 + then + s + else ( + local elems = split(s, '/'); + local preserveRight = num + 1; + if len(elems) <= preserveRight + then + s + else ( + local remove = len(elems) - preserveRight; + join('/', elems[remove:]) + ) + ) +); + +// stripExtension is a key mapping function that strips the file extension from the key +local stripExtension = function(s) ( + local parts = split(s, '/'); + local dirs = parts[:len(parts) - 1]; + local file = parts[len(parts) - 1]; + local fileParts = split(file, '.'); + local fixed = if len(fileParts) == 1 then file else join('.', fileParts[:len(fileParts) - 1]); + join('/', dirs + [fixed]) +); + +// compose composes an array of map functions by applying them in sequence +local compose = function(arr) function(s) std.foldl(function(prev, fn) fn(prev), arr, s); + +// transform transforms an object, mapping keys using the key mapper and values using the valueMapper. +// It ensures that the key mapping does not produce duplicate keys. +local transform = function(globObject, keyMapper=function(s) s, valueMapper=function(o) o) ( + local keys = std.objectFields(globObject); + std.foldl(function(obj, key) ( + local mKey = keyMapper(key); + local val = globObject[key]; + if std.objectHas(obj, mKey) + then + error 'multiple keys map to the same value: %s' % [mKey] + else + obj { [mKey]: valueMapper(val) } + ), keys, {}) +); + +// nameOnly is a key mapper that removes all directories and strips extensions from file names, +// syntax sugar for the common case. +local nameOnly = compose([keepDirs(0), stripExtension]); + +{ + transform:: transform, + keepDirs:: keepDirs, + stripExtension:: stripExtension, + compose:: compose, + nameOnly:: nameOnly, +} diff --git a/examples/test-app/params.libsonnet b/examples/test-app/params.libsonnet index c5afcdfa..ef9f0569 100644 --- a/examples/test-app/params.libsonnet +++ b/examples/test-app/params.libsonnet @@ -1,10 +1,5 @@ -local p = { - _: import './environments/base.libsonnet', - dev: import './environments/dev.libsonnet', - prod: import './environments/prod.libsonnet', -}; - -local env = std.extVar('qbec.io/env'); - -if std.objectHas(p, env) then p[env] else error 'Environment ' + env + ' not defined in ' + std.thisFile +local globutil = import 'globutil.libsonnet'; +local p = globutil.transform(import 'glob-import:environments/*.libsonnet', globutil.nameOnly); +local key = std.extVar('qbec.io/env'); +if std.objectHas(p, key) then p[key] else error 'Environment ' + key + ' not defined in environments/' diff --git a/internal/vm/importers/api.go b/internal/vm/importers/api.go new file mode 100644 index 00000000..68aad8aa --- /dev/null +++ b/internal/vm/importers/api.go @@ -0,0 +1,10 @@ +package importers + +import "github.com/google/go-jsonnet" + +// ExtendedImporter extends the jsonnet importer interface to add a new method that can determine whether +// an importer can be used for a path. +type ExtendedImporter interface { + jsonnet.Importer + CanProcess(path string) bool +} diff --git a/internal/vm/importers/composite.go b/internal/vm/importers/composite.go new file mode 100644 index 00000000..95834415 --- /dev/null +++ b/internal/vm/importers/composite.go @@ -0,0 +1,28 @@ +package importers + +import ( + "fmt" + + "github.com/google/go-jsonnet" +) + +// CompositeImporter tries multiple extended importers in sequence for a given path +type CompositeImporter struct { + importers []ExtendedImporter +} + +// NewCompositeImporter creates a composite importer with the supplied extended importers. Note that if +// two importers could match the same path, the first one will be used so order is important. +func NewCompositeImporter(importers ...ExtendedImporter) *CompositeImporter { + return &CompositeImporter{importers: importers} +} + +// Import implements the interface method by delegating to installed importers in sequence +func (c *CompositeImporter) Import(importedFrom, importedPath string) (contents jsonnet.Contents, foundAt string, err error) { + for _, importer := range c.importers { + if importer.CanProcess(importedPath) { + return importer.Import(importedFrom, importedPath) + } + } + return contents, foundAt, fmt.Errorf("no importer for path %s", importedPath) +} diff --git a/internal/vm/importers/composite_test.go b/internal/vm/importers/composite_test.go new file mode 100644 index 00000000..55183818 --- /dev/null +++ b/internal/vm/importers/composite_test.go @@ -0,0 +1,36 @@ +package importers + +import ( + "testing" + + "github.com/google/go-jsonnet" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCompositeImporter(t *testing.T) { + vm := jsonnet.MakeVM() + vm.Importer( + NewCompositeImporter( + NewGlobImporter("import"), + NewGlobImporter("importstr"), + NewFileImporter(&jsonnet.FileImporter{}), + ), + ) + _, err := vm.EvaluateSnippet("testdata/example1/caller/caller.json", `import '../a.json'`) + require.NoError(t, err) + + _, err = vm.EvaluateSnippet("testdata/example1/caller/caller.json", `import 'glob-import:../*.json'`) + require.NoError(t, err) + + vm = jsonnet.MakeVM() + vm.Importer( + NewCompositeImporter( + NewGlobImporter("import"), + NewGlobImporter("importstr"), + ), + ) + _, err = vm.EvaluateSnippet("testdata/example1/caller/caller.json", `import '../bag-of-files/a.json'`) + require.Error(t, err) + assert.Contains(t, err.Error(), "RUNTIME ERROR: no importer for path ../bag-of-files/a.json") +} diff --git a/internal/vm/importers/file.go b/internal/vm/importers/file.go new file mode 100644 index 00000000..a518e4ce --- /dev/null +++ b/internal/vm/importers/file.go @@ -0,0 +1,18 @@ +package importers + +import "github.com/google/go-jsonnet" + +// NewFileImporter creates an extended file importer, wrapping the one supplied. +func NewFileImporter(jfi *jsonnet.FileImporter) *ExtendedFileImporter { + return &ExtendedFileImporter{FileImporter: jfi} +} + +// ExtendedFileImporter wraps a file importer and declares that it can import any path. +type ExtendedFileImporter struct { + *jsonnet.FileImporter +} + +// CanProcess implements the interface method. +func (e *ExtendedFileImporter) CanProcess(_ string) bool { + return true +} diff --git a/internal/vm/importers/glob.go b/internal/vm/importers/glob.go new file mode 100644 index 00000000..b7d77d36 --- /dev/null +++ b/internal/vm/importers/glob.go @@ -0,0 +1,180 @@ +package importers + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "fmt" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/google/go-jsonnet" +) + +// globCacheKey is the key to use for the importer cache. Two entries are equivalent if +// they resolve to the same set of files, have the same relative path to access them, and +// the same inner verb for access. +type globCacheKey struct { + verb string // the inner verb used for import + resolved string // the glob pattern as resolved from the current working directory + relative string // the glob pattern as specified by the user +} + +var separator = []byte{0} + +// file returns a virtual file name as represented by this cache key, relative to the supplied base directory. +func (c globCacheKey) file(baseDir string) string { + h := sha256.New() + h.Write([]byte(c.verb)) + h.Write(separator) + h.Write([]byte(c.resolved)) + h.Write(separator) + h.Write([]byte(c.relative)) + baseName := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) + fileName := fmt.Sprintf("%s-%s.glob", baseName, c.verb) + return filepath.Join(baseDir, fileName) +} + +// globEntry is a cache entry for a glob path resolved from the current working directory. +type globEntry struct { + contents jsonnet.Contents + foundAt string + err error +} + +// GlobImporter provides facilities to import a bag of files using a glob pattern. Note that it will NOT +// honor any library paths and must be exactly resolved from the caller's location. It is initialized with +// a verb that configures how the inner imports are done (i.e. `import` or `importstr`) and it processes +// paths that start with `glob-{verb}:` +// +// After the marker prefix is stripped, the input is treated as a file pattern that is resolved using Go's glob functionality. +// The return value is an object that is keyed by file names relative to the import location with values +// importing the contents of the file. +// +// That is, given the following directory structure: +// +// lib +// - a.json +// - b.json +// caller +// - c.libsonnet +// +// where c.libsonnet has the following contents +// +// import 'glob-import:../lib/*.json' +// +// evaluating `c.libsonnet` will return jsonnet code of the following form: +// +// { +// '../lib/a.json': import '../lib/a.json', +// '../lib/b.json': import '../lib/b.json', +// } +// +type GlobImporter struct { + innerVerb string + prefix string + cache map[globCacheKey]*globEntry +} + +// NewGlobImporter creates a glob importer. +func NewGlobImporter(innerVerb string) *GlobImporter { + if !(innerVerb == "import" || innerVerb == "importstr") { + panic("invalid inner verb " + innerVerb + " for glob importer") + } + return &GlobImporter{ + innerVerb: innerVerb, + prefix: fmt.Sprintf("glob-%s:", innerVerb), + cache: map[globCacheKey]*globEntry{}, + } +} + +func (g *GlobImporter) cacheKey(resolved, relative string) globCacheKey { + return globCacheKey{ + verb: g.innerVerb, + resolved: resolved, + relative: relative, + } +} + +// getEntry returns an entry from the cache or nil, if not found. +func (g *GlobImporter) getEntry(resolved, relative string) *globEntry { + ret := g.cache[g.cacheKey(resolved, relative)] + return ret +} + +// setEntry sets the cache entry for the supplied path. +func (g *GlobImporter) setEntry(resolved, relative string, e *globEntry) { + g.cache[g.cacheKey(resolved, relative)] = e +} + +// CanProcess implements the interface method, returning true for paths that start with the string "glob:" +func (g *GlobImporter) CanProcess(path string) bool { + return strings.HasPrefix(path, g.prefix) +} + +// Import implements the interface method. +func (g *GlobImporter) Import(importedFrom, importedPath string) (contents jsonnet.Contents, foundAt string, err error) { + // baseDir is the directory from which things are relatively imported + baseDir, _ := path.Split(importedFrom) + + relativeGlob := strings.TrimPrefix(importedPath, g.prefix) + + if strings.HasPrefix(relativeGlob, "/") { + return contents, foundAt, fmt.Errorf("invalid glob pattern '%s', cannot be absolute", relativeGlob) + } + + baseDir = filepath.FromSlash(baseDir) + relativeGlob = filepath.FromSlash(relativeGlob) + + // globPath is the glob path relative to the working directory + globPath := filepath.Clean(filepath.Join(baseDir, relativeGlob)) + r := g.getEntry(globPath, relativeGlob) + if r != nil { + return r.contents, r.foundAt, r.err + } + + // once we have successfully gotten a glob path, we can store results in the cache + defer func() { + g.setEntry(globPath, relativeGlob, &globEntry{ + contents: contents, + foundAt: foundAt, + err: err, + }) + }() + + matches, err := filepath.Glob(globPath) + if err != nil { + return contents, foundAt, fmt.Errorf("unable to expand glob %q, %v", globPath, err) + } + + // convert matches to be relative to our baseDir + var relativeMatches []string + for _, m := range matches { + rel, err := filepath.Rel(baseDir, m) + if err != nil { + return contents, globPath, fmt.Errorf("could not resolve %s from %s", m, importedFrom) + } + relativeMatches = append(relativeMatches, rel) + } + + // ensure consistent order (not strictly required, makes it human friendly) + sort.Strings(relativeMatches) + + var out bytes.Buffer + out.WriteString("{\n") + for _, file := range relativeMatches { + file = filepath.ToSlash(file) + out.WriteString("\t") + _, _ = fmt.Fprintf(&out, `'%s': %s '%s',`, file, g.innerVerb, file) + out.WriteString("\n") + } + out.WriteString("}") + k := g.cacheKey(globPath, relativeGlob) + output := out.String() + + return jsonnet.MakeContents(output), + filepath.ToSlash(k.file(baseDir)), + nil +} diff --git a/internal/vm/importers/glob_test.go b/internal/vm/importers/glob_test.go new file mode 100644 index 00000000..3bf40f4a --- /dev/null +++ b/internal/vm/importers/glob_test.go @@ -0,0 +1,151 @@ +package importers + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-jsonnet" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeVM() (*jsonnet.VM, *GlobImporter) { + vm := jsonnet.MakeVM() + g1 := NewGlobImporter("import") + g2 := NewGlobImporter("importstr") + vm.Importer( + NewCompositeImporter( + g1, + g2, + NewFileImporter(&jsonnet.FileImporter{}), + ), + ) + return vm, g1 +} + +type outputData map[string]interface{} + +func evaluateVirtual(t *testing.T, vm *jsonnet.VM, virtFile string, code string) outputData { + jsonStr, err := vm.EvaluateSnippet(virtFile, code) + require.NoError(t, err) + t.Logf("input from '%s'\n%s\noutput:%s\n", virtFile, code, jsonStr) + var data outputData + err = json.Unmarshal([]byte(jsonStr), &data) + require.NoError(t, err) + return data +} + +func evaluateVirtualErr(t *testing.T, virtFile string, code string) error { + vm, _ := makeVM() + _, err := vm.EvaluateSnippet(virtFile, code) + require.Error(t, err) + return err +} + +func TestGlobSimple(t *testing.T) { + vm, _ := makeVM() + data := evaluateVirtual(t, vm, "testdata/caller.jsonnet", `import 'glob-import:example1/*.json'`) + for _, k := range []string{"a", "b", "z"} { + relFile := fmt.Sprintf("example1/%s.json", k) + val, ok := data[relFile] + require.True(t, ok) + mVal, ok := val.(map[string]interface{}) + require.True(t, ok) + _, ok = mVal[k] + assert.True(t, ok) + } +} + +func TestDuplicateFileName(t *testing.T) { + vm, _ := makeVM() + data := evaluateVirtual(t, vm, "testdata/example2/caller.jsonnet", `import 'glob-import:inc?/*.json'`) + _, firstOk := data["inc1/a.json"] + require.True(t, firstOk) + _, secondOk := data["inc2/a.json"] + require.True(t, secondOk) +} + +func TestGlobNoMatch(t *testing.T) { + vm, _ := makeVM() + data := evaluateVirtual(t, vm, "testdata/example1/caller/no-match.jsonnet", `import 'glob-import:*.json'`) + require.Equal(t, 0, len(data)) +} + +func TestGlobImportStr(t *testing.T) { + vm, _ := makeVM() + data, err := vm.EvaluateSnippet("testdata/example1/caller/synthesized.jsonnet", `importstr 'glob-import:../*.json'`) + require.NoError(t, err) + var str string + err = json.Unmarshal([]byte(data), &str) + require.NoError(t, err) + assert.Equal(t, `{ + '../a.json': import '../a.json', + '../b.json': import '../b.json', + '../z.json': import '../z.json', +}`, str) +} + +func TestGlobImportStrVerb(t *testing.T) { + vm, _ := makeVM() + data := evaluateVirtual(t, vm, "testdata/example1/caller/synthesized.jsonnet", `import 'glob-importstr:../*.json'`) + for _, k := range []string{"a", "b", "z"} { + val, ok := data[fmt.Sprintf("../%s.json", k)] + require.True(t, ok) + mVal, ok := val.(string) + require.True(t, ok) + assert.Contains(t, mVal, fmt.Sprintf("%q", k)) + } +} + +func TestGlobInternalCaching(t *testing.T) { + a := assert.New(t) + vm, gi := makeVM() + _ = evaluateVirtual(t, vm, "testdata/example1/caller/synthesized.jsonnet", `import 'glob-import:../*.json'`) + a.Equal(1, len(gi.cache)) + _ = evaluateVirtual(t, vm, "testdata/example1/caller/synthesized2.jsonnet", `import 'glob-import:../*.json'`) + a.Equal(1, len(gi.cache)) + _ = evaluateVirtual(t, vm, "testdata/example1/caller2/synthesized.jsonnet", `import 'glob-import:../*.json'`) + a.Equal(1, len(gi.cache)) + _ = evaluateVirtual(t, vm, "testdata/example1/caller/inner/synthesized.jsonnet", `import 'glob-import:../../*.json'`) + a.Equal(2, len(gi.cache)) + _ = evaluateVirtual(t, vm, "testdata/example1/caller/inner/synthesized.jsonnet", `import 'glob-import:../../[a,b].json'`) + a.Equal(3, len(gi.cache)) +} + +func TestGlobNegativeCases(t *testing.T) { + checkMsg := func(m string) func(t *testing.T, err error) { + return func(t *testing.T, err error) { + assert.Contains(t, err.Error(), m) + } + } + tests := []struct { + name string + expr string + asserter func(t *testing.T, err error) + }{ + { + name: "bad path", + expr: `import 'glob-import:/bag-of-files/*.json'`, + asserter: checkMsg(`RUNTIME ERROR: invalid glob pattern '/bag-of-files/*.json', cannot be absolute`), + }, + { + name: "bad pattern", + expr: `import 'glob-import:../[.json'`, + asserter: func(t *testing.T, err error) { + assert.Contains(t, err.Error(), `RUNTIME ERROR: unable to expand glob`) + assert.Contains(t, err.Error(), `[.json", syntax error in pattern`) + }, + }, + } + file := "testdata/example1/caller/synthesized.jsonnet" + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.asserter(t, evaluateVirtualErr(t, file, test.expr)) + }) + } +} + +func TestGlobInit(t *testing.T) { + require.Panics(t, func() { _ = NewGlobImporter("foobar") }) +} diff --git a/internal/vm/importers/testdata/example1/a.json b/internal/vm/importers/testdata/example1/a.json new file mode 100644 index 00000000..09d92d0c --- /dev/null +++ b/internal/vm/importers/testdata/example1/a.json @@ -0,0 +1,3 @@ +{ + "a": "the first letter of the alphabet" +} diff --git a/internal/vm/importers/testdata/example1/b.json b/internal/vm/importers/testdata/example1/b.json new file mode 100644 index 00000000..3d373b08 --- /dev/null +++ b/internal/vm/importers/testdata/example1/b.json @@ -0,0 +1,3 @@ +{ + "b": "the second letter of the alphabet" +} diff --git a/internal/vm/importers/testdata/example1/z.json b/internal/vm/importers/testdata/example1/z.json new file mode 100644 index 00000000..1079a143 --- /dev/null +++ b/internal/vm/importers/testdata/example1/z.json @@ -0,0 +1,3 @@ +{ + "z": "the last letter of the alphabet" +} diff --git a/internal/vm/importers/testdata/example2/inc1/a.json b/internal/vm/importers/testdata/example2/inc1/a.json new file mode 100644 index 00000000..97f7e168 --- /dev/null +++ b/internal/vm/importers/testdata/example2/inc1/a.json @@ -0,0 +1,3 @@ +{ + "a": "a" +} diff --git a/internal/vm/importers/testdata/example2/inc2/a.json b/internal/vm/importers/testdata/example2/inc2/a.json new file mode 100644 index 00000000..b42d34e3 --- /dev/null +++ b/internal/vm/importers/testdata/example2/inc2/a.json @@ -0,0 +1,3 @@ +{ + "a": "long form a" +} diff --git a/internal/vm/vm.go b/internal/vm/vm.go index e00548e2..2cd3795e 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -28,6 +28,7 @@ import ( "github.com/google/go-jsonnet" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/splunk/qbec/internal/vm/importers" ) // Config is the desired configuration of the Jsonnet VM. @@ -353,9 +354,15 @@ func New(config Config) *VM { if config.importer != nil { vm.Importer(config.importer) } else { - vm.Importer(&jsonnet.FileImporter{ - JPaths: config.libPaths, - }) + vm.Importer( + importers.NewCompositeImporter( + importers.NewGlobImporter("import"), + importers.NewGlobImporter("importstr"), + importers.NewFileImporter(&jsonnet.FileImporter{ + JPaths: config.libPaths, + }), + ), + ) } return &VM{VM: vm, config: config} } From 41d56b0441665700ef3fbaf116969d6896f8ab91 Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 9 Sep 2020 15:10:49 -0700 Subject: [PATCH 03/38] update changelog, up version --- CHANGELOG.md | 6 ++++++ Makefile | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef977ad..bb9aa906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog --- +## v0.12.3 (Sep 9, 2020) + +* Add ability to import a bag of files using a glob pattern (see #153 for details). At this point this should be + considered experimental. Do not rely on it yet until the next release when we will have docs for it. +* Add windows build in CI, thanks to @harsimranmaan + ## v0.12.2 (Aug 30, 2020) * Fix a bug where under certain circumstances of failed discovery, qbec would delete resources not meant to be deleted. diff --git a/Makefile b/Makefile index 42c01d29..c3ee2bba 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.12.2 +VERSION := 0.12.3 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From 0994bb2c661cfdd3a2154112009a76747043ecdd Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Wed, 9 Sep 2020 17:16:55 -0700 Subject: [PATCH 04/38] Make actions consistent between release and ci (#158) --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 54d3a4ec..6f3e04de 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,12 +11,12 @@ jobs: name: goreleaser steps: - name: Set up Go 1.14 - uses: actions/setup-go@v1 + uses: actions/setup-go@v2 with: go-version: 1.14 id: go - name: Check out code into the Go module directory - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: Install package dependencies run: | echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" From 5bdd81c21d6c9ccf8d7f4e441e35cc7dfbb07c01 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Wed, 9 Sep 2020 17:33:50 -0700 Subject: [PATCH 05/38] setup correct binary files (#159) --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index ce76443f..dbac7399 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ +*.pb-v1 binary +*.ico binary # Ensure that git would checkout files with lf instead of crlf on windows * text eol=lf From 732cc1998e66ddc7fd6e2503d4e2dd867342bd7c Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 9 Sep 2020 17:45:54 -0700 Subject: [PATCH 06/38] set no text attr for binary files --- .gitattributes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index dbac7399..2e2534bb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -*.pb-v1 binary -*.ico binary +*.pb-v1 -text +*.ico -text # Ensure that git would checkout files with lf instead of crlf on windows * text eol=lf From e46f566a737b20d80137ff842adc25b3e355861d Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 9 Sep 2020 17:54:08 -0700 Subject: [PATCH 07/38] set no text attr for binary files --- .gitattributes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 2e2534bb..3cadbdfc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -*.pb-v1 -text -*.ico -text # Ensure that git would checkout files with lf instead of crlf on windows * text eol=lf +*.pb-v1 -text +*.ico -text From 18894161e7ca2c74cc696dc8cb7007d7de82baae Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 9 Sep 2020 17:57:48 -0700 Subject: [PATCH 08/38] set no text attr for binary files --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 3cadbdfc..cc7bff70 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,5 @@ * text eol=lf *.pb-v1 -text *.ico -text +*.png -text + From 2005f81940742464212b87d02812225e57f1a27a Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Thu, 24 Sep 2020 22:26:59 -0700 Subject: [PATCH 09/38] add --wait-all in addition to --wait to wait on all objects (#164) Currently, if --wait is specified and the apply times out, if apply is run for a second time it will cheerfully succeed because it has already updated the deployment and doesn't wait for it again. Guard against this by allowing the caller to wait on all objects independent of whether they were changed. In the current commit, the --wait-all flag has a default of false to preserve backwards compatibility. The next minor version will change the default of both the --wait and --wait-all flags to true --- examples/test-app/lib/objects.libsonnet | 8 ++++++++ internal/commands/apply.go | 13 ++++++++----- internal/commands/apply_test.go | 2 ++ internal/commands/integration_test.go | 10 ++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/examples/test-app/lib/objects.libsonnet b/examples/test-app/lib/objects.libsonnet index ee601766..1c0241ff 100644 --- a/examples/test-app/lib/objects.libsonnet +++ b/examples/test-app/lib/objects.libsonnet @@ -25,7 +25,15 @@ name: name, }, spec: { + selector: { + matchLabels: { + app: name + }, + }, template: { + metadata: { + labels: { app: name }, + }, spec: { containers: [ { diff --git a/internal/commands/apply.go b/internal/commands/apply.go index fde17d84..c4ebfd3e 100644 --- a/internal/commands/apply.go +++ b/internal/commands/apply.go @@ -58,6 +58,7 @@ type applyCommandConfig struct { showDetails bool gc bool wait bool + waitAll bool waitTimeout time.Duration filterFunc func() (filterParams, error) } @@ -158,7 +159,7 @@ func doApply(args []string, config applyCommandConfig) error { } var stats applyStats - var changedObjects []model.K8sMeta + var waitObjects []model.K8sMeta printSyncStatus := func(name string, res *remote.SyncResult, err error) { if err != nil { @@ -201,8 +202,9 @@ func doApply(args []string, config applyCommandConfig) error { if err != nil { return err } - if res.Type == remote.SyncCreated || res.Type == remote.SyncUpdated { - changedObjects = append(changedObjects, metaWrap{K8sMeta: ob}) + shouldWait := config.waitAll || (res.Type == remote.SyncCreated || res.Type == remote.SyncUpdated) + if shouldWait { + waitObjects = append(waitObjects, metaWrap{K8sMeta: ob}) } stats.update(name, res) } @@ -259,11 +261,11 @@ func doApply(args []string, config applyCommandConfig) error { } defaultNs := config.app.DefaultNamespace(env) - if config.wait { + if config.wait || config.waitAll { wl := &waitListener{ displayNameFn: client.DisplayName, } - return applyWaitFn(changedObjects, + return applyWaitFn(waitObjects, func(obj model.K8sMeta) (watch.Interface, error) { return waitWatcher(client.ResourceInterface, nsWrap{K8sMeta: obj, ns: defaultNs}) @@ -295,6 +297,7 @@ func newApplyCommand(cp configProvider) *cobra.Command { cmd.Flags().BoolVar(&config.showDetails, "show-details", false, "show details for object operations") cmd.Flags().BoolVar(&config.gc, "gc", true, "garbage collect extra objects on the server") cmd.Flags().BoolVar(&config.wait, "wait", false, "wait for objects to be ready") + cmd.Flags().BoolVar(&config.waitAll, "wait-all", false, "wait for all objects to be ready, not just the ones that have changed") var waitTime string cmd.Flags().StringVar(&waitTime, "wait-timeout", "5m", "wait timeout") diff --git a/internal/commands/apply_test.go b/internal/commands/apply_test.go index 6cf5160f..24094cdc 100644 --- a/internal/commands/apply_test.go +++ b/internal/commands/apply_test.go @@ -55,9 +55,11 @@ func TestNsWrap(t *testing.T) { func TestApplyBasic(t *testing.T) { s := newScaffold(t) defer s.reset() + origWait := applyWaitFn applyWaitFn = func(objects []model.K8sMeta, wp rollout.WatchProvider, opts rollout.WaitOptions) (finalErr error) { return nil } + defer func() { applyWaitFn = origWait }() first := true var captured remote.SyncOptions s.client.syncFunc = func(obj model.K8sLocalObject, opts remote.SyncOptions) (*remote.SyncResult, error) { diff --git a/internal/commands/integration_test.go b/internal/commands/integration_test.go index 0ac04a7f..9ff91365 100644 --- a/internal/commands/integration_test.go +++ b/internal/commands/integration_test.go @@ -101,6 +101,16 @@ func TestIntegrationBasic(t *testing.T) { a.EqualValues(1, stats["same"]) a.EqualValues(2, len(stats["updated"].([]interface{}))) }) + t.Run("apply3", func(t *testing.T) { + s := newIntegrationScaffold(t, ns, dir) + defer s.reset() + err := s.executeCommand(append(changeArgs, "apply", "local", "--wait", "--wait-all")...) + require.NoError(t, err) + stats := s.outputStats() + a := assert.New(t) + a.EqualValues(3, stats["same"]) + s.assertErrorLineMatch(regexp.MustCompile(`waiting for readiness of 1 objects`)) + }) } func TestIntegrationLazyCustomResources(t *testing.T) { From 4cb7cf797c1b98353c1e6ecb6f27331c65861e2a Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Thu, 24 Sep 2020 22:52:34 -0700 Subject: [PATCH 10/38] update changelog, up version --- CHANGELOG.md | 5 +++++ Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb9aa906..5c47db4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog --- +## v0.12.4 (Sep 24, 2020) + +* Add `--wait-all` flag to the `apply` command to wait on all objects instead of just the ones that were changed in the + current run. + ## v0.12.3 (Sep 9, 2020) * Add ability to import a bag of files using a glob pattern (see #153 for details). At this point this should be diff --git a/Makefile b/Makefile index c3ee2bba..cd03c2d6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.12.3 +VERSION := 0.12.4 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From dbc69a23aebd4286e8de87cce4803b3f646b6ef0 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Sat, 26 Sep 2020 17:57:43 -0700 Subject: [PATCH 11/38] add wait policy directive to allow disabling wait on specific deployments (#165) --- internal/commands/apply.go | 8 +++- internal/commands/directives.go | 12 ++++++ internal/commands/directives_test.go | 11 +++++ internal/commands/integration_test.go | 28 +++++++++++++ .../wait/components/deployment/index.jsonnet | 41 +++++++++++++++++++ .../commands/testdata/projects/wait/qbec.yaml | 15 +++++++ internal/model/external-names.go | 2 + 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 internal/commands/testdata/projects/wait/components/deployment/index.jsonnet create mode 100644 internal/commands/testdata/projects/wait/qbec.yaml diff --git a/internal/commands/apply.go b/internal/commands/apply.go index c4ebfd3e..f6a43292 100644 --- a/internal/commands/apply.go +++ b/internal/commands/apply.go @@ -190,6 +190,7 @@ func doApply(args []string, config applyCommandConfig) error { } } + waitPolicy := newWaitPolicy() for _, ob := range objects { name := client.DisplayName(ob) res, err := client.Sync(ob, opts) @@ -204,7 +205,11 @@ func doApply(args []string, config applyCommandConfig) error { } shouldWait := config.waitAll || (res.Type == remote.SyncCreated || res.Type == remote.SyncUpdated) if shouldWait { - waitObjects = append(waitObjects, metaWrap{K8sMeta: ob}) + if waitPolicy.disableWait(ob) { + sio.Debugf("%s: wait disabled by policy\n", name) + } else { + waitObjects = append(waitObjects, metaWrap{K8sMeta: ob}) + } } stats.update(name, res) } @@ -268,7 +273,6 @@ func doApply(args []string, config applyCommandConfig) error { return applyWaitFn(waitObjects, func(obj model.K8sMeta) (watch.Interface, error) { return waitWatcher(client.ResourceInterface, nsWrap{K8sMeta: obj, ns: defaultNs}) - }, rollout.WaitOptions{ Listener: wl, diff --git a/internal/commands/directives.go b/internal/commands/directives.go index ab67b0ea..763a351f 100644 --- a/internal/commands/directives.go +++ b/internal/commands/directives.go @@ -102,3 +102,15 @@ func (d *deletePolicy) disableDelete(ob model.K8sMeta) bool { } return false } + +type waitPolicy struct { + defaultNS string +} + +func newWaitPolicy() *waitPolicy { + return &waitPolicy{} +} + +func (d *waitPolicy) disableWait(ob model.K8sMeta) bool { + return isSet(ob, model.QbecNames.Directives.WaitPolicy, policyNever, []string{policyDefault}) +} diff --git a/internal/commands/directives_test.go b/internal/commands/directives_test.go index c6a174e5..ebf885ea 100644 --- a/internal/commands/directives_test.go +++ b/internal/commands/directives_test.go @@ -117,3 +117,14 @@ func TestDirectivesDeletePolicy(t *testing.T) { a.True(dp.disableDelete(clusterNs)) a.False(dp.disableDelete(k8sMetaWithAnnotations("Namespace", "", "yyy", nil))) } + +func TestDirectivesWaitPolicy(t *testing.T) { + wp := newWaitPolicy() + a := assert.New(t) + ret := wp.disableWait(k8sMetaWithAnnotations("Deployment", "foo", "bar", nil)) + a.False(ret) + ret = wp.disableWait(k8sMetaWithAnnotations("Deployment", "foo", "bar", map[string]interface{}{ + "directives.qbec.io/wait-policy": "never", + })) + a.True(ret) +} diff --git a/internal/commands/integration_test.go b/internal/commands/integration_test.go index 9ff91365..bf32bfd3 100644 --- a/internal/commands/integration_test.go +++ b/internal/commands/integration_test.go @@ -141,3 +141,31 @@ func TestIntegrationLazyCustomResources(t *testing.T) { require.NoError(t, err2) <-done } + +func TestIntegrationWait(t *testing.T) { + dir := "testdata/projects/wait" + ns, reset := newNamespace(t) + defer reset() + + t.Run("apply", func(t *testing.T) { + s := newIntegrationScaffold(t, ns, dir) + defer s.reset() + err := s.executeCommand("apply", "local", "--wait-all") + require.NoError(t, err) + stats := s.outputStats() + a := assert.New(t) + a.EqualValues(1, len(stats["created"].([]interface{}))) + s.assertErrorLineMatch(regexp.MustCompile(`waiting for readiness of 1 objects`)) + }) + t.Run("apply-no-wait", func(t *testing.T) { + s := newIntegrationScaffold(t, ns, dir) + defer s.reset() + err := s.executeCommand("apply", "local", "--wait-all", "--vm:ext-code=wait=false") + require.NoError(t, err) + stats := s.outputStats() + a := assert.New(t) + a.EqualValues(1, len(stats["updated"].([]interface{}))) + s.assertErrorLineMatch(regexp.MustCompile(`waiting for readiness of 0 objects`)) + s.assertErrorLineMatch(regexp.MustCompile(`: wait disabled by policy`)) + }) +} diff --git a/internal/commands/testdata/projects/wait/components/deployment/index.jsonnet b/internal/commands/testdata/projects/wait/components/deployment/index.jsonnet new file mode 100644 index 00000000..03842789 --- /dev/null +++ b/internal/commands/testdata/projects/wait/components/deployment/index.jsonnet @@ -0,0 +1,41 @@ +local wait = std.extVar('wait'); +local annotations = if !wait then { 'directives.qbec.io/wait-policy': 'never' } else {}; + +{ + apiVersion: 'apps/v1', + kind: 'Deployment', + metadata: { + annotations: annotations, + labels: { + app: 'nginx', + }, + name: 'nginx-by-wait', + }, + spec: { + replicas: 1, + selector: { + matchLabels: { + app: 'nginx', + }, + }, + strategy: { + type: 'RollingUpdate', + }, + template: { + metadata: { + labels: { + app: 'nginx', + }, + }, + spec: { + containers: [ + { + image: 'nginx', + imagePullPolicy: 'Always', + name: 'nginx', + }, + ], + }, + }, + }, +} diff --git a/internal/commands/testdata/projects/wait/qbec.yaml b/internal/commands/testdata/projects/wait/qbec.yaml new file mode 100644 index 00000000..0356c978 --- /dev/null +++ b/internal/commands/testdata/projects/wait/qbec.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: wait +spec: + environments: + local: + context: kind-kind + defaultNamespace: default + vars: + external: + - name: wait + default: true + diff --git a/internal/model/external-names.go b/internal/model/external-names.go index 98f85750..15f584a9 100644 --- a/internal/model/external-names.go +++ b/internal/model/external-names.go @@ -27,6 +27,7 @@ type Directives struct { ApplyOrder string // numeric apply order for object DeletePolicy string // delete policy "default" | "never" UpdatePolicy string // update policy "default" | "never" + WaitPolicy string // wait policy "default" | "never" } // QbecNames is the set of names used by Qbec. @@ -57,5 +58,6 @@ var QbecNames = struct { ApplyOrder: QBECDirectivesNamespace + "apply-order", DeletePolicy: QBECDirectivesNamespace + "delete-policy", UpdatePolicy: QBECDirectivesNamespace + "update-policy", + WaitPolicy: QBECDirectivesNamespace + "wait-policy", }, } From 7e69a98c5e9faa72c6ce2403dfca398e5499748d Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Fri, 2 Oct 2020 10:41:38 -0700 Subject: [PATCH 12/38] update changelog, up version --- CHANGELOG.md | 6 ++++++ Makefile | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c47db4a..b7c6e8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog --- +## v0.12.5 (Oct 2, 2020) + +* Add a new `wait-policy` directive to disable waits on specific deployments + and daemonsets. The annotation `"directives.qbec.io/wait-policy": "never"` + will cause qbec to not wait on the deployment even if it has changed. + ## v0.12.4 (Sep 24, 2020) * Add `--wait-all` flag to the `apply` command to wait on all objects instead of just the ones that were changed in the diff --git a/Makefile b/Makefile index cd03c2d6..cd046c2e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.12.4 +VERSION := 0.12.5 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From 288ee443e51929f52f1b19421dad9a048a0a442a Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Sat, 21 Nov 2020 21:29:26 -0800 Subject: [PATCH 13/38] Only use lf on formatted test files (#160) * Only use lf on formatted test files * add yml * rebase gone wrong --- .gitattributes | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitattributes b/.gitattributes index cc7bff70..013d0122 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,3 @@ # Ensure that git would checkout files with lf instead of crlf on windows -* text eol=lf -*.pb-v1 -text -*.ico -text -*.png -text - +*.formatted text eol=lf +*.yml text eol=lf From eca67d0e94680524f6cab1347f802728135f2c19 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Sat, 21 Nov 2020 21:35:14 -0800 Subject: [PATCH 14/38] Remove goreleaser deprecation warnings (#161) See https://goreleaser.com/deprecations\#brewsgithub --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1e0b8dd5..261127b0 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -35,7 +35,7 @@ archives: brews: - name: qbec - github: + tap: owner: splunk name: homebrew-tap url_template: https://github.com/splunk/qbec/releases/download/{{.Tag}}/{{.ArtifactName}} From 9bf09c0f807467354dcc376b95e87d0c80ceb749 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Sat, 21 Nov 2020 21:38:06 -0800 Subject: [PATCH 15/38] Move actions workflows to use new syntax (#169) * Move actions workflows to use new syntax Closes #168 * fix failure * Stage name is a bit misleading --- .github/workflows/ci.yaml | 24 +++++++++--------------- .github/workflows/release.yaml | 8 +++----- Makefile | 4 ++-- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7191f5b2..bbaf9e3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,28 +9,25 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - name: Set up Go 1.14 - uses: actions/setup-go@v2 + - uses: actions/setup-go@v2 with: go-version: 1.14 id: go - - name: Install helm - uses: azure/setup-helm@v1 + - uses: azure/setup-helm@v1 with: version: "v3.3.1" # default is latest stable id: helm - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: Install command dependencies run: | mkdir -p $HOME/go/bin export GOPATH=$HOME/go - echo "::set-env name=GOPATH::$HOME/go" - echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" - echo "::add-path::$HOME/go/bin" + echo "GOPATH=$GOPATH" >> $GITHUB_ENV + echo "GO_VERSION=$(go version | awk '{ print $3}' | sed 's/^go//')" >> $GITHUB_ENV + echo "$HOME/go/bin" >> $GITHUB_PATH make install-ci create-cluster install - name: Build @@ -58,20 +55,17 @@ jobs: name: Build(Windows) runs-on: windows-latest steps: - - name: Set up Go 1.14 - uses: actions/setup-go@v2 + - uses: actions/setup-go@v2 with: go-version: 1.14 id: go - - name: Install helm - uses: azure/setup-helm@v1 + - uses: azure/setup-helm@v1 with: version: "v3.3.1" # default is latest stable id: helm - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: Install command dependencies run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6f3e04de..f221b215 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,16 +10,14 @@ jobs: runs-on: ubuntu-latest name: goreleaser steps: - - name: Set up Go 1.14 - uses: actions/setup-go@v2 + - uses: actions/setup-go@v2 with: go-version: 1.14 id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: Install package dependencies run: | - echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" + echo "GO_VERSION=$(go version | awk '{ print $3}' | sed 's/^go//')" >> $GITHUB_ENV make get - name: Release via goreleaser uses: goreleaser/goreleaser-action@master diff --git a/Makefile b/Makefile index cd046c2e..c63d79ee 100644 --- a/Makefile +++ b/Makefile @@ -50,10 +50,10 @@ lint: check-format .PHONY: check-format check-format: @echo "Running gofmt..." - $(eval unformatted=$(shell find . -name '*.go' | grep -v ./.git | grep -v vendor | xargs gofmt -l)) + $(eval unformatted=$(shell find . -name '*.go' | grep -v ./.git | grep -v vendor | xargs gofmt -s -l)) $(if $(strip $(unformatted)),\ $(error $(\n) Some files are ill formatted! Run: \ - $(foreach file,$(unformatted),$(\n) gofmt -w $(file))$(\n)),\ + $(foreach file,$(unformatted),$(\n) gofmt -s -w $(file))$(\n)),\ @echo All files are well formatted.\ ) From 8322eff4ab2d7e4d030df81f6957a1ffb6ea690a Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Sat, 21 Nov 2020 22:00:29 -0800 Subject: [PATCH 16/38] eliminate deprecated syntax from windows build (#171) --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bbaf9e3b..ae07d8f3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,9 +69,9 @@ jobs: - name: Install command dependencies run: | - echo "::set-env name=GOPATH::$HOME/go" - echo "::set-env name=GO_VERSION::$(go version | awk '{ print $3}' | sed 's/^go//')" - echo "::add-path::$HOME/go/bin" + echo "GOPATH=$HOME/go" >> $GITHUB_ENV + echo "GO_VERSION=$(go version | awk '{ print $3}' | sed 's/^go//')" >> $GITHUB_ENV + echo "$HOME/go/bin" >> $GITHUB_PATH - name: Build run: make build From 94b7004247e569beac0ff8a4fabdd8d80e2b2a1a Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Sun, 22 Nov 2020 03:58:31 -0800 Subject: [PATCH 17/38] Update third party dependencies (#141) * BREAKING CHANGE: Update deps Update k8s api deps to v1.17.x Update the yaml formatter. Follows https://prettier.io/ formatting for lists :) Update cobra to v1 - seems backwards compatible Release suggestions: Worth using the new binary for various use-cases before cutting a new version. Update go 1.15 * update windows workflow to go 1.15 * Update deps * Revert version bump --- .github/workflows/ci.yaml | 6 +- .github/workflows/release.yaml | 4 +- .gitignore | 1 + go.mod | 53 +- go.sum | 516 +++++++++++++++--- internal/commands/config.go | 3 +- internal/commands/setup.go | 4 +- internal/commands/testdata/test.yml | 3 + internal/commands/testdata/test.yml.formatted | 9 +- internal/remote/k8smeta/schema.go | 2 +- 10 files changed, 494 insertions(+), 107 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ae07d8f3..213350b8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: 1.14 + go-version: 1.15 id: go - uses: azure/setup-helm@v1 @@ -45,7 +45,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: goreleaser - uses: goreleaser/goreleaser-action@master + uses: goreleaser/goreleaser-action@v2 with: args: release --snapshot --skip-publish --rm-dist --release-notes .release-notes.md env: @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: 1.14 + go-version: 1.15 id: go - uses: azure/setup-helm@v1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f221b215..d1b351af 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: 1.14 + go-version: 1.15 id: go - uses: actions/checkout@v2 - name: Install package dependencies @@ -20,7 +20,7 @@ jobs: echo "GO_VERSION=$(go version | awk '{ print $3}' | sed 's/^go//')" >> $GITHUB_ENV make get - name: Release via goreleaser - uses: goreleaser/goreleaser-action@master + uses: goreleaser/goreleaser-action@v2 with: args: release --rm-dist --release-notes .release-notes.md env: diff --git a/.gitignore b/.gitignore index e0413b13..655aea40 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ darwin-amd64/ bin/ .release-notes.md .tools/ +.vscode/ diff --git a/go.mod b/go.mod index 9653f909..22b4e8e3 100644 --- a/go.mod +++ b/go.mod @@ -1,47 +1,32 @@ module github.com/splunk/qbec -go 1.14 +go 1.15 require ( - cloud.google.com/go v0.44.3 // indirect github.com/chzyer/logex v1.1.10 // indirect - github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect - github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/ghodss/yaml v1.0.0 - github.com/go-openapi/analysis v0.19.0 // indirect - github.com/go-openapi/errors v0.19.0 // indirect - github.com/go-openapi/jsonpointer v0.19.0 // indirect - github.com/go-openapi/jsonreference v0.19.0 // indirect - github.com/go-openapi/loads v0.19.0 // indirect - github.com/go-openapi/runtime v0.19.0 // indirect - github.com/go-openapi/spec v0.18.0 - github.com/go-openapi/strfmt v0.18.0 - github.com/go-openapi/swag v0.19.0 // indirect - github.com/go-openapi/validate v0.18.0 - github.com/gogo/protobuf v1.2.1 // indirect - github.com/golang/protobuf v1.3.2 + github.com/go-openapi/spec v0.19.12 + github.com/go-openapi/strfmt v0.19.8 + github.com/go-openapi/validate v0.19.12 + github.com/golang/protobuf v1.4.3 github.com/google/go-jsonnet v0.16.0 github.com/googleapis/gnostic v0.2.0 - github.com/gophercloud/gophercloud v0.0.0-20190419190354-e53f839b56ea // indirect - github.com/imdario/mergo v0.3.7 // indirect github.com/jonboulle/clockwork v0.1.0 - github.com/json-iterator/go v1.1.7 // indirect - github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 // indirect + github.com/mailru/easyjson v0.7.1 // indirect github.com/mattn/go-isatty v0.0.11 - github.com/onsi/ginkgo v1.7.0 // indirect - github.com/onsi/gomega v1.4.3 // indirect - github.com/pkg/errors v0.8.1 + github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 - github.com/spf13/cobra v0.0.5 - github.com/stretchr/testify v1.4.0 - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c - k8s.io/api v0.0.0-20191016110246-af539daaa43a - k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 - k8s.io/client-go v0.0.0-20191016110837-54936ba21026 - k8s.io/gengo v0.0.0-20200728071708-7794989d0000 - k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 - k8s.io/kubernetes v1.14.1 + github.com/spf13/cobra v1.1.1 + github.com/stretchr/testify v1.6.1 + golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect + golang.org/x/text v0.3.3 // indirect + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 + k8s.io/api v0.17.13 + k8s.io/apimachinery v0.17.13 + k8s.io/client-go v0.17.13 + k8s.io/gengo v0.0.0-20201105220851-a92726d06152 + k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 + k8s.io/kubectl v0.17.13 ) diff --git a/go.sum b/go.sum index 5a496ffd..e1bb3d60 100644 --- a/go.sum +++ b/go.sum @@ -2,114 +2,273 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.3 h1:0sMegbmn/8uTwpNkB0q9cLEpZ2W5a6kl+wtBQgPWBJQ= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA= -github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 h1:JVe1zduaiPlSLOuQcU/MqRJkBbWRPsjdW48+20AtJXM= github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= -github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.0 h1:sYEyyO7OKQvJX0z4OyHWoGt0uLuALxB/ZJ4Jb3I6KNU= github.com/go-openapi/analysis v0.19.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.0 h1:guf3T2lnCBKlODmERt4T9GtMWRpJOikgKGyIvi0xcb8= github.com/go-openapi/errors v0.19.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.7 h1:Lcq+o0mSwCLKACMxZhreVHigB9ebghJ/lrmeaqASbjo= +github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8= -github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= -github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0 h1:wCOBNscACI8L93tt5tvB2zOMkJ098XCw3fP0BY2ybDA= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0 h1:sU6pp4dSV2sGlNKKyHxZzi1m1kG4WnYtWcJ+HYbygjE= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.16 h1:tQMAY5s5BfmmCC31+ufDCsGrr8iO1A8UIdYfDo5ADvs= +github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.12 h1:OO9WrvhDwtiMY/Opr1j1iFZzirI3JW4/bxNFRcntAr4= +github.com/go-openapi/spec v0.19.12/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0 h1:FqqmmVCKn3di+ilU/+1m957T1CnMz3IteVUcV3aGXWA= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.8 h1:9wAdSoImc5UCnUj79GhcjkJXxuU/nEwlbe6SKe9ZdRs= +github.com/go-openapi/strfmt v0.19.8/go.mod h1:qBBipho+3EoIqn6YDI+4RnQEtj6jT/IdKm+PAlXxSUc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8= -github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= +github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= github.com/go-openapi/validate v0.18.0 h1:PVXYcP1GkTl+XIAJnyJxOmK6CSG5Q1UcvoCvNO++5Kg= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-openapi/validate v0.19.12 h1:mPLM/bfbd00PGOCJlU0yJL7IulkZ+q9VjPv7U11RMQQ= +github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= +github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-jsonnet v0.16.0 h1:Nb4EEOp+rdeGGyB1rQ5eisgSAqrTnhf9ip+X6lzZbY0= github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -117,6 +276,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -125,45 +285,102 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gophercloud/gophercloud v0.0.0-20190419190354-e53f839b56ea h1:9BWcVDhDAavZkvtLa1uC7wF3inEEv1y4JpEQrKCh1zw= -github.com/gophercloud/gophercloud v0.0.0-20190419190354-e53f839b56ea/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -171,70 +388,165 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.4.2 h1:WlnEglfTg/PfPq4WXs2Vkl/5ICC6hoG8+r+LraPmGk4= +go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -243,67 +555,110 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= @@ -316,21 +671,36 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -338,30 +708,54 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.0.0-20191016110246-af539daaa43a h1:IocS6+jQEuO8ZGQXhrD9BZ7Ze+Ly6FUKPlYs/m4I6xo= -k8s.io/api v0.0.0-20191016110246-af539daaa43a/go.mod h1:ceHJE/vDjU8jKnRV6Vqn/+vyZmC6NvOluInN+RhQkIs= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762 h1:GYWOVyO+ZU+YK01nyPiAwB/fQrkxysXwkjbSpIIHdN4= -k8s.io/apimachinery v0.0.0-20191004115701-31ade1b30762/go.mod h1:Xc10RHc1U+F/e9GCloJ8QAeCGevSVP5xhOhqlE+e1kM= -k8s.io/client-go v0.0.0-20191016110837-54936ba21026 h1:HEJL/LGwm+NIepJIQML4AeU/BTn9vhDNnIE3qzpKL7g= -k8s.io/client-go v0.0.0-20191016110837-54936ba21026/go.mod h1:OjMHP19NDb8WkZ+pxaQjCDVs1IRkcpFn97Snv6K5w6A= -k8s.io/gengo v0.0.0-20200728071708-7794989d0000 h1:XgICMZutMLbopSVIJJrhUun6Hbuh1NTZBv2sd0lvypU= -k8s.io/gengo v0.0.0-20200728071708-7794989d0000/go.mod h1:aG2eeomYfcUw8sE3fa7YdkjgnGtyY56TjZlaJJ0ZoWo= -k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.17.13 h1:Yx/xYyFgBz2YgHoKxwmszhjXbibuvYhU3nWqweAucAQ= +k8s.io/api v0.17.13/go.mod h1:BXoqukCVV3sKdK6E2dBxC8aIKxftVRrpyd92HY3Qyjg= +k8s.io/apimachinery v0.17.13 h1:Xr39uO/hzxFSn8kCOkmUA2HKphb66TGhaiSLEbe/134= +k8s.io/apimachinery v0.17.13/go.mod h1:T54ZSpncArE25c5r2PbUPsLeTpkPWY/ivafigSX6+xk= +k8s.io/cli-runtime v0.17.13/go.mod h1:V7ZDVaOPMyRPCeFmw9QnUdbge1vbjcPVApsjM7aLwY4= +k8s.io/client-go v0.17.13 h1:oJ5eq/wwMvM4WDj1uawBaaJDZsxV0U0m/Q1iEjtEqbw= +k8s.io/client-go v0.17.13/go.mod h1:Z2RP7Rgd1ipVIQalAPBeWHcvxfaDHc6iNLPhWrkj9kw= +k8s.io/code-generator v0.17.13/go.mod h1:iiHz51+oTx+Z9D0vB3CH3O4HDDPWrvZyUgUYaIE9h9M= +k8s.io/component-base v0.17.13/go.mod h1:oMo4MumG5RRjqOXrCpVlQdP6hLrFbWaIcH8P5G99jeo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505 h1:ZY6yclUKVbZ+SdWnkfY+Je5vrMpKOxmGeKRbsXVmqYM= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201105220851-a92726d06152 h1:9oQviBt458i7F7GNoCfOP4mvrJf6L1tvHyx7DcQgHQE= +k8s.io/gengo v0.0.0-20201105220851-a92726d06152/go.mod h1:aG2eeomYfcUw8sE3fa7YdkjgnGtyY56TjZlaJJ0ZoWo= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kubernetes v1.14.1 h1:I9F52h5sqVxBmoSsBlNQ0YygNcukDilkpGxUbJRoBoY= -k8s.io/kubernetes v1.14.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= -k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= +k8s.io/kubectl v0.17.13 h1:M6546RDexLJl2N4zo6gf/EFsW4AsekodHAuXeGocBT8= +k8s.io/kubectl v0.17.13/go.mod h1:qFe0QvthFz97W3AFi6QaPZncsa5aIF8b+7e1Z/SIBdE= +k8s.io/metrics v0.17.13/go.mod h1:+iU0o18CGkSQsazuTgOucbVzxHVBPEtPpA66EQcbv3M= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/internal/commands/config.go b/internal/commands/config.go index df973772..38dbda19 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "strconv" "strings" @@ -363,7 +364,7 @@ func (c config) Confirm(context string) error { } inst, err := readline.NewEx(&readline.Config{ Prompt: "Do you want to continue [y/n]: ", - Stdin: c.stdin, + Stdin: ioutil.NopCloser(c.stdin), Stdout: c.stdout, Stderr: c.stderr, ForceUseInteractive: true, diff --git a/internal/commands/setup.go b/internal/commands/setup.go index dba8e0d5..539d20ab 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -41,8 +41,8 @@ var ( version = "dev" commit = "dev" goVersion = "unknown" - jsonnetVersion = "v0.16.0" // update this when library dependency is upgraded - clientGoVersion = "kubernetes-1.15.5" // ditto when client go dep is upgraded + jsonnetVersion = "v0.16.0" // update this when library dependency is upgraded + clientGoVersion = "kubernetes-1.17.13" // ditto when client go dep is upgraded ) // Executable is the name of the qbec executable. diff --git a/internal/commands/testdata/test.yml b/internal/commands/testdata/test.yml index fea372b1..1dac5110 100644 --- a/internal/commands/testdata/test.yml +++ b/internal/commands/testdata/test.yml @@ -4,6 +4,9 @@ b: - c: e: 4 a: 4 +e: +- f: 2 + g: 4 --- b: c diff --git a/internal/commands/testdata/test.yml.formatted b/internal/commands/testdata/test.yml.formatted index 934d1396..b33bbbfd 100644 --- a/internal/commands/testdata/test.yml.formatted +++ b/internal/commands/testdata/test.yml.formatted @@ -1,9 +1,12 @@ a: 1 # My super important comment b: -- c: - e: 4 - a: 4 + - c: + e: 4 + a: 4 +e: + - f: 2 + g: 4 --- b: c --- diff --git a/internal/remote/k8smeta/schema.go b/internal/remote/k8smeta/schema.go index 3a14a484..06d17614 100644 --- a/internal/remote/k8smeta/schema.go +++ b/internal/remote/k8smeta/schema.go @@ -26,7 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kube-openapi/pkg/util/proto/validation" - "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kubectl/pkg/util/openapi" ) // ErrSchemaNotFound is returned when a schema could not be found. From f41555204769d0fb6f308b1820c2c3b5b48c02d2 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Tue, 24 Nov 2020 22:47:35 -0800 Subject: [PATCH 18/38] Update jsonnet lib (#174) --- go.mod | 2 +- go.sum | 2 ++ internal/commands/setup.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 22b4e8e3..9bde7a96 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/go-openapi/strfmt v0.19.8 github.com/go-openapi/validate v0.19.12 github.com/golang/protobuf v1.4.3 - github.com/google/go-jsonnet v0.16.0 + github.com/google/go-jsonnet v0.17.0 github.com/googleapis/gnostic v0.2.0 github.com/jonboulle/clockwork v0.1.0 github.com/mailru/easyjson v0.7.1 // indirect diff --git a/go.sum b/go.sum index e1bb3d60..032d4b55 100644 --- a/go.sum +++ b/go.sum @@ -269,6 +269,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-jsonnet v0.16.0 h1:Nb4EEOp+rdeGGyB1rQ5eisgSAqrTnhf9ip+X6lzZbY0= github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= +github.com/google/go-jsonnet v0.17.0 h1:/9NIEfhK1NQRKl3sP2536b2+x5HnZMdql7x3yK/l8JY= +github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= diff --git a/internal/commands/setup.go b/internal/commands/setup.go index 539d20ab..cf8aa8c1 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -41,7 +41,7 @@ var ( version = "dev" commit = "dev" goVersion = "unknown" - jsonnetVersion = "v0.16.0" // update this when library dependency is upgraded + jsonnetVersion = "v0.17.0" // update this when library dependency is upgraded clientGoVersion = "kubernetes-1.17.13" // ditto when client go dep is upgraded ) From 2d7a7854699b3b554149ecb9c353b596cc25d043 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Tue, 24 Nov 2020 22:48:35 -0800 Subject: [PATCH 19/38] Add json formatter (#143) * Use the type flag for specifying format types --- go.mod | 3 +- go.sum | 12 ++-- internal/commands/examples.go | 8 +-- internal/commands/fmt.go | 71 +++++++++++++++---- internal/commands/fmt_test.go | 65 +++++++++++++++-- internal/commands/testdata/test.json | 39 ++++++++++ .../commands/testdata/test.json.formatted | 53 ++++++++++++++ 7 files changed, 218 insertions(+), 33 deletions(-) create mode 100644 internal/commands/testdata/test.json create mode 100644 internal/commands/testdata/test.json.formatted diff --git a/go.mod b/go.mod index 9bde7a96..23560dc6 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,13 @@ require ( github.com/google/go-jsonnet v0.17.0 github.com/googleapis/gnostic v0.2.0 github.com/jonboulle/clockwork v0.1.0 - github.com/mailru/easyjson v0.7.1 // indirect github.com/mattn/go-isatty v0.0.11 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 github.com/spf13/cobra v1.1.1 github.com/stretchr/testify v1.6.1 + github.com/tidwall/pretty v1.0.0 golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect - golang.org/x/text v0.3.3 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 k8s.io/api v0.17.13 k8s.io/apimachinery v0.17.13 diff --git a/go.sum b/go.sum index 032d4b55..27e0b940 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 h1:JVe1zduaiPlSLOuQcU/MqRJkBbWRPsjdW48+20AtJXM= -github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= @@ -117,8 +115,6 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.0 h1:sYEyyO7OKQvJX0z4OyHWoGt0uLuALxB/ZJ4Jb3I6KNU= -github.com/go-openapi/analysis v0.19.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= @@ -126,8 +122,6 @@ github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gm github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.0 h1:guf3T2lnCBKlODmERt4T9GtMWRpJOikgKGyIvi0xcb8= -github.com/go-openapi/errors v0.19.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= @@ -266,6 +260,7 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-jsonnet v0.16.0 h1:Nb4EEOp+rdeGGyB1rQ5eisgSAqrTnhf9ip+X6lzZbY0= github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= @@ -347,6 +342,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= @@ -394,6 +390,7 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -479,6 +476,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -652,6 +650,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= @@ -692,6 +691,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= diff --git a/internal/commands/examples.go b/internal/commands/examples.go index d2c77f0f..94439bd1 100644 --- a/internal/commands/examples.go +++ b/internal/commands/examples.go @@ -71,10 +71,10 @@ func fmtExamples() string { return exampleHelp( newExample("alpha fmt -w", "format all files in-place"), newExample("alpha fmt -e", "check if all files are formatted well. Non zero exit code in case a unformatted file is found"), - newExample("alpha fmt --jsonnet", "format all jsonnet and libsonnet files to stdout"), - newExample("alpha fmt -j somefolder file1.jsonnet file2.libsonnet", "format all jsonnet and libsonnet files in the somefolder, file1.jsonnet and file2.libsonnet files to stdout"), - newExample("alpha fmt --yaml -j=false", "format all yaml files to stdout"), - newExample("alpha fmt --yaml -j=false somefolder file1.yaml file2.yml", "format all yaml files in the somefolder, file1.yaml and file2.yml files to stdout"), + newExample("alpha fmt --type=jsonnet", "format all jsonnet and libsonnet files to stdout"), + newExample("alpha fmt somefolder file1.jsonnet file2.libsonnet", "format all json, jsonnet, libsonnet and yaml files in the somefolder, file1.jsonnet and file2.libsonnet files to stdout"), + newExample("alpha fmt -t=yaml", "format all yaml files to stdout"), + newExample("alpha fmt --type=json,yaml somefolder file1.yaml file2.yml file3.json", "format all json and yaml files in the somefolder, file1.yaml, file2.yml and file3.json files to stdout"), ) } diff --git a/internal/commands/fmt.go b/internal/commands/fmt.go index fbd7dc35..daafc5d5 100644 --- a/internal/commands/fmt.go +++ b/internal/commands/fmt.go @@ -3,6 +3,7 @@ package commands import ( "bufio" "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -13,16 +14,17 @@ import ( "github.com/google/go-jsonnet/formatter" "github.com/spf13/cobra" + "github.com/tidwall/pretty" "gopkg.in/yaml.v3" ) type fmtCommandConfig struct { *config - check bool - write bool - formatJsonnet bool - formatYaml bool - files []string + check bool + write bool + formatTypes map[string]bool + specifiedTypes []string + files []string } func doFmt(args []string, config *fmtCommandConfig) error { @@ -34,6 +36,21 @@ func doFmt(args []string, config *fmtCommandConfig) error { } else { config.files = []string{"."} } + config.formatTypes = make(map[string]bool) + isSupported := func(s string) bool { + for _, t := range supportedTypes { + if s == t { + return true + } + } + return false + } + for _, s := range config.specifiedTypes { + if !isSupported(s) { + return newUsageError(fmt.Sprintf("%q is not a supported type", s)) + } + config.formatTypes[s] = true + } for _, path := range config.files { switch dir, err := os.Stat(path); { case err != nil: @@ -51,6 +68,10 @@ func doFmt(args []string, config *fmtCommandConfig) error { return nil } +var ( + supportedTypes = []string{"json", "jsonnet", "yaml"} +) + func newFmtCommand(cp configProvider) *cobra.Command { cmd := &cobra.Command{ Use: "fmt", @@ -59,11 +80,9 @@ func newFmtCommand(cp configProvider) *cobra.Command { } config := fmtCommandConfig{} - cmd.Flags().BoolVarP(&config.check, "check-errors", "e", false, "check for unformatted files") cmd.Flags().BoolVarP(&config.write, "write", "w", false, "write result to (source) file instead of stdout") - cmd.Flags().BoolVarP(&config.formatJsonnet, "jsonnet", "j", true, "format jsonnet and libsonnet files") - cmd.Flags().BoolVar(&config.formatYaml, "yaml", false, "format yaml files") + cmd.Flags().StringSliceVarP(&config.specifiedTypes, "type", "t", supportedTypes, "file types that should be formatted") cmd.RunE = func(c *cobra.Command, args []string) error { config.config = cp() return wrapError(doFmt(args, &config)) @@ -80,15 +99,21 @@ func isJsonnetFile(f os.FileInfo) bool { name := f.Name() return !f.IsDir() && !strings.HasPrefix(name, ".") && getFileType(name) == "jsonnet" } + +func isJSONFile(f os.FileInfo) bool { + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && getFileType(name) == "json" +} + func shouldFormat(config *fmtCommandConfig, f os.FileInfo) bool { - if config.formatJsonnet && config.formatYaml { - return isJsonnetFile(f) || isYamlFile(f) + if isJsonnetFile(f) { + return config.formatTypes["jsonnet"] } - if config.formatJsonnet { - return isJsonnetFile(f) + if isYamlFile(f) { + return config.formatTypes["yaml"] } - if config.formatYaml { - return isYamlFile(f) + if isJSONFile(f) { + return config.formatTypes["json"] } return false } @@ -205,6 +230,9 @@ func format(in []byte, filename string) ([]byte, error) { if getFileType(filename) == "jsonnet" { return formatJsonnet(in) } + if getFileType(filename) == "json" { + return formatJSON(in) + } return nil, fmt.Errorf("unknown file type for file %q", filename) } @@ -265,6 +293,18 @@ func formatJsonnet(in []byte) ([]byte, error) { return []byte(ret), nil } +func formatJSON(in []byte) ([]byte, error) { + var j interface{} + decoder := json.NewDecoder(bytes.NewReader(in)) + decoder.UseNumber() + defaultOptions := pretty.DefaultOptions + // Make array values to spread across lines + defaultOptions.Width = -1 + //Validate input json + var err = decoder.Decode(&j) + return pretty.PrettyOptions(in, defaultOptions), err +} + func getFileType(filename string) string { if strings.HasSuffix(filename, ".yml") || strings.HasSuffix(filename, ".yaml") { return "yaml" @@ -272,6 +312,9 @@ func getFileType(filename string) string { if strings.HasSuffix(filename, ".jsonnet") || strings.HasSuffix(filename, ".libsonnet") { return "jsonnet" } + if strings.HasSuffix(filename, ".json") { + return "json" + } return "" } diff --git a/internal/commands/fmt_test.go b/internal/commands/fmt_test.go index 368c98d6..8a25cc37 100644 --- a/internal/commands/fmt_test.go +++ b/internal/commands/fmt_test.go @@ -39,16 +39,42 @@ func TestIsYaml(t *testing.T) { } } +func TestIsJson(t *testing.T) { + var tests = []struct { + fileName string + expected bool + }{ + {"testdata/qbec.yaml", false}, + {"testdata/test.yml", false}, + {"testdata", false}, + {"testdata/test.json", true}, + {"testdata/test.libsonnet", false}, + } + for _, test := range tests { + t.Run(test.fileName, func(t *testing.T) { + f, err := os.Stat(test.fileName) + if err != nil { + t.Fatalf("Unexpected error'%v'", err) + } + var actual = isJSONFile(f) + if test.expected != actual { + t.Errorf("Expected '%t', got '%t'", test.expected, actual) + } + }) + } +} + func TestShouldFormat(t *testing.T) { var tests = []struct { fileName string config fmtCommandConfig expected bool }{ - {"testdata/qbec.yaml", fmtCommandConfig{formatYaml: true}, true}, - {"testdata/test.yml", fmtCommandConfig{formatJsonnet: true}, false}, - {"testdata", fmtCommandConfig{formatYaml: true, formatJsonnet: true}, false}, - {"testdata/components/c1.jsonnet", fmtCommandConfig{formatJsonnet: true}, true}, + {"testdata/qbec.yaml", fmtCommandConfig{formatTypes: map[string]bool{"yaml": true, "jsonnet": true}}, true}, + {"testdata/test.yml", fmtCommandConfig{formatTypes: map[string]bool{"jsonnet": true}}, false}, + {"testdata", fmtCommandConfig{formatTypes: map[string]bool{"jsonnet": true, "json": true, "yaml": true}}, false}, + {"testdata/components/c1.jsonnet", fmtCommandConfig{formatTypes: map[string]bool{"jsonnet": true}}, true}, + {"testdata/test.json", fmtCommandConfig{formatTypes: map[string]bool{"json": true}}, true}, } for _, test := range tests { t.Run(test.fileName, func(t *testing.T) { @@ -98,8 +124,8 @@ func TestDoFmt(t *testing.T) { }{ {[]string{}, fmtCommandConfig{check: true, write: true}, `check and write are not supported together`}, {[]string{"nonexistentfile"}, fmtCommandConfig{}, testutil.FileNotFoundMessage}, - {[]string{"testdata/qbec.yaml"}, fmtCommandConfig{formatYaml: true, config: &config{stdout: &b}}, ""}, - {[]string{"testdata/components"}, fmtCommandConfig{formatJsonnet: true, config: &config{stdout: &b}}, ""}, + {[]string{"testdata/qbec.yaml"}, fmtCommandConfig{formatTypes: map[string]bool{"yaml": true}, config: &config{stdout: &b}}, ""}, + {[]string{"testdata/components"}, fmtCommandConfig{formatTypes: map[string]bool{"jsonnet": true}, config: &config{stdout: &b}}, ""}, } for i, test := range tests { @@ -159,16 +185,40 @@ func TestFormatJsonnet(t *testing.T) { if !bytes.Equal(o, e) { t.Errorf("Expected %q, got %q", string(e), string(o)) } + _, err = formatJsonnet([]byte("---")) + require.NotNil(t, err) +} + +func TestFormatJSON(t *testing.T) { + var testfile, err = ioutil.ReadFile("testdata/test.json") + require.Nil(t, err) + o, err := formatJSON(testfile) + require.Nil(t, err) + e, err := ioutil.ReadFile("testdata/test.json.formatted") + require.Nil(t, err) + if !bytes.Equal(o, e) { + t.Errorf("Expected %q, got %q", string(e), string(o)) + } + _, err = formatJSON([]byte("---")) + require.NotNil(t, err) } func TestFmtCommand(t *testing.T) { s := newScaffold(t) defer s.reset() - err := s.executeCommand("alpha", "fmt", "--yaml", "prod-env.yaml") + err := s.executeCommand("alpha", "fmt", "-t=yaml", "prod-env.yaml") require.Nil(t, err) s.assertOutputLineMatch(regexp.MustCompile(` - service2`)) } +func TestInvalidFormatType(t *testing.T) { + s := newScaffold(t) + defer s.reset() + err := s.executeCommand("alpha", "fmt", "-t=unknown", "prod-env.yaml") + require.NotNil(t, err) + require.Equal(t, `"unknown" is not a supported type`, err.Error()) +} + func TestProcessFile(t *testing.T) { var tests = []struct { @@ -177,6 +227,7 @@ func TestProcessFile(t *testing.T) { }{ {input: "testdata/test.libsonnet", output: "testdata/test.libsonnet.formatted"}, {input: "testdata/test.yml", output: "testdata/test.yml.formatted"}, + {input: "testdata/test.json", output: "testdata/test.json.formatted"}, } for _, test := range tests { t.Run(test.input, func(t *testing.T) { diff --git a/internal/commands/testdata/test.json b/internal/commands/testdata/test.json new file mode 100644 index 00000000..1cbf0a78 --- /dev/null +++ b/internal/commands/testdata/test.json @@ -0,0 +1,39 @@ +{"groups": {"kind": "APIGroupList", + "apiVersion": "v1", + "num": 1,"bool": true,"float": 1.12, +"groups": [ + { + "name": "", + "versions": [{ + "groupVersion": "v1", "version": "v1" + } + ],"preferredVersion": { + "groupVersion": "v1", + "version": "v1" + }, + "serverAddressByClientCIDRs": null + } + ] + }, + "resourceLists": { + ":v1": { + "kind": "APIResourceList", + "groupVersion": "v1","resources": [ + { + "name": "bindings", + "singularName": "", + "namespaced": true, + "kind": "Binding", + "verbs": ["create"] + }, + { + "name": "resourcequotas/status", + "singularName": "", + "namespaced": true, + "kind": "ResourceQuota", + "verbs": [ + "get", "patch", + "update" + ] + }] + }}} diff --git a/internal/commands/testdata/test.json.formatted b/internal/commands/testdata/test.json.formatted new file mode 100644 index 00000000..a5b6b97b --- /dev/null +++ b/internal/commands/testdata/test.json.formatted @@ -0,0 +1,53 @@ +{ + "groups": { + "kind": "APIGroupList", + "apiVersion": "v1", + "num": 1, + "bool": true, + "float": 1.12, + "groups": [ + { + "name": "", + "versions": [ + { + "groupVersion": "v1", + "version": "v1" + } + ], + "preferredVersion": { + "groupVersion": "v1", + "version": "v1" + }, + "serverAddressByClientCIDRs": null + } + ] + }, + "resourceLists": { + ":v1": { + "kind": "APIResourceList", + "groupVersion": "v1", + "resources": [ + { + "name": "bindings", + "singularName": "", + "namespaced": true, + "kind": "Binding", + "verbs": [ + "create" + ] + }, + { + "name": "resourcequotas/status", + "singularName": "", + "namespaced": true, + "kind": "ResourceQuota", + "verbs": [ + "get", + "patch", + "update" + ] + } + ] + } + } +} From a91a82c12b15f2ba1b4ce36714199779f69b2bc1 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Tue, 24 Nov 2020 22:49:41 -0800 Subject: [PATCH 20/38] Use namespaced queries when multiple namespaces present, allow override using spec property (#167) * refactor remote list to use a common function * add clusterScopedLists attribute to qbec spec, use namespace queries by default --- internal/commands/apply.go | 20 +------------------- internal/commands/common.go | 30 ++++++++++++++++++++++++++++++ internal/commands/delete.go | 13 +------------ internal/commands/diff.go | 20 +------------------- internal/commands/filter.go | 4 ---- internal/commands/remote-list.go | 4 ++++ internal/model/app.go | 6 ++++++ internal/model/swagger-schema.go | 6 +++++- internal/model/swagger.yaml | 3 +++ internal/model/types.go | 3 +++ internal/remote/client.go | 14 +++++++------- internal/remote/query.go | 4 ++-- 12 files changed, 63 insertions(+), 64 deletions(-) diff --git a/internal/commands/apply.go b/internal/commands/apply.go index f6a43292..f54d6b90 100644 --- a/internal/commands/apply.go +++ b/internal/commands/apply.go @@ -124,30 +124,12 @@ func doApply(args []string, config applyCommandConfig) error { // prepare for GC with object list of deletions var lister lister = &stubLister{} - var all []model.K8sLocalObject var retainObjects []model.K8sLocalObject if config.gc { - all, err = allObjects(config.config, env) + lister, retainObjects, err = startRemoteList(env, config.config, client, fp) if err != nil { return err } - for _, o := range all { - if o.GetName() != "" { - retainObjects = append(retainObjects, o) - } - } - var scope remote.ListQueryScope - lister, scope, err = newRemoteLister(client, all, config.app.DefaultNamespace(env)) - if err != nil { - return err - } - lister.start(remote.ListQueryConfig{ - Application: config.App().Name(), - Tag: config.App().Tag(), - Environment: env, - KindFilter: fp.GVKFilter, - ListQueryScope: scope, - }) } // continue with apply diff --git a/internal/commands/common.go b/internal/commands/common.go index 7f29a8f0..e0c4327a 100644 --- a/internal/commands/common.go +++ b/internal/commands/common.go @@ -179,3 +179,33 @@ func (lw *lockWriter) Write(buf []byte) (int, error) { lw.l.Unlock() return n, err } + +func startRemoteList(env string, config *config, client kubeClient, fp filterParams) (_ lister, retainObjects []model.K8sLocalObject, _ error) { + all, err := filteredObjects(config, env, nil, filterParams{}) + if err != nil { + return nil, nil, err + } + for _, o := range all { + if o.GetName() != "" { + retainObjects = append(retainObjects, o) + } + } + var scope remote.ListQueryScope + lister, scope, err := newRemoteLister(client, all, config.app.DefaultNamespace(env)) + if err != nil { + return nil, nil, err + } + clusterScopedLists := false + if len(scope.Namespaces) > 1 && config.app.ClusterScopedLists() { + clusterScopedLists = true + } + lister.start(remote.ListQueryConfig{ + Application: config.App().Name(), + Tag: config.App().Tag(), + Environment: env, + KindFilter: fp.GVKFilter, + ListQueryScope: scope, + ClusterScopedLists: clusterScopedLists, + }) + return lister, retainObjects, nil +} diff --git a/internal/commands/delete.go b/internal/commands/delete.go index 173000ed..748740ce 100644 --- a/internal/commands/delete.go +++ b/internal/commands/delete.go @@ -63,21 +63,10 @@ func doDelete(args []string, config deleteCommandConfig) error { } } } else { - all, err := allObjects(config.config, env) + lister, _, err := startRemoteList(env, config.config, client, fp) if err != nil { return err } - lister, scope, err := newRemoteLister(client, all, config.app.DefaultNamespace(env)) - if err != nil { - return err - } - lister.start(remote.ListQueryConfig{ - Application: config.App().Name(), - Tag: config.App().Tag(), - Environment: env, - KindFilter: fp.GVKFilter, - ListQueryScope: scope, - }) deletions, err = lister.deletions(nil, fp.Includes) if err != nil { return err diff --git a/internal/commands/diff.go b/internal/commands/diff.go index 529043c2..eab3c385 100644 --- a/internal/commands/diff.go +++ b/internal/commands/diff.go @@ -297,30 +297,12 @@ func doDiff(args []string, config diffCommandConfig) error { } var lister lister = &stubLister{} - var all []model.K8sLocalObject var retainObjects []model.K8sLocalObject if config.showDeletions { - all, err = allObjects(config.config, env) + lister, retainObjects, err = startRemoteList(env, config.config, client, fp) if err != nil { return err } - for _, o := range all { - if o.GetName() != "" { - retainObjects = append(retainObjects, o) - } - } - var scope remote.ListQueryScope - lister, scope, err = newRemoteLister(client, all, config.app.DefaultNamespace(env)) - if err != nil { - return err - } - lister.start(remote.ListQueryConfig{ - Application: config.App().Name(), - Tag: config.App().Tag(), - Environment: env, - KindFilter: fp.GVKFilter, - ListQueryScope: scope, - }) } objects = objsort.Sort(objects, sortConfig(client.IsNamespaced)) diff --git a/internal/commands/filter.go b/internal/commands/filter.go index edde9c65..2b89bd6f 100644 --- a/internal/commands/filter.go +++ b/internal/commands/filter.go @@ -77,10 +77,6 @@ func addFilterParams(cmd *cobra.Command, includeKindFilters bool) func() (filter // keyFunc is a function that provides a string key for an object type keyFunc func(object model.K8sMeta) string -func allObjects(cfg *config, env string) ([]model.K8sLocalObject, error) { - return filteredObjects(cfg, env, nil, filterParams{kindFilter: nil}) -} - func displayName(obj model.K8sLocalObject) string { group := obj.GroupVersionKind().Group if group != "" { diff --git a/internal/commands/remote-list.go b/internal/commands/remote-list.go index 5ed9cf9c..6298c5c4 100644 --- a/internal/commands/remote-list.go +++ b/internal/commands/remote-list.go @@ -31,8 +31,12 @@ type listClient interface { ListObjects(scope remote.ListQueryConfig) (remote.Collection, error) } +// lister lists remote objects and returns a list of objects to be deleted. type lister interface { + // start starts listing objects based on the supplied query config in the background. start(config remote.ListQueryConfig) + // deletions returns a list of objects to be deleted given a list of objects to be retained and + // a filter function that should return true for other objects if they can be deleted. deletions(ignore []model.K8sLocalObject, filter func(obj model.K8sQbecMeta) bool) ([]model.K8sQbecMeta, error) } diff --git a/internal/model/app.go b/internal/model/app.go index f8d680f0..573a20f5 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -583,3 +583,9 @@ func (a *App) updateComponentTopLevelVars() { a.allComponents[name] = comp } } + +// ClusterScopedLists returns the value of the qbec app attribute to determine if cluster scope +// lists should be performed when multiple namespaces are present. +func (a *App) ClusterScopedLists() bool { + return a.inner.Spec.ClusterScopedLists +} diff --git a/internal/model/swagger-schema.go b/internal/model/swagger-schema.go index 7cd0fd4e..f04c8bdf 100644 --- a/internal/model/swagger-schema.go +++ b/internal/model/swagger-schema.go @@ -1,6 +1,6 @@ package model -// generated by gen-qbec-swagger from ./internal/model/swagger.yaml at 2020-05-24 03:08:51.413936 +0000 UTC +// generated by gen-qbec-swagger from swagger.yaml at 2020-11-09 11:44:20.253614 +0000 UTC // Do NOT edit this file by hand var swaggerJSON = ` @@ -56,6 +56,10 @@ var swaggerJSON = ` "description": "properties for the baseline environment", "type": "object" }, + "clusterScopedLists": { + "description": "whether remote lists should use cluster scoped queries when multiple namespaces present", + "type": "boolean" + }, "componentsDir": { "description": "directory containing component files, default to components/", "type": "string" diff --git a/internal/model/swagger.yaml b/internal/model/swagger.yaml index 67d9d8e9..90cafd6c 100644 --- a/internal/model/swagger.yaml +++ b/internal/model/swagger.yaml @@ -110,6 +110,9 @@ definitions: baseProperties: description: properties for the baseline environment type: object + clusterScopedLists: + description: whether remote lists should use cluster scoped queries when multiple namespaces present + type: boolean vars: $ref: "#/definitions/qbec.io.v1alpha1.Variables" namespaceTagSuffix: diff --git a/internal/model/types.go b/internal/model/types.go index 86c48fe3..4dae7b61 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -101,6 +101,9 @@ type AppSpec struct { NamespaceTagSuffix bool `json:"namespaceTagSuffix,omitempty"` // properties for the baseline environment, can be used to define what env properties should look like BaseProperties map[string]interface{} `json:"baseProperties,omitempty"` + // whether remote lists for GC purposes should use cluster scoped queries + // when multiple namespaces are present. Not used when only one namespace is present. + ClusterScopedLists bool `json:"clusterScopedLists,omitempty"` } // QbecEnvironmentMapSpec is the spec for a QbecEnvironmentMap object. diff --git a/internal/remote/client.go b/internal/remote/client.go index 1b89fe48..2dc8e2a2 100644 --- a/internal/remote/client.go +++ b/internal/remote/client.go @@ -240,13 +240,13 @@ type GVKFilter func(gvk schema.GroupVersionKind) bool // ListQueryConfig is the config with which to execute list queries. type ListQueryConfig struct { - Application string // must be non-blank - Tag string // may be blank - Environment string // must be non-blank - ListQueryScope // the query scope for namespaces and non-namespaced resources - KindFilter GVKFilter // filters for group version kind - Concurrency int // concurrent queries to execute - DisableAllNsQueries bool // do not perform list queries across namespaces when multiple namespaces in picture + Application string // must be non-blank + Tag string // may be blank + Environment string // must be non-blank + ListQueryScope // the query scope for namespaces and non-namespaced resources + KindFilter GVKFilter // filters for group version kind + Concurrency int // concurrent queries to execute + ClusterScopedLists bool // perform list queries across namespaces when multiple namespaces in picture } // Collection represents a set of k8s objects with the ability to remove a subset of objects from it. diff --git a/internal/remote/query.go b/internal/remote/query.go index 3d7b86c9..cfab02f8 100644 --- a/internal/remote/query.go +++ b/internal/remote/query.go @@ -173,12 +173,12 @@ func (o *objectLister) serverObjects(coll *collection) error { if len(o.scope.Namespaces) > 0 { switch { - case o.scope.DisableAllNsQueries || len(o.scope.Namespaces) == 1: + case len(o.scope.Namespaces) == 1 || !o.scope.ClusterScopedLists: for _, ns := range o.scope.Namespaces { addQueries(o.namespacedTypes, ns) } default: - sio.Warnf("using cluster scoped queries since multiple namespaces present (%s)\n", strings.Join(o.scope.Namespaces, ", ")) + sio.Debugln("using cluster scoped queries for multiple namespaces") addQueries(o.namespacedTypes, "") } } From c9eec94787d5734008190e9e96c5041da283065b Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Tue, 24 Nov 2020 23:01:44 -0800 Subject: [PATCH 21/38] fix diff command to handle update and delete policies (#173) * fix diff command to handle update and delete policies * add integration tests --- internal/commands/diff.go | 54 ++++++++++++++++--- internal/commands/integration_test.go | 30 +++++++++++ .../projects/policies/components/cm.jsonnet | 16 ++++++ .../projects/policies/components/ns.jsonnet | 7 +++ .../policies/components/secret.jsonnet | 17 ++++++ .../testdata/projects/policies/qbec.yaml | 18 +++++++ 6 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 internal/commands/testdata/projects/policies/components/cm.jsonnet create mode 100644 internal/commands/testdata/projects/policies/components/ns.jsonnet create mode 100644 internal/commands/testdata/projects/policies/components/secret.jsonnet create mode 100644 internal/commands/testdata/projects/policies/qbec.yaml diff --git a/internal/commands/diff.go b/internal/commands/diff.go index eab3c385..d62bea1c 100644 --- a/internal/commands/diff.go +++ b/internal/commands/diff.go @@ -71,13 +71,19 @@ func (di diffIgnores) preprocess(obj *unstructured.Unstructured) { } } +type skipStats struct { + Updates []string `json:"updates,omitempty"` + Deletions []string `json:"deletions,omitempty"` +} + type diffStats struct { l sync.Mutex - Additions []string `json:"additions,omitempty"` - Changes []string `json:"changes,omitempty"` - Deletions []string `json:"deletions,omitempty"` - SameCount int `json:"same,omitempty"` - Errors []string `json:"errors,omitempty"` + Additions []string `json:"additions,omitempty"` + Changes []string `json:"changes,omitempty"` + Deletions []string `json:"deletions,omitempty"` + SameCount int `json:"same,omitempty"` + Errors []string `json:"errors,omitempty"` + Skipped *skipStats `json:"skipped,omitempty"` } func (d *diffStats) added(s string) { @@ -98,6 +104,24 @@ func (d *diffStats) deleted(s string) { d.Deletions = append(d.Deletions, s) } +func (d *diffStats) skippedUpdated(s string) { + d.l.Lock() + defer d.l.Unlock() + if d.Skipped == nil { + d.Skipped = &skipStats{} + } + d.Skipped.Updates = append(d.Skipped.Updates, s) +} + +func (d *diffStats) skippedDeletion(s string) { + d.l.Lock() + defer d.l.Unlock() + if d.Skipped == nil { + d.Skipped = &skipStats{} + } + d.Skipped.Deletions = append(d.Skipped.Deletions, s) +} + func (d *diffStats) same(s string) { d.l.Lock() defer d.l.Unlock() @@ -114,6 +138,10 @@ func (d *diffStats) done() { sort.Strings(d.Additions) sort.Strings(d.Changes) sort.Strings(d.Errors) + if d.Skipped != nil { + sort.Strings(d.Skipped.Deletions) + sort.Strings(d.Skipped.Updates) + } } type differ struct { @@ -124,6 +152,8 @@ type differ struct { ignores diffIgnores showSecrets bool verbose int + upPolicy *updatePolicy + delPolicy *deletePolicy } func (d *differ) names(ob model.K8sMeta) (name, leftName, rightName string) { @@ -178,8 +208,12 @@ func (d *differ) writeDiff(name string, left, right namedUn) (finalErr error) { } d.stats.same(name) } else { - fmt.Fprintln(d.w, string(b)) - d.stats.changed(name) + if d.upPolicy.disableUpdate(left.obj) { + d.stats.skippedUpdated(name) + } else { + fmt.Fprintln(d.w, string(b)) + d.stats.changed(name) + } } case left.obj == nil: rightContent, err := asYaml(right.obj) @@ -198,6 +232,10 @@ func (d *differ) writeDiff(name string, left, right namedUn) (finalErr error) { fmt.Fprintln(d.w, string(b)) d.stats.added(name) default: + if d.delPolicy.disableDelete(left.obj) { + d.stats.skippedDeletion(name) + break + } leftContent, err := asYaml(left.obj) if err != nil { return err @@ -322,6 +360,8 @@ func doDiff(args []string, config diffCommandConfig) error { ignores: config.di, showSecrets: config.showSecrets, verbose: config.Verbosity(), + upPolicy: newUpdatePolicy(), + delPolicy: newDeletePolicy(client.IsNamespaced, config.App().DefaultNamespace(env)), } dErr := runInParallel(objects, d.diffLocal, config.parallel) diff --git a/internal/commands/integration_test.go b/internal/commands/integration_test.go index bf32bfd3..1040e3e9 100644 --- a/internal/commands/integration_test.go +++ b/internal/commands/integration_test.go @@ -169,3 +169,33 @@ func TestIntegrationWait(t *testing.T) { s.assertErrorLineMatch(regexp.MustCompile(`: wait disabled by policy`)) }) } + +func TestIntegrationDiffPolicies(t *testing.T) { + dir := "testdata/projects/policies" + ns, reset := newNamespace(t) + defer reset() + + t.Run("apply", func(t *testing.T) { + s := newIntegrationScaffold(t, ns, dir) + defer s.reset() + err := s.executeCommand("apply", "local") + require.NoError(t, err) + stats := s.outputStats() + a := assert.New(t) + a.EqualValues(1, len(stats["updated"].([]interface{}))) + a.EqualValues(2, len(stats["created"].([]interface{}))) + }) + t.Run("diff", func(t *testing.T) { + s := newIntegrationScaffold(t, ns, dir) + defer s.reset() + err := s.executeCommand("diff", "local", "--vm:ext-code=hide=true", "--vm:ext-str=foo=xxx --error-exit=false") + require.NoError(t, err) + stats := s.outputStats() + sk := stats["skipped"] + skipped, ok := sk.(map[string]interface{}) + require.True(t, ok) + a := assert.New(t) + a.EqualValues(1, len(skipped["updates"].([]interface{}))) + a.EqualValues(1, len(skipped["deletions"].([]interface{}))) + }) +} diff --git a/internal/commands/testdata/projects/policies/components/cm.jsonnet b/internal/commands/testdata/projects/policies/components/cm.jsonnet new file mode 100644 index 00000000..666c4b83 --- /dev/null +++ b/internal/commands/testdata/projects/policies/components/cm.jsonnet @@ -0,0 +1,16 @@ +local foo = std.extVar('foo'); +local cm = { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'cm1', + annotations: { + 'directives.qbec.io/update-policy': 'never', + }, + }, + data: { + foo: foo, + }, +}; +cm + diff --git a/internal/commands/testdata/projects/policies/components/ns.jsonnet b/internal/commands/testdata/projects/policies/components/ns.jsonnet new file mode 100644 index 00000000..1f09a9e6 --- /dev/null +++ b/internal/commands/testdata/projects/policies/components/ns.jsonnet @@ -0,0 +1,7 @@ +{ + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: std.extVar('qbec.io/defaultNs'), + }, +} diff --git a/internal/commands/testdata/projects/policies/components/secret.jsonnet b/internal/commands/testdata/projects/policies/components/secret.jsonnet new file mode 100644 index 00000000..7fb2788b --- /dev/null +++ b/internal/commands/testdata/projects/policies/components/secret.jsonnet @@ -0,0 +1,17 @@ +local secret = { + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name: 's1', + annotations: { + 'directives.qbec.io/delete-policy': 'never', + }, + }, + stringData: { + foo: 'bar', + }, +}; +if std.extVar('hide') then null else secret + + + diff --git a/internal/commands/testdata/projects/policies/qbec.yaml b/internal/commands/testdata/projects/policies/qbec.yaml new file mode 100644 index 00000000..c4288ec5 --- /dev/null +++ b/internal/commands/testdata/projects/policies/qbec.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: policies +spec: + libPaths: + - lib + environments: + local: + context: kind-kind + defaultNamespace: foobar + vars: + external: + - name: foo + default: foobar + - name: hide + default: false From 1728fa6b1ddd3d66e2a6bb9c22d71ce622525c68 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 27 Nov 2020 06:44:02 -0800 Subject: [PATCH 22/38] change default values for flags in incompatible manner (#175) --- internal/commands/apply.go | 5 +++-- internal/commands/apply_test.go | 2 +- internal/commands/diff.go | 2 +- internal/commands/diff_test.go | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/internal/commands/apply.go b/internal/commands/apply.go index f54d6b90..c39b80e6 100644 --- a/internal/commands/apply.go +++ b/internal/commands/apply.go @@ -282,8 +282,8 @@ func newApplyCommand(cp configProvider) *cobra.Command { cmd.Flags().BoolVarP(&config.syncOptions.ShowSecrets, "show-secrets", "S", false, "do not obfuscate secret values in the output") cmd.Flags().BoolVar(&config.showDetails, "show-details", false, "show details for object operations") cmd.Flags().BoolVar(&config.gc, "gc", true, "garbage collect extra objects on the server") - cmd.Flags().BoolVar(&config.wait, "wait", false, "wait for objects to be ready") - cmd.Flags().BoolVar(&config.waitAll, "wait-all", false, "wait for all objects to be ready, not just the ones that have changed") + cmd.Flags().BoolVar(&config.wait, "wait", false, "wait for changed objects to be ready") + cmd.Flags().BoolVar(&config.waitAll, "wait-all", true, "wait for all objects to be ready, not just the ones that have changed") var waitTime string cmd.Flags().StringVar(&waitTime, "wait-timeout", "5m", "wait timeout") @@ -296,6 +296,7 @@ func newApplyCommand(cp configProvider) *cobra.Command { } if config.syncOptions.DryRun { config.wait = false + config.waitAll = false } if !cmd.Flag("show-details").Changed { config.showDetails = config.syncOptions.DryRun diff --git a/internal/commands/apply_test.go b/internal/commands/apply_test.go index 24094cdc..b3fb5ea7 100644 --- a/internal/commands/apply_test.go +++ b/internal/commands/apply_test.go @@ -118,7 +118,7 @@ func TestApplyFlags(t *testing.T) { return &remote.SyncResult{Type: remote.SyncObjectsIdentical, Details: "sync skipped"}, nil } } - err := s.executeCommand("apply", "dev", "-S", "-n", "--skip-create", "--gc=false") + err := s.executeCommand("apply", "dev", "-S", "-n", "--skip-create", "--gc=false", "--wait-all=false") require.NoError(t, err) stats := s.outputStats() a := assert.New(t) diff --git a/internal/commands/diff.go b/internal/commands/diff.go index d62bea1c..b970f7fc 100644 --- a/internal/commands/diff.go +++ b/internal/commands/diff.go @@ -417,7 +417,7 @@ func newDiffCommand(cp configProvider) *cobra.Command { cmd.Flags().StringArrayVar(&config.di.annotationNames, "ignore-annotation", nil, "remove specific annotation from objects before diff") cmd.Flags().BoolVar(&config.di.allLabels, "ignore-all-labels", false, "remove all labels from objects before diff") cmd.Flags().StringArrayVar(&config.di.labelNames, "ignore-label", nil, "remove specific label from objects before diff") - cmd.Flags().BoolVar(&config.exitNonZero, "error-exit", true, "exit with non-zero status code when diffs present") + cmd.Flags().BoolVar(&config.exitNonZero, "error-exit", false, "exit with non-zero status code when diffs present") cmd.RunE = func(c *cobra.Command, args []string) error { config.config = cp() diff --git a/internal/commands/diff_test.go b/internal/commands/diff_test.go index 030098b6..88de545e 100644 --- a/internal/commands/diff_test.go +++ b/internal/commands/diff_test.go @@ -97,7 +97,7 @@ func TestDiffBasicNoLabels(t *testing.T) { defer s.reset() d := &dg{cmValue: "baz", secretValue: "baz"} s.client.getFunc = d.get - err := s.executeCommand("diff", "dev", "--ignore-all-labels", "-S", "--show-deletes=false") + err := s.executeCommand("diff", "dev", "--ignore-all-labels", "-S", "--show-deletes=false", "--error-exit=true") require.NotNil(t, err) a := assert.New(t) secretValue := base64.StdEncoding.EncodeToString([]byte("baz")) @@ -113,7 +113,7 @@ func TestDiffBasicNoSpecificLabels(t *testing.T) { d := &dg{cmValue: "baz", secretValue: "baz"} s.client.getFunc = d.get err := s.executeCommand("diff", "dev", "--ignore-label", "qbec.io/environment", "--show-deletes=false") - require.NotNil(t, err) + require.NoError(t, err) a := assert.New(t) a.NotContains(s.stdout(), "qbec.io/environment:") a.Contains(s.stdout(), "qbec.io/application:") @@ -125,7 +125,7 @@ func TestDiffBasicNoSpecificAnnotation(t *testing.T) { d := &dg{cmValue: "baz", secretValue: "baz"} s.client.getFunc = d.get err := s.executeCommand("diff", "dev", "--ignore-annotation", "ann/foo", "--show-deletes=false") - require.NotNil(t, err) + require.NoError(t, err) a := assert.New(t) a.NotContains(s.stdout(), "ann/foo") a.Contains(s.stdout(), "ann/bar") @@ -137,7 +137,7 @@ func TestDiffBasicNoAnnotations(t *testing.T) { d := &dg{cmValue: "baz", secretValue: "baz"} s.client.getFunc = d.get err := s.executeCommand("diff", "dev", "--ignore-all-annotations", "--show-deletes=false") - require.NotNil(t, err) + require.NoError(t, err) a := assert.New(t) a.NotContains(s.stdout(), "ann/foo") a.NotContains(s.stdout(), "ann/bar") From cbae67f013d0a80b06dd1fa8c8309e2b9b236325 Mon Sep 17 00:00:00 2001 From: esp Date: Fri, 27 Nov 2020 06:48:37 -0800 Subject: [PATCH 23/38] Adds support for downloading env files (#172) Co-authored-by: Dan Nguyen --- internal/commands/setup.go | 12 ++++++--- internal/commands/setup_test.go | 48 +++++++++++++++++++++++++++++++++ internal/model/app.go | 41 +++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/internal/commands/setup.go b/internal/commands/setup.go index cf8aa8c1..f2647a16 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -233,11 +233,15 @@ func doSetup(root *cobra.Command, cf configFactory, overrideCP clientProvider) { // directory before we change it var envFiles []string if envFile != "" { - abs, err := filepath.Abs(envFile) - if err != nil { - return err + if model.IsRemoteFile(envFile) { + envFiles = append(envFiles, envFile) + } else { + abs, err := filepath.Abs(envFile) + if err != nil { + return err + } + envFiles = append(envFiles, abs) } - envFiles = append(envFiles, abs) } if err := setWorkDir(rootDir); err != nil { return err diff --git a/internal/commands/setup_test.go b/internal/commands/setup_test.go index e1bd35aa..699dd2db 100644 --- a/internal/commands/setup_test.go +++ b/internal/commands/setup_test.go @@ -1,6 +1,9 @@ package commands import ( + "fmt" + "net/http" + "net/http/httptest" "os" "testing" @@ -68,6 +71,51 @@ func TestSetupNoFail(t *testing.T) { assert.NotPanics(t, func() { Setup(&cobra.Command{}) }) } +var payload = ` +apiVersion: qbec.io/v1alpha1 +kind: EnvironmentMap +spec: + environments: + prod2: + server: https://prod-server + includes: + - service2 + properties: + envType: prod +` + +func TestListRemoteEnv(t *testing.T) { + s := newScaffold(t) + defer s.reset() + + // setup test server + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintln(w, payload) + })) + defer ts.Close() + + // the new env from the remote file should be displayed in env list command + err := s.executeCommand("env", "list", "--env-file", ts.URL, "-o", "json") + require.NoError(t, err) + + out := map[string][]map[string]string{} + err = s.jsonOutput(&out) + require.NoError(t, err) + + environments := out["environments"] + + var found = false + for i := 0; i < len(environments); i++ { + if environments[i]["name"] == "prod2" { + env := environments[i] + assert.Equal(t, "https://prod-server", env["server"], "prod server should be `https://prod-server`") + assert.Equal(t, "default", env["defaultNamespace"], "prod defaultNamespace should be `default`") + found = true + } + } + assert.True(t, found, "environments should include prod2") +} + func TestSetupEnvironments(t *testing.T) { tests := []struct { name string diff --git a/internal/model/app.go b/internal/model/app.go index 573a20f5..91ff3e82 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -20,11 +20,13 @@ package model import ( "fmt" "io/ioutil" + "net/http" "os" "path/filepath" "regexp" "sort" "strings" + "time" "github.com/ghodss/yaml" "github.com/pkg/errors" @@ -46,6 +48,9 @@ var supportedExtensions = map[string]bool{ ".json": true, } +// Basic http client +var httpClient = &http.Client{Timeout: time.Second * 10} + // Component is one or more logically related files that contains objects to be applied to a cluster. type Component struct { Name string // component name @@ -72,6 +77,40 @@ func makeValError(file string, errs []error) error { } +func downloadEnvFile(url string) ([]byte, error) { + var payload []byte + var resp *http.Response + var err error + + resp, err = httpClient.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + payload, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("failed to retrieve remote env file. Http Status: %d", resp.StatusCode) + } + return payload, err +} + +// IsRemoteFile distinguishes remote files from local files +func IsRemoteFile(file string) bool { + return strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") +} + +func readEnvFile(file string) ([]byte, error) { + if IsRemoteFile(file) { + return downloadEnvFile(file) + } + return ioutil.ReadFile(file) +} + func loadEnvFiles(app *QbecApp, additionalFiles []string, v *validator) error { if app.Spec.Environments == nil { app.Spec.Environments = map[string]Environment{} @@ -86,7 +125,7 @@ func loadEnvFiles(app *QbecApp, additionalFiles []string, v *validator) error { allFiles = append(allFiles, additionalFiles...) for _, file := range allFiles { - b, err := ioutil.ReadFile(file) + b, err := readEnvFile(file) if err != nil { return err } From c6d3d73b45d2c38789176a5d84452acb374484da Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Fri, 27 Nov 2020 11:32:02 -0800 Subject: [PATCH 24/38] default to jsonnet for formatting (#178) * default to jsonnet for formatting * Fix example --- internal/commands/examples.go | 8 ++++---- internal/commands/fmt.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/commands/examples.go b/internal/commands/examples.go index 94439bd1..9ab39d23 100644 --- a/internal/commands/examples.go +++ b/internal/commands/examples.go @@ -69,10 +69,10 @@ func showExamples() string { func fmtExamples() string { return exampleHelp( - newExample("alpha fmt -w", "format all files in-place"), - newExample("alpha fmt -e", "check if all files are formatted well. Non zero exit code in case a unformatted file is found"), - newExample("alpha fmt --type=jsonnet", "format all jsonnet and libsonnet files to stdout"), - newExample("alpha fmt somefolder file1.jsonnet file2.libsonnet", "format all json, jsonnet, libsonnet and yaml files in the somefolder, file1.jsonnet and file2.libsonnet files to stdout"), + newExample("alpha fmt -w", "format all jsonnet and libsonnet files in-place"), + newExample("alpha fmt -e", "check if all jsonnet and libsonnet files are formatted well. Non zero exit code in case a unformatted file is found"), + newExample("alpha fmt --type=json", "format all json files to stdout"), + newExample("alpha fmt somefolder file1.jsonnet file2.libsonnet", "format all jsonnet and libsonnet= files in the somefolder, file1.jsonnet and file2.libsonnet files to stdout"), newExample("alpha fmt -t=yaml", "format all yaml files to stdout"), newExample("alpha fmt --type=json,yaml somefolder file1.yaml file2.yml file3.json", "format all json and yaml files in the somefolder, file1.yaml, file2.yml and file3.json files to stdout"), ) diff --git a/internal/commands/fmt.go b/internal/commands/fmt.go index daafc5d5..04160350 100644 --- a/internal/commands/fmt.go +++ b/internal/commands/fmt.go @@ -82,7 +82,7 @@ func newFmtCommand(cp configProvider) *cobra.Command { config := fmtCommandConfig{} cmd.Flags().BoolVarP(&config.check, "check-errors", "e", false, "check for unformatted files") cmd.Flags().BoolVarP(&config.write, "write", "w", false, "write result to (source) file instead of stdout") - cmd.Flags().StringSliceVarP(&config.specifiedTypes, "type", "t", supportedTypes, "file types that should be formatted") + cmd.Flags().StringSliceVarP(&config.specifiedTypes, "type", "t", []string{"jsonnet"}, "file types that should be formatted") cmd.RunE = func(c *cobra.Command, args []string) error { config.config = cp() return wrapError(doFmt(args, &config)) From 87afd85bff2e0a3f395e2ae8e30ce4345a8bb4d7 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 27 Nov 2020 11:50:16 -0800 Subject: [PATCH 25/38] obfuscate stringData in addition to data for secret objects, fixes #162 (#177) * obfuscate stringData in addition to data for secret objects * add unit test --- internal/commands/show_test.go | 22 +++++++++++++++ .../string-secrets/components/secret.jsonnet | 10 +++++++ .../projects/string-secrets/qbec.yaml | 13 +++++++++ internal/types/secrets.go | 28 +++++++++++++------ 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 internal/commands/testdata/projects/string-secrets/components/secret.jsonnet create mode 100644 internal/commands/testdata/projects/string-secrets/qbec.yaml diff --git a/internal/commands/show_test.go b/internal/commands/show_test.go index cfa4e028..9f70609f 100644 --- a/internal/commands/show_test.go +++ b/internal/commands/show_test.go @@ -217,6 +217,28 @@ func TestShowOpenSecrets(t *testing.T) { s.assertOutputLineMatch(regexp.MustCompile(secretValue)) } +func TestShowHiddenStringSecrets(t *testing.T) { + s := newCustomScaffold(t, "testdata/projects/string-secrets") + defer s.reset() + secretValue := "foobar" + redactedValue := base64.RawStdEncoding.EncodeToString([]byte("redacted.")) + err := s.executeCommand("show", "local") + require.NoError(t, err) + s.assertOutputLineMatch(regexp.MustCompile(redactedValue)) + s.assertOutputLineNoMatch(regexp.MustCompile(secretValue)) +} + +func TestShowOpenStringSecrets(t *testing.T) { + s := newCustomScaffold(t, "testdata/projects/string-secrets") + defer s.reset() + secretValue := "foobar" + redactedValue := base64.RawStdEncoding.EncodeToString([]byte("redacted.")) + err := s.executeCommand("show", "local", "-S") + require.NoError(t, err) + s.assertOutputLineNoMatch(regexp.MustCompile(redactedValue)) + s.assertOutputLineMatch(regexp.MustCompile(secretValue)) +} + func TestShowNegative(t *testing.T) { tests := []struct { name string diff --git a/internal/commands/testdata/projects/string-secrets/components/secret.jsonnet b/internal/commands/testdata/projects/string-secrets/components/secret.jsonnet new file mode 100644 index 00000000..39af154d --- /dev/null +++ b/internal/commands/testdata/projects/string-secrets/components/secret.jsonnet @@ -0,0 +1,10 @@ +{ + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name: 'my-secret', + }, + stringData: { + foo: std.extVar('secretValue'), + }, +} diff --git a/internal/commands/testdata/projects/string-secrets/qbec.yaml b/internal/commands/testdata/projects/string-secrets/qbec.yaml new file mode 100644 index 00000000..178adabf --- /dev/null +++ b/internal/commands/testdata/projects/string-secrets/qbec.yaml @@ -0,0 +1,13 @@ +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: string-secrets +spec: + environments: + local: + context: kind-kind + defaultNamespace: default + vars: + external: + - name: secretValue + default: foobar diff --git a/internal/types/secrets.go b/internal/types/secrets.go index 9a88e936..f805b1e8 100644 --- a/internal/types/secrets.go +++ b/internal/types/secrets.go @@ -48,6 +48,18 @@ func obfuscate(value string) string { return fmt.Sprintf("redacted.%s", base64.RawURLEncoding.EncodeToString(shasum)) } +func obfuscateMap(in map[string]interface{}) map[string]interface{} { + if len(in) == 0 { + return nil + } + ret := map[string]interface{}{} + for k, v := range in { + value := obfuscate(fmt.Sprintf("%s:%s", k, v)) + ret[k] = base64.StdEncoding.EncodeToString([]byte(value)) + } + return ret +} + // HasSensitiveInfo returns true if the supplied object has sensitive data that might need // to be hidden. func HasSensitiveInfo(obj *unstructured.Unstructured) bool { @@ -66,17 +78,15 @@ func HideSensitiveInfo(obj *unstructured.Unstructured) (*unstructured.Unstructur if !HasSensitiveInfo(obj) { return obj, false } + clone := obj.DeepCopy() - secretData, _, _ := unstructured.NestedMap(obj.Object, "data") - if secretData == nil { - secretData = map[string]interface{}{} - } - changedData := map[string]interface{}{} - for k, v := range secretData { - value := obfuscate(fmt.Sprintf("%s:%s", k, v)) - changedData[k] = base64.StdEncoding.EncodeToString([]byte(value)) + for _, section := range []string{"data", "stringData"} { + secretData, _, _ := unstructured.NestedMap(obj.Object, section) + changed := obfuscateMap(secretData) + if changed != nil { + clone.Object[section] = changed + } } - clone.Object["data"] = changedData return clone, true } From 85f4637e2113562b48e9ff69370a168c6dcb808d Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 27 Nov 2020 11:54:59 -0800 Subject: [PATCH 26/38] prepare 0.13 release (#176) --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ Makefile | 2 +- site/content/reference/qbec-yaml.md | 4 ++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c6e8f0..95e292c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,34 @@ Changelog --- +## v0.13.0 (Nov 27, 2020) + +* Misc. CI build changes +* Update jsonnet library to `v0.17.0` and k8s client libs to `v1.17.13` +* Add json formatter to the `qbec alpha fmt` command +* Fix diff commands to show skipped updates and deletes based on qbec directives specified for existing objects. + This will no longer show spurious diffs for deletes and updates if those have been turned off. +* Use per-namespace queries by default when multiple namespaces are present, allow using cluster-scoped queries + using an opt-in flag. +* The `--env-file` option now allows http(s) URLs in addition to local files. In addition, the `envFiles` attribute + in `qbec.yaml` can also contain http(s) URLs. +* String data in secrets is now obfuscated in addition to binary data + +### Incompatibilities + +This release is incompatible from previous minor versions in the following ways: + +* `qbec apply` will now wait on all objects by default. That is, the `--wait-all` now defaults to `true`. + To get the previous behavior, you need to add `--wait-all=false` to the `apply` command. +* `qbec diff` now exits 0 by default even when diffs are found. To restore previous behavior, add `--error-exit` + to the command. +* qbec now defaults to per-namespace queries when multiple namespaces are present. To get the previous behavior + of using cluster-scoped queries add `clusterScopedLists: true` under `spec` in `qbec.yaml` +* The command line syntax of the `qbec alpha fmt` command has changed in incompatible ways. Instead of options like + `--jsonnet`, `--yaml` etc. you need to specify options as `--type=jsonnet`, `--type=yaml` and so on. +* YAML formatter now follows `prettier` conventions requiring arrays to be indented under the parent key. +* Any corner-case behavior from updating k8s client and jsonnet libraries. + ## v0.12.5 (Oct 2, 2020) * Add a new `wait-policy` directive to disable waits on specific deployments diff --git a/Makefile b/Makefile index c63d79ee..d80558c2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.12.5 +VERSION := 0.13.0 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') diff --git a/site/content/reference/qbec-yaml.md b/site/content/reference/qbec-yaml.md index 5fc25179..8c7516fc 100644 --- a/site/content/reference/qbec-yaml.md +++ b/site/content/reference/qbec-yaml.md @@ -27,6 +27,10 @@ spec: - default - excluded - components + + # when dealing with apps that deploy to multiple namespaces, use object lists at cluster scope for GC purposes. + # by default, this will use namespaced queries for each namespace. + clusterScopedLists: true # declaration of late-bound variable definitions that can be passed in on the command line using the --vm:* options. vars: From 94358cf0009b9d6915f87af498ef60e99dedc5cb Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 27 Nov 2020 14:10:25 -0800 Subject: [PATCH 27/38] add missing tests for http downloads, overlooked in a previous PR (#179) --- internal/model/app.go | 27 ++++---- internal/model/app_test.go | 69 +++++++++++++++++++ internal/model/testdata/http-app/.gitignore | 1 + .../http-app/components/service.jsonnet | 7 ++ internal/model/testdata/http-app/envs.yaml | 10 +++ .../testdata/http-app/qbec-template.yaml | 11 +++ site/content/reference/qbec-yaml.md | 3 +- 7 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 internal/model/testdata/http-app/.gitignore create mode 100644 internal/model/testdata/http-app/components/service.jsonnet create mode 100644 internal/model/testdata/http-app/envs.yaml create mode 100644 internal/model/testdata/http-app/qbec-template.yaml diff --git a/internal/model/app.go b/internal/model/app.go index 91ff3e82..b6558c51 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -78,23 +78,18 @@ func makeValError(file string, errs []error) error { } func downloadEnvFile(url string) ([]byte, error) { - var payload []byte - var resp *http.Response - var err error - - resp, err = httpClient.Get(url) + res, err := httpClient.Get(url) if err != nil { return nil, err } - defer resp.Body.Close() + defer res.Body.Close() - if resp.StatusCode == http.StatusOK { - payload, err = ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - } else { - return nil, fmt.Errorf("failed to retrieve remote env file. Http Status: %d", resp.StatusCode) + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("status : %s", res.Status) + } + payload, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err } return payload, err } @@ -106,7 +101,11 @@ func IsRemoteFile(file string) bool { func readEnvFile(file string) ([]byte, error) { if IsRemoteFile(file) { - return downloadEnvFile(file) + b, err := downloadEnvFile(file) + if err != nil { + return nil, errors.Wrapf(err, "download environments from %s", file) + } + return b, nil } return ioutil.ReadFile(file) } diff --git a/internal/model/app_test.go b/internal/model/app_test.go index 4ae0d066..2731e33c 100644 --- a/internal/model/app_test.go +++ b/internal/model/app_test.go @@ -19,10 +19,15 @@ package model import ( "bytes" "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" "testing" + "text/template" + "time" "github.com/ghodss/yaml" "github.com/splunk/qbec/internal/sio" @@ -317,6 +322,38 @@ func TestAppComponentLoadNegative(t *testing.T) { a.Equal(`cannot include as well as exclude components, specify one or the other`, err.Error()) } +func TestHttpEnvFiles(t *testing.T) { + reset := setPwd(t, "testdata/http-app") + defer reset() + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + b, err := ioutil.ReadFile("envs.yaml") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + _, _ = w.Write(b) + })) + defer s.Close() + + b, err := ioutil.ReadFile("qbec-template.yaml") + tmpl, err := template.New("qbec").Parse(string(b)) + require.NoError(t, err) + var buf bytes.Buffer + err = tmpl.Execute(&buf, map[string]interface{}{"URL": s.URL}) + require.NoError(t, err) + err = ioutil.WriteFile("qbec.yaml", buf.Bytes(), 0640) + require.NoError(t, err) + app, err := NewApp("qbec.yaml", nil, "") + require.NoError(t, err) + envs := app.Environments() + a := assert.New(t) + a.Equal(3, len(envs)) + a.Contains(envs, "stage") + a.Contains(envs, "prod") + require.Contains(t, envs, "dev") + a.EqualValues("https://new-dev-server", envs["dev"].Server) +} + func TestAppNegative(t *testing.T) { reset := setPwd(t, "./testdata/bad-app") defer reset() @@ -465,3 +502,35 @@ func TestAppNegative(t *testing.T) { }) } } + +func TestNegativeDownload(t *testing.T) { + t.Run("no-endpoint", func(t *testing.T) { + _, err := readEnvFile("http://nonexistent.server") + require.Error(t, err) + assert.Contains(t, err.Error(), "download environments from http://nonexistent.server") + }) + t.Run("bad-status", func(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + })) + defer s.Close() + _, err := readEnvFile(s.URL) + require.Error(t, err) + assert.Contains(t, err.Error(), "download environments from "+s.URL) + assert.Contains(t, err.Error(), "status : 400 Bad Request") + }) + t.Run("slow-server", func(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + time.Sleep(time.Second) + w.WriteHeader(http.StatusBadRequest) + })) + defer s.Close() + o := httpClient + defer func() { httpClient = o }() + httpClient = &http.Client{Timeout: 100 * time.Millisecond} + _, err := readEnvFile(s.URL) + require.Error(t, err) + assert.Contains(t, err.Error(), "download environments from "+s.URL) + assert.Contains(t, err.Error(), "context deadline exceeded") + }) +} diff --git a/internal/model/testdata/http-app/.gitignore b/internal/model/testdata/http-app/.gitignore new file mode 100644 index 00000000..8f481cd5 --- /dev/null +++ b/internal/model/testdata/http-app/.gitignore @@ -0,0 +1 @@ +qbec.yaml diff --git a/internal/model/testdata/http-app/components/service.jsonnet b/internal/model/testdata/http-app/components/service.jsonnet new file mode 100644 index 00000000..62b8efb6 --- /dev/null +++ b/internal/model/testdata/http-app/components/service.jsonnet @@ -0,0 +1,7 @@ +{ + apiVersion: 'v1', + kind: 'ConfigMap', + data: { + foo: bar, + }, +} diff --git a/internal/model/testdata/http-app/envs.yaml b/internal/model/testdata/http-app/envs.yaml new file mode 100644 index 00000000..713003bb --- /dev/null +++ b/internal/model/testdata/http-app/envs.yaml @@ -0,0 +1,10 @@ +apiVersion: qbec.io/v1alpha1 +kind: EnvironmentMap +spec: + environments: + stage: + server: https://stage-server + dev: + server: https://new-dev-server + prod: + server: https://prod-server diff --git a/internal/model/testdata/http-app/qbec-template.yaml b/internal/model/testdata/http-app/qbec-template.yaml new file mode 100644 index 00000000..73f35e84 --- /dev/null +++ b/internal/model/testdata/http-app/qbec-template.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: subdir-app +spec: + environments: + dev: + server: https://dev-server + envFiles: + - '{{.URL}}' diff --git a/site/content/reference/qbec-yaml.md b/site/content/reference/qbec-yaml.md index 8c7516fc..c6f325c2 100644 --- a/site/content/reference/qbec-yaml.md +++ b/site/content/reference/qbec-yaml.md @@ -77,9 +77,10 @@ spec: # additional environments can be loaded from files. Files are loaded in the order specified. # It is explicitly allowed for a later file to replace an inline environment or one loaded from an earlier file. - # The file path is relative to the directory where qbec.yaml resides. + # The file path is relative to the directory where qbec.yaml resides. http(s) URLs are also supported envFiles: - more-envs.yaml + - https://my.server/envs.yaml ``` ### Environment files From e2cbd9665f7e6a601c32deb665735e0868691a76 Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Sat, 28 Nov 2020 07:52:54 -0800 Subject: [PATCH 28/38] document wait policy directive --- site/content/reference/directives.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/site/content/reference/directives.md b/site/content/reference/directives.md index 852687af..aeafa673 100644 --- a/site/content/reference/directives.md +++ b/site/content/reference/directives.md @@ -33,4 +33,12 @@ when set to `"never"`, indicates that the specific object should never be update If you want qbec to update this object, you need to remove the annotation from the in-cluster object. Changing the source object to remove this annotation will not work. +#### `directives.qbec.io/wait-policy` + +* Annotation source: local object +* Allowed values: `"default"`, `"never"` +* Default value: `"default"` + +when set to `"never"` for deployments or daemonsets, indicates that qbec should not wait for that object even when +the `--wait` or `--wait-all` flags are set for the `apply` command. From c18c0a98c63f77d15ab952e30fe92e08a44675b7 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Wed, 2 Dec 2020 13:34:48 -0800 Subject: [PATCH 29/38] ensure all fmt arguments are processed when no errors (#181) --- internal/commands/fmt.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/commands/fmt.go b/internal/commands/fmt.go index 04160350..6e4e1503 100644 --- a/internal/commands/fmt.go +++ b/internal/commands/fmt.go @@ -56,7 +56,9 @@ func doFmt(args []string, config *fmtCommandConfig) error { case err != nil: return err case dir.IsDir(): - return walkDir(config, path) + if err := walkDir(config, path); err != nil { + return err + } default: if shouldFormat(config, dir) { if err := processFile(config, path, nil, config.Stdout()); err != nil { From 4eb019139514b6306d77a40e2008ed8eab54ee97 Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 2 Dec 2020 13:38:37 -0800 Subject: [PATCH 30/38] update changelog, up version --- CHANGELOG.md | 4 ++++ Makefile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e292c6..458c8c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog --- +## v0.13.1 (Dec 2, 2020) + +* Fix a bug where the `alpha fmt` command would stop processing arguments after encountering a directory. + ## v0.13.0 (Nov 27, 2020) * Misc. CI build changes diff --git a/Makefile b/Makefile index d80558c2..46142c79 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.13.0 +VERSION := 0.13.1 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From b2adc0b740ba602187d827f5afe4416f993a3003 Mon Sep 17 00:00:00 2001 From: splkforrest <70663392+splkforrest@users.noreply.github.com> Date: Thu, 3 Dec 2020 10:57:50 -0800 Subject: [PATCH 31/38] =?UTF-8?q?add=20env=20vars=20to=20supplement=20--fo?= =?UTF-8?q?rce:k8s-context=20and=20--force:k8s-namesp=E2=80=A6=20(#183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add env vars to supplement --force:k8s-context and --force:k8s-namespace flags --- go.mod | 1 + go.sum | 6 ++++++ internal/commands/config.go | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 23560dc6..5c94139d 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/spf13/cobra v1.1.1 github.com/stretchr/testify v1.6.1 github.com/tidwall/pretty v1.0.0 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 k8s.io/api v0.17.13 diff --git a/go.sum b/go.sum index 27e0b940..a78a4c5a 100644 --- a/go.sum +++ b/go.sum @@ -532,11 +532,15 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -647,6 +651,8 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/commands/config.go b/internal/commands/config.go index 38dbda19..dc2312b4 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -52,12 +52,12 @@ type forceOptions struct { // addForceOptions adds flags to the supplied root command and returns forced options. func addForceOptions(cmd *cobra.Command, prefix string) func() forceOptions { var f forceOptions - ctxUsage := fmt.Sprintf("force K8s context with supplied value. Special values are %s and %s for in-cluster and current contexts respectively", + ctxUsage := fmt.Sprintf("force K8s context with supplied value. Special values are %s and %s for in-cluster and current contexts respectively. Defaulted from QBEC_FORCE_K8S_CONTEXT", remote.ForceInClusterContext, currentMarker) pf := cmd.PersistentFlags() - pf.StringVar(&f.k8sContext, prefix+"k8s-context", "", ctxUsage) - nsUsage := fmt.Sprintf("override default namespace for environment with supplied value. The special value %s can be used to extract the value in the kube config", currentMarker) - pf.StringVar(&f.k8sNamespace, prefix+"k8s-namespace", "", nsUsage) + pf.StringVar(&f.k8sContext, prefix+"k8s-context", envOrDefault("QBEC_FORCE_K8S_CONTEXT", ""), ctxUsage) + nsUsage := fmt.Sprintf("override default namespace for environment with supplied value. The special value %s can be used to extract the value in the kube config. Defaulted from QBEC_FORCE_K8S_NAMESPACE", currentMarker) + pf.StringVar(&f.k8sNamespace, prefix+"k8s-namespace", envOrDefault("QBEC_FORCE_K8S_NAMESPACE", ""), nsUsage) return func() forceOptions { return f } } From 3ef9f8e13d4fc8cd5ec9f102f67c06a3c5540f77 Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Thu, 3 Dec 2020 12:00:27 -0800 Subject: [PATCH 32/38] update changelog, up version --- CHANGELOG.md | 6 +++++- Makefile | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 458c8c00..910d4c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog --- +## v0.13.2 (Dec 3, 2020) + +* Allow force options to be set via environment variables (thanks @splkforrest) + ## v0.13.1 (Dec 2, 2020) * Fix a bug where the `alpha fmt` command would stop processing arguments after encountering a directory. @@ -15,7 +19,7 @@ Changelog * Use per-namespace queries by default when multiple namespaces are present, allow using cluster-scoped queries using an opt-in flag. * The `--env-file` option now allows http(s) URLs in addition to local files. In addition, the `envFiles` attribute - in `qbec.yaml` can also contain http(s) URLs. + in `qbec.yaml` can also contain http(s) URLs. (thanks @dan1) * String data in secrets is now obfuscated in addition to binary data ### Incompatibilities diff --git a/Makefile b/Makefile index 46142c79..5f378862 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.13.1 +VERSION := 0.13.2 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From c125a4e0d3471780ffd538286d252b21332eb90f Mon Sep 17 00:00:00 2001 From: Artur Korzeniewski Date: Wed, 23 Dec 2020 21:16:25 +0100 Subject: [PATCH 33/38] Add qbec.io/component as label (#186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add a `qbec.io/component` label (in addition to the annotation) when `qbec.yaml` has the `addComponentLabel` property set to true. Co-authored-by: Michał Hudy --- internal/commands/common_test.go | 2 +- internal/commands/config.go | 21 ++++++++-------- internal/commands/config_test.go | 3 ++- internal/commands/diff.go | 1 + internal/eval/eval.go | 23 +++++++++-------- internal/eval/eval_test.go | 25 +++++++++++++++++++ internal/model/app.go | 4 +++ internal/model/app_test.go | 10 ++++++++ internal/model/external-names.go | 2 ++ internal/model/k8s.go | 5 +++- internal/model/k8s_test.go | 22 ++++++++++++++-- internal/model/swagger-schema.go | 6 ++++- internal/model/swagger.yaml | 3 +++ .../testdata/label-app/components/cm.jsonnet | 10 ++++++++ .../label-app/components/index.jsonnet | 1 + internal/model/testdata/label-app/qbec.yaml | 10 ++++++++ internal/model/types.go | 2 ++ internal/objsort/sort_test.go | 2 +- internal/remote/pristine.go | 2 +- internal/remote/pristine_test.go | 2 +- internal/types/secrets.go | 2 +- internal/types/secrets_test.go | 4 +-- site/content/reference/qbec-yaml.md | 3 +++ 23 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 internal/model/testdata/label-app/components/cm.jsonnet create mode 100644 internal/model/testdata/label-app/components/index.jsonnet create mode 100644 internal/model/testdata/label-app/qbec.yaml diff --git a/internal/commands/common_test.go b/internal/commands/common_test.go index 002eda01..590d7ffb 100644 --- a/internal/commands/common_test.go +++ b/internal/commands/common_test.go @@ -52,7 +52,7 @@ func (i input) makeObject() model.K8sLocalObject { "foo": "bar", }, } - return model.NewK8sLocalObject(data, "app1", "t1", i.component, i.env) + return model.NewK8sLocalObject(data, "app1", "t1", i.component, i.env, false) } func (i input) String() string { diff --git a/internal/commands/config.go b/internal/commands/config.go index dc2312b4..be31baff 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -281,16 +281,17 @@ func (c config) EvalContext(env string, props map[string]interface{}) eval.Conte sio.Warnln("unable to serialize env properties to JSON:", err) } return eval.Context{ - App: c.App().Name(), - Tag: c.App().Tag(), - Env: env, - EnvPropsJSON: string(p), - DefaultNs: c.App().DefaultNamespace(env), - VMConfig: c.vmConfig, - Verbose: c.Verbosity() > 1, - Concurrency: c.EvalConcurrency(), - PostProcessFile: c.App().PostProcessor(), - CleanMode: c.cleanEvalMode, + App: c.App().Name(), + Tag: c.App().Tag(), + Env: env, + EnvPropsJSON: string(p), + DefaultNs: c.App().DefaultNamespace(env), + VMConfig: c.vmConfig, + Verbose: c.Verbosity() > 1, + AddComponentLabel: c.App().AddComponentLabel(), + Concurrency: c.EvalConcurrency(), + PostProcessFile: c.App().PostProcessor(), + CleanMode: c.cleanEvalMode, } } diff --git a/internal/commands/config_test.go b/internal/commands/config_test.go index e20582e2..829b3f64 100644 --- a/internal/commands/config_test.go +++ b/internal/commands/config_test.go @@ -67,6 +67,7 @@ func TestConfigCreate(t *testing.T) { a.Equal("t1", ctx.Tag) a.Equal("kube-system-t1", ctx.DefaultNs) a.Equal(cfg.EvalConcurrency(), ctx.Concurrency) + a.Equal(false, ctx.AddComponentLabel) testVMC := ctx.VMConfig([]string{"tlaFoo", "tlaBar"}) a.EqualValues(map[string]string{"tlaFoo": "xxx"}, testVMC.TopLevelVars()) @@ -206,7 +207,7 @@ data: for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ret := ordering(model.NewK8sLocalObject(test.data, "app", "tag", "component", "env")) + ret := ordering(model.NewK8sLocalObject(test.data, "app", "tag", "component", "env", false)) assert.Equal(t, test.expected, ret) }) } diff --git a/internal/commands/diff.go b/internal/commands/diff.go index b970f7fc..98cdc74c 100644 --- a/internal/commands/diff.go +++ b/internal/commands/diff.go @@ -409,6 +409,7 @@ func newDiffCommand(cp configProvider) *cobra.Command { config := diffCommandConfig{ filterFunc: addFilterParams(cmd, true), } + cmd.Flags().BoolVar(&config.showDeletions, "show-deletes", true, "include deletions in diff") cmd.Flags().IntVar(&config.contextLines, "context", 3, "context lines for diff") cmd.Flags().IntVar(&config.parallel, "parallel", 5, "number of parallel routines to run") diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 064cd74c..b3eb7653 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -83,16 +83,17 @@ func (p postProc) run(obj map[string]interface{}) (map[string]interface{}, error // Context is the evaluation context type Context struct { - App string // the application for which the evaluation is done - Tag string // the gc tag if present - Env string // the environment for which the evaluation is done - EnvPropsJSON string // the environment properties to expose as an external variable - DefaultNs string // the default namespace to expose as an external variable - VMConfig VMConfigFunc // the base VM config to use for eval - Verbose bool // show generated code - Concurrency int // concurrent components to evaluate, default 5 - PostProcessFile string // the file that contains post-processing code for all objects - CleanMode bool // whether clean mode is enabled + App string // the application for which the evaluation is done + Tag string // the gc tag if present + Env string // the environment for which the evaluation is done + EnvPropsJSON string // the environment properties to expose as an external variable + DefaultNs string // the default namespace to expose as an external variable + VMConfig VMConfigFunc // the base VM config to use for eval + Verbose bool // show generated code + AddComponentLabel bool // add component name as label to Kubernetes objects + Concurrency int // concurrent components to evaluate, default 5 + PostProcessFile string // the file that contains post-processing code for all objects + CleanMode bool // whether clean mode is enabled } func (c Context) baseVMConfig(tlas []string) vm.Config { @@ -256,7 +257,7 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca if err := model.AssertMetadataValid(proc); err != nil { return nil, err } - processed = append(processed, model.NewK8sLocalObject(proc, ctx.App, ctx.Tag, c.Name, ctx.Env)) + processed = append(processed, model.NewK8sLocalObject(proc, ctx.App, ctx.Tag, c.Name, ctx.Env, ctx.AddComponentLabel)) } return processed, nil } diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 0df577f9..75414b90 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -134,6 +134,31 @@ func TestEvalComponentsClean(t *testing.T) { a.Equal("json-config-map", obj.GetName()) a.Equal("", obj.ToUnstructured().GetAnnotations()["team"]) a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"]) + a.Equal("", obj.ToUnstructured().GetLabels()["qbec.io/component"]) +} + +func TestEvalComponentsComponentLabel(t *testing.T) { + objs, err := Components([]model.Component{ + { + Name: "a", + Files: []string{"testdata/components/a.json"}, + }, + }, Context{Env: "dev", CleanMode: true, AddComponentLabel: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) + require.Nil(t, err) + require.Equal(t, 1, len(objs)) + a := assert.New(t) + + obj := objs[0] + a.Equal("a", obj.Component()) + a.Equal("dev", obj.Environment()) + a.Equal("", obj.GroupVersionKind().Group) + a.Equal("v1", obj.GroupVersionKind().Version) + a.Equal("ConfigMap", obj.GroupVersionKind().Kind) + a.Equal("", obj.GetNamespace()) + a.Equal("json-config-map", obj.GetName()) + a.Equal("", obj.ToUnstructured().GetAnnotations()["team"]) + a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"]) + a.Equal("a", obj.ToUnstructured().GetLabels()["qbec.io/component"]) } func TestEvalComponentsEdges(t *testing.T) { diff --git a/internal/model/app.go b/internal/model/app.go index b6558c51..6a923996 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -268,6 +268,10 @@ func (a *App) LibPaths() []string { return a.inner.Spec.LibPaths } +func (a *App) AddComponentLabel() bool { + return a.inner.Spec.AddComponentLabel +} + func (a *App) envObject(env string) (Environment, error) { envObj, ok := a.inner.Spec.Environments[env] if !ok { diff --git a/internal/model/app_test.go b/internal/model/app_test.go index 2731e33c..8c07aaf8 100644 --- a/internal/model/app_test.go +++ b/internal/model/app_test.go @@ -105,6 +105,7 @@ func TestAppSimple(t *testing.T) { a.Equal(3, len(app.defaultComponents)) a.Contains(app.allComponents, "service2") a.NotContains(app.defaultComponents, "service2") + a.Equal(false, app.AddComponentLabel()) comps, err := app.ComponentsForEnvironment("_", nil, nil) require.Nil(t, err) @@ -534,3 +535,12 @@ func TestNegativeDownload(t *testing.T) { assert.Contains(t, err.Error(), "context deadline exceeded") }) } + +func TestAppLabel(t *testing.T) { + reset := setPwd(t, "testdata/label-app") + defer reset() + app, err := NewApp("qbec.yaml", nil, "") + require.Nil(t, err) + a := assert.New(t) + a.Equal(true, app.AddComponentLabel()) +} diff --git a/internal/model/external-names.go b/internal/model/external-names.go index 15f584a9..9dc2b2c3 100644 --- a/internal/model/external-names.go +++ b/internal/model/external-names.go @@ -35,6 +35,7 @@ var QbecNames = struct { ApplicationLabel string // the label to use for tagging an object with an application name TagLabel string // the label to use for tagging an object with a scoped GC tag ComponentAnnotation string // the label to use for tagging an object with a component + ComponentLabel string // the label to use for tagging an object with a component EnvironmentLabel string // the label to use for tagging an object with an annotation PristineAnnotation string // the annotation to use for storing the pristine object EnvVarName string // the name of the external variable that has the environment name @@ -47,6 +48,7 @@ var QbecNames = struct { ApplicationLabel: QBECMetadataPrefix + "application", TagLabel: QBECMetadataPrefix + "tag", ComponentAnnotation: QBECMetadataPrefix + "component", + ComponentLabel: QBECMetadataPrefix + "component", EnvironmentLabel: QBECMetadataPrefix + "environment", PristineAnnotation: QBECMetadataPrefix + "last-applied", EnvVarName: QBECMetadataPrefix + "env", diff --git a/internal/model/k8s.go b/internal/model/k8s.go index d64a877a..69808ec6 100644 --- a/internal/model/k8s.go +++ b/internal/model/k8s.go @@ -135,7 +135,7 @@ func NewK8sObject(data map[string]interface{}) K8sObject { // NewK8sLocalObject wraps a K8sLocalObject implementation around the unstructured object data specified as a bag // of attributes for the supplied application, component and environment. -func NewK8sLocalObject(data map[string]interface{}, app, tag, component, env string) K8sLocalObject { +func NewK8sLocalObject(data map[string]interface{}, app, tag, component, env string, setComponentAsLabel bool) K8sLocalObject { base := toUnstructured(data) ret := &ko{Unstructured: base, app: app, tag: tag, comp: component, env: env} labels := base.GetLabels() @@ -147,6 +147,9 @@ func NewK8sLocalObject(data map[string]interface{}, app, tag, component, env str labels[QbecNames.TagLabel] = tag } labels[QbecNames.EnvironmentLabel] = env + if setComponentAsLabel { + labels[QbecNames.ComponentLabel] = component + } base.SetLabels(labels) anns := base.GetAnnotations() diff --git a/internal/model/k8s_test.go b/internal/model/k8s_test.go index 8653d007..dae742a5 100644 --- a/internal/model/k8s_test.go +++ b/internal/model/k8s_test.go @@ -64,7 +64,7 @@ func TestK8sObject(t *testing.T) { } func TestK8sLocalObject(t *testing.T) { - obj := NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1") + obj := NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1", false) a := assert.New(t) a.Equal("app1", obj.Application()) a.Equal("c1", obj.Component()) @@ -75,10 +75,27 @@ func TestK8sLocalObject(t *testing.T) { a.Equal("e1", labels[QbecNames.EnvironmentLabel]) _, ok := labels[QbecNames.TagLabel] a.False(ok) + _, ok = labels[QbecNames.ComponentLabel] + a.False(ok) } func TestK8sLocalObjectWithTag(t *testing.T) { - obj := NewK8sLocalObject(toData(cm), "app1", "t1", "c1", "e1") + obj := NewK8sLocalObject(toData(cm), "app1", "t1", "c1", "e1", false) + a := assert.New(t) + a.Equal("app1", obj.Application()) + a.Equal("c1", obj.Component()) + a.Equal("e1", obj.Environment()) + a.Equal("t1", obj.Tag()) + labels := obj.ToUnstructured().GetLabels() + a.Equal("app1", labels[QbecNames.ApplicationLabel]) + a.Equal("e1", labels[QbecNames.EnvironmentLabel]) + a.Equal("t1", labels[QbecNames.TagLabel]) + _, ok := labels[QbecNames.ComponentLabel] + a.False(ok) +} + +func TestK8sLocalObjectWithComponentLabel(t *testing.T) { + obj := NewK8sLocalObject(toData(cm), "app1", "t1", "c1", "e1", true) a := assert.New(t) a.Equal("app1", obj.Application()) a.Equal("c1", obj.Component()) @@ -88,6 +105,7 @@ func TestK8sLocalObjectWithTag(t *testing.T) { a.Equal("app1", labels[QbecNames.ApplicationLabel]) a.Equal("e1", labels[QbecNames.EnvironmentLabel]) a.Equal("t1", labels[QbecNames.TagLabel]) + a.Equal("c1", labels[QbecNames.ComponentLabel]) } func TestAssertMetadata(t *testing.T) { diff --git a/internal/model/swagger-schema.go b/internal/model/swagger-schema.go index f04c8bdf..6d6979c4 100644 --- a/internal/model/swagger-schema.go +++ b/internal/model/swagger-schema.go @@ -1,6 +1,6 @@ package model -// generated by gen-qbec-swagger from swagger.yaml at 2020-11-09 11:44:20.253614 +0000 UTC +// generated by gen-qbec-swagger from swagger.yaml at 2020-12-23 09:53:16.846093 +0000 UTC // Do NOT edit this file by hand var swaggerJSON = ` @@ -52,6 +52,10 @@ var swaggerJSON = ` "qbec.io.v1alpha1.AppSpec": { "additionalProperties": false, "properties": { + "addComponentLabel": { + "description": "add component name as label to Kubernetes objects", + "type": "boolean" + }, "baseProperties": { "description": "properties for the baseline environment", "type": "object" diff --git a/internal/model/swagger.yaml b/internal/model/swagger.yaml index 90cafd6c..1762822b 100644 --- a/internal/model/swagger.yaml +++ b/internal/model/swagger.yaml @@ -118,6 +118,9 @@ definitions: namespaceTagSuffix: description: suffix default namespace when app-tag provided, with the supplied tag type: boolean + addComponentLabel: + description: add component name as label to Kubernetes objects + type: boolean title: AppSpec is the user-supplied configuration of the qbec app. type: object qbec.io.v1alpha1.Environment: diff --git a/internal/model/testdata/label-app/components/cm.jsonnet b/internal/model/testdata/label-app/components/cm.jsonnet new file mode 100644 index 00000000..255cec9b --- /dev/null +++ b/internal/model/testdata/label-app/components/cm.jsonnet @@ -0,0 +1,10 @@ +{ + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + name: "cm0" + }, + data: { + foo: "bar", + } +} diff --git a/internal/model/testdata/label-app/components/index.jsonnet b/internal/model/testdata/label-app/components/index.jsonnet new file mode 100644 index 00000000..ff796258 --- /dev/null +++ b/internal/model/testdata/label-app/components/index.jsonnet @@ -0,0 +1 @@ +import './cm.jsonnet' diff --git a/internal/model/testdata/label-app/qbec.yaml b/internal/model/testdata/label-app/qbec.yaml new file mode 100644 index 00000000..d77f37d9 --- /dev/null +++ b/internal/model/testdata/label-app/qbec.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: label-app +spec: + addComponentLabel: true + environments: + dev: + server: https://dev-server diff --git a/internal/model/types.go b/internal/model/types.go index 4dae7b61..5a351b07 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -104,6 +104,8 @@ type AppSpec struct { // whether remote lists for GC purposes should use cluster scoped queries // when multiple namespaces are present. Not used when only one namespace is present. ClusterScopedLists bool `json:"clusterScopedLists,omitempty"` + // add component name as label to Kubernetes objects, default to false + AddComponentLabel bool `json:"addComponentLabel,omitempty"` } // QbecEnvironmentMapSpec is the spec for a QbecEnvironmentMap object. diff --git a/internal/objsort/sort_test.go b/internal/objsort/sort_test.go index a145387b..a177f32a 100644 --- a/internal/objsort/sort_test.go +++ b/internal/objsort/sort_test.go @@ -42,7 +42,7 @@ func object(d data) model.K8sLocalObject { "namespace": d.namespace, "name": d.name, }, - }, "app1", "", d.component, "dev") + }, "app1", "", d.component, "dev", false) } func TestBasicSort(t *testing.T) { diff --git a/internal/remote/pristine.go b/internal/remote/pristine.go index 0527769e..5979fd27 100644 --- a/internal/remote/pristine.go +++ b/internal/remote/pristine.go @@ -115,7 +115,7 @@ func (k qbecPristine) createFromPristine(pristine model.K8sLocalObject) (model.K } annotations[model.QbecNames.PristineAnnotation] = zipped annotated.SetAnnotations(annotations) - return model.NewK8sLocalObject(annotated.Object, pristine.Application(), pristine.Tag(), pristine.Component(), pristine.Environment()), nil + return model.NewK8sLocalObject(annotated.Object, pristine.Application(), pristine.Tag(), pristine.Component(), pristine.Environment(), false), nil } const kubectlLastConfig = "kubectl.kubernetes.io/last-applied-configuration" diff --git a/internal/remote/pristine_test.go b/internal/remote/pristine_test.go index ff79b169..2bb5e1ac 100644 --- a/internal/remote/pristine_test.go +++ b/internal/remote/pristine_test.go @@ -192,7 +192,7 @@ func TestPristineReaderNoFallback(t *testing.T) { func TestCreateFromPristine(t *testing.T) { un := loadFile(t, "input.yaml") p := qbecPristine{} - obj := model.NewK8sLocalObject(un.Object, "app", "", "comp1", "dev") + obj := model.NewK8sLocalObject(un.Object, "app", "", "comp1", "dev", false) ret, err := p.createFromPristine(obj) require.Nil(t, err) a := assert.New(t) diff --git a/internal/types/secrets.go b/internal/types/secrets.go index f805b1e8..7699c218 100644 --- a/internal/types/secrets.go +++ b/internal/types/secrets.go @@ -96,5 +96,5 @@ func HideSensitiveLocalInfo(in model.K8sLocalObject) (model.K8sLocalObject, bool if !changed { return in, false } - return model.NewK8sLocalObject(obj.Object, in.Application(), in.Tag(), in.Component(), in.Environment()), true + return model.NewK8sLocalObject(obj.Object, in.Application(), in.Tag(), in.Component(), in.Environment(), false), true } diff --git a/internal/types/secrets_test.go b/internal/types/secrets_test.go index 819f41ed..038b9227 100644 --- a/internal/types/secrets_test.go +++ b/internal/types/secrets_test.go @@ -60,8 +60,8 @@ func toData(s string) map[string]interface{} { } func TestSecrets(t *testing.T) { - cmObj := model.NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1") - secretObj := model.NewK8sLocalObject(toData(secret), "", "app1", "c1", "e1") + cmObj := model.NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1", false) + secretObj := model.NewK8sLocalObject(toData(secret), "", "app1", "c1", "e1", false) a := assert.New(t) a.False(HasSensitiveInfo(cmObj.ToUnstructured())) a.True(HasSensitiveInfo(secretObj.ToUnstructured())) diff --git a/site/content/reference/qbec-yaml.md b/site/content/reference/qbec-yaml.md index c6f325c2..4cb0e547 100644 --- a/site/content/reference/qbec-yaml.md +++ b/site/content/reference/qbec-yaml.md @@ -81,6 +81,9 @@ spec: envFiles: - more-envs.yaml - https://my.server/envs.yaml + + # if the following attribute is set to true, qbec will add component names also as labels to Kubernetes objects. + addComponentLabel: true ``` ### Environment files From d9ba742a94028affcbf4381af5bd6daf369843f0 Mon Sep 17 00:00:00 2001 From: gotwarlost Date: Wed, 23 Dec 2020 12:21:00 -0800 Subject: [PATCH 34/38] update changelog, up version --- CHANGELOG.md | 5 +++++ Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910d4c16..2bb0dad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog --- +## v0.13.3 (Dec 23, 2020) + +* Add ability to add the component name as a label in addition to the existing annotation. This is opt-in and is activated + by setting the `addComponentLabel` property to `true` in qbec.yaml (thanks @korroot and @hudymi). + ## v0.13.2 (Dec 3, 2020) * Allow force options to be set via environment variables (thanks @splkforrest) diff --git a/Makefile b/Makefile index 5f378862..e68e022e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ include Makefile.tools -VERSION := 0.13.2 +VERSION := 0.13.3 SHORT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) GO_VERSION := $(shell go version | awk '{ print $$3}' | sed 's/^go//') From 044dc6419ba06e587af6acb82bd7cf8a3732c7cc Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 25 Dec 2020 15:41:17 -0800 Subject: [PATCH 35/38] refactor component evaluation and local object creation (#190) * refactor component evaluation and local object creation * extract object producer out of context and supply only where needed --- internal/commands/common_test.go | 2 +- internal/commands/config.go | 46 ++++++++++---- internal/commands/config_test.go | 25 +++++--- internal/commands/filter.go | 2 +- internal/eval/eval.go | 47 +++++--------- internal/eval/eval_test.go | 106 +++++++++++-------------------- internal/model/app.go | 2 + internal/model/k8s.go | 27 +++++--- internal/model/k8s_test.go | 6 +- internal/objsort/sort_test.go | 2 +- internal/remote/pristine.go | 7 +- internal/remote/pristine_test.go | 2 +- internal/types/secrets.go | 7 +- internal/types/secrets_test.go | 4 +- 14 files changed, 145 insertions(+), 140 deletions(-) diff --git a/internal/commands/common_test.go b/internal/commands/common_test.go index 590d7ffb..775b22eb 100644 --- a/internal/commands/common_test.go +++ b/internal/commands/common_test.go @@ -52,7 +52,7 @@ func (i input) makeObject() model.K8sLocalObject { "foo": "bar", }, } - return model.NewK8sLocalObject(data, "app1", "t1", i.component, i.env, false) + return model.NewK8sLocalObject(data, model.LocalAttrs{App: "app1", Tag: "t1", Component: i.component, Env: i.env}) } func (i input) String() string { diff --git a/internal/commands/config.go b/internal/commands/config.go index be31baff..33e7664b 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -195,7 +195,7 @@ type config struct { // init checks variables and sets up defaults. In strict mode, it requires all variables // to be specified and does not allow undeclared variables to be passed in. // It also sets the base VM config to include the library paths from the app definition -// and exclude all TLA variables. Require TLA variables are set per component later. +// and exclude all TLA variables. Required TLA variables are set per component later. func (c *config) init(strict bool) error { var msgs []string c.tlaVars = c.vmc.TopLevelVars() @@ -280,24 +280,42 @@ func (c config) EvalContext(env string, props map[string]interface{}) eval.Conte if err != nil { sio.Warnln("unable to serialize env properties to JSON:", err) } + cm := "off" + if c.cleanEvalMode { + cm = "on" + } + baseConfig := c.vmc.WithVars(map[string]string{ + model.QbecNames.EnvVarName: env, + model.QbecNames.TagVarName: c.app.Tag(), + model.QbecNames.DefaultNsVarName: c.app.DefaultNamespace(env), + model.QbecNames.CleanModeVarName: cm, + }).WithCodeVars(map[string]string{ + model.QbecNames.EnvPropsVarName: string(p), + }) return eval.Context{ - App: c.App().Name(), - Tag: c.App().Tag(), - Env: env, - EnvPropsJSON: string(p), - DefaultNs: c.App().DefaultNamespace(env), - VMConfig: c.vmConfig, - Verbose: c.Verbosity() > 1, - AddComponentLabel: c.App().AddComponentLabel(), - Concurrency: c.EvalConcurrency(), - PostProcessFile: c.App().PostProcessor(), - CleanMode: c.cleanEvalMode, + VMConfig: func(tlaVars []string) vm.Config { return c.vmConfig(baseConfig, tlaVars) }, + Verbose: c.Verbosity() > 1, + Concurrency: c.EvalConcurrency(), + PostProcessFile: c.App().PostProcessor(), + } +} + +func (c config) ObjectProducer(env string) eval.LocalObjectProducer { + return func(component string, data map[string]interface{}) model.K8sLocalObject { + app := c.app + return model.NewK8sLocalObject(data, model.LocalAttrs{ + App: app.Name(), + Tag: app.Tag(), + Component: component, + Env: env, + SetComponentLabel: app.AddComponentLabel(), + }) } } // vmConfig returns the VM configuration that only has the supplied top-level arguments. -func (c config) vmConfig(tlaVars []string) vm.Config { - cfg := c.vmc.WithoutTopLevel() +func (c config) vmConfig(baseConfig vm.Config, tlaVars []string) vm.Config { + cfg := baseConfig.WithoutTopLevel() // common case to avoid useless object creation. If no required vars // needed or none present, just return the config with empty TLAs diff --git a/internal/commands/config_test.go b/internal/commands/config_test.go index 829b3f64..6371fbc8 100644 --- a/internal/commands/config_test.go +++ b/internal/commands/config_test.go @@ -62,18 +62,22 @@ func TestConfigCreate(t *testing.T) { a.Nil(cfg.Confirm("we will destroy you")) ctx := cfg.EvalContext("dev", map[string]interface{}{"foo": "bar"}) - a.Equal("app1", ctx.App) - a.Equal("dev", ctx.Env) - a.Equal("t1", ctx.Tag) - a.Equal("kube-system-t1", ctx.DefaultNs) a.Equal(cfg.EvalConcurrency(), ctx.Concurrency) - a.Equal(false, ctx.AddComponentLabel) testVMC := ctx.VMConfig([]string{"tlaFoo", "tlaBar"}) a.EqualValues(map[string]string{"tlaFoo": "xxx"}, testVMC.TopLevelVars()) a.EqualValues(map[string]string{"tlaBar": "true"}, testVMC.TopLevelCodeVars()) - a.EqualValues(map[string]string{"extFoo": "xxx"}, testVMC.Vars()) - a.EqualValues(map[string]string{"extBar": `{"bar":"quux"}`}, testVMC.CodeVars()) + a.EqualValues(map[string]string{ + "extFoo": "xxx", + "qbec.io/cleanMode": "off", + "qbec.io/defaultNs": "kube-system-t1", + "qbec.io/env": "dev", + "qbec.io/tag": "t1", + }, testVMC.Vars()) + a.EqualValues(map[string]string{ + "extBar": `{"bar":"quux"}`, + "qbec.io/envProperties": `{"foo":"bar"}`, + }, testVMC.CodeVars()) } func TestConfigStrictVarsPass(t *testing.T) { @@ -207,7 +211,12 @@ data: for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ret := ordering(model.NewK8sLocalObject(test.data, "app", "tag", "component", "env", false)) + ret := ordering(model.NewK8sLocalObject(test.data, model.LocalAttrs{ + App: "app", + Tag: "tag", + Component: "component", + Env: "env", + })) assert.Equal(t, test.expected, ret) }) } diff --git a/internal/commands/filter.go b/internal/commands/filter.go index 2b89bd6f..00e21def 100644 --- a/internal/commands/filter.go +++ b/internal/commands/filter.go @@ -116,7 +116,7 @@ func filteredObjects(cfg *config, env string, kf keyFunc, fp filterParams) ([]mo if err != nil { return nil, err } - output, err := eval.Components(components, cfg.EvalContext(env, props)) + output, err := eval.Components(components, cfg.EvalContext(env, props), cfg.ObjectProducer(env)) if err != nil { return nil, err } diff --git a/internal/eval/eval.go b/internal/eval/eval.go index b3eb7653..6836bf7b 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -43,6 +43,10 @@ const ( // specified top-level variables of interest. type VMConfigFunc func(tlaVars []string) vm.Config +// LocalObjectProducer converts a data object that has basic Kubernetes attributes +// to a local model object. +type LocalObjectProducer func(component string, data map[string]interface{}) model.K8sLocalObject + type postProc struct { ctx Context code string @@ -83,17 +87,10 @@ func (p postProc) run(obj map[string]interface{}) (map[string]interface{}, error // Context is the evaluation context type Context struct { - App string // the application for which the evaluation is done - Tag string // the gc tag if present - Env string // the environment for which the evaluation is done - EnvPropsJSON string // the environment properties to expose as an external variable - DefaultNs string // the default namespace to expose as an external variable - VMConfig VMConfigFunc // the base VM config to use for eval - Verbose bool // show generated code - AddComponentLabel bool // add component name as label to Kubernetes objects - Concurrency int // concurrent components to evaluate, default 5 - PostProcessFile string // the file that contains post-processing code for all objects - CleanMode bool // whether clean mode is enabled + VMConfig VMConfigFunc // the base VM config to use for eval + Verbose bool // show generated code + Concurrency int // concurrent components to evaluate, default 5 + PostProcessFile string // the file that contains post-processing code for all objects } func (c Context) baseVMConfig(tlas []string) vm.Config { @@ -101,19 +98,7 @@ func (c Context) baseVMConfig(tlas []string) vm.Config { if fn == nil { fn = defaultFunc } - cm := "off" - if c.CleanMode { - cm = "on" - } - cfg := fn(tlas).WithVars(map[string]string{ - model.QbecNames.EnvVarName: c.Env, - model.QbecNames.TagVarName: c.Tag, - model.QbecNames.DefaultNsVarName: c.DefaultNs, - model.QbecNames.CleanModeVarName: cm, - }).WithCodeVars(map[string]string{ - model.QbecNames.EnvPropsVarName: c.EnvPropsJSON, - }) - return cfg + return fn(tlas) } func (c Context) vm(tlas []string) *vm.VM { @@ -139,7 +124,7 @@ var defaultFunc = func(_ []string) vm.Config { return vm.Config{} } // Components evaluates the specified components using the specific runtime // parameters file and returns the result. -func Components(components []model.Component, ctx Context) (_ []model.K8sLocalObject, finalErr error) { +func Components(components []model.Component, ctx Context, lop LocalObjectProducer) (_ []model.K8sLocalObject, finalErr error) { start := time.Now() defer func() { if finalErr == nil { @@ -150,7 +135,7 @@ func Components(components []model.Component, ctx Context) (_ []model.K8sLocalOb if err != nil { return nil, err } - ret, err := evalComponents(components, ctx, pe) + ret, err := evalComponents(components, ctx, pe, lop) if err != nil { return nil, err } @@ -207,7 +192,7 @@ func evaluationCode(file string) (string, string, error) { return inputCode, contextFile, nil } -func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLocalObject, error) { +func evalComponent(ctx Context, c model.Component, pe postProc, lop LocalObjectProducer) ([]model.K8sLocalObject, error) { jvm := ctx.vm(c.TopLevelVars) var inputCode string var contextFile string @@ -216,6 +201,8 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca canonicalFiles = append(canonicalFiles, filepath.ToSlash(f)) } switch { + case len(canonicalFiles) == 0: + return nil, fmt.Errorf("internal error: component %s did not have any files to evaluate", c.Name) case len(canonicalFiles) > 1: var lines []string for _, file := range canonicalFiles { @@ -257,12 +244,12 @@ func evalComponent(ctx Context, c model.Component, pe postProc) ([]model.K8sLoca if err := model.AssertMetadataValid(proc); err != nil { return nil, err } - processed = append(processed, model.NewK8sLocalObject(proc, ctx.App, ctx.Tag, c.Name, ctx.Env, ctx.AddComponentLabel)) + processed = append(processed, lop(c.Name, proc)) } return processed, nil } -func evalComponents(list []model.Component, ctx Context, pe postProc) ([]model.K8sLocalObject, error) { +func evalComponents(list []model.Component, ctx Context, pe postProc, lop LocalObjectProducer) ([]model.K8sLocalObject, error) { var ret []model.K8sLocalObject if len(list) == 0 { return ret, nil @@ -291,7 +278,7 @@ func evalComponents(list []model.Component, ctx Context, pe postProc) ([]model.K go func() { defer wg.Done() for c := range ch { - objs, err := evalComponent(ctx, c, pe) + objs, err := evalComponent(ctx, c, pe, lop) l.Lock() if err != nil { errs = append(errs, err) diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 75414b90..8df6e3d7 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -21,18 +21,34 @@ import ( "github.com/splunk/qbec/internal/model" "github.com/splunk/qbec/internal/testutil" + "github.com/splunk/qbec/internal/vm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func producer(component string, data map[string]interface{}) model.K8sLocalObject { + return model.NewK8sLocalObject(data, model.LocalAttrs{App: "foo", Tag: "", Component: component, Env: "dev"}) +} + +func decorate(ctx Context) Context { + fn := func(tlaVars []string) vm.Config { + return vm.Config{}.WithVars(map[string]string{ + "qbec.io/tag": "t1", + "qbec.io/env": "dev", + "qbec.io/cleanMode": "off", + "qbec.io/defaultNs": "foobar", + }).WithCodeVars(map[string]string{ + "qbec.io/envProperties": `{ foo: "bar"}`, + }) + } + ctx.VMConfig = fn + return ctx +} + func TestEvalParams(t *testing.T) { - paramsMap, err := Params("testdata/params.libsonnet", Context{ - Env: "dev", - EnvPropsJSON: `{"foo": "bar"}`, - Tag: "t1", - DefaultNs: "foobar", - Verbose: true, - }) + paramsMap, err := Params("testdata/params.libsonnet", decorate(Context{ + Verbose: true, + })) require.Nil(t, err) a := assert.New(t) comps, ok := paramsMap["components"].(map[string]interface{}) @@ -46,11 +62,11 @@ func TestEvalParams(t *testing.T) { } func TestEvalParamsNegative(t *testing.T) { - _, err := Params("testdata/params.invalid.libsonnet", Context{Env: "dev"}) + _, err := Params("testdata/params.invalid.libsonnet", decorate(Context{})) require.NotNil(t, err) require.Contains(t, err.Error(), "end of file") - _, err = Params("testdata/params.non-object.libsonnet", Context{Env: "dev"}) + _, err = Params("testdata/params.non-object.libsonnet", decorate(Context{})) require.NotNil(t, err) require.Contains(t, err.Error(), "cannot unmarshal array") } @@ -77,7 +93,10 @@ func TestEvalComponents(t *testing.T) { "testdata/components/d/subdir-cm2.json", }, }, - }, Context{Env: "dev", Verbose: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) + }, + decorate(Context{Verbose: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}), + producer, + ) require.Nil(t, err) require.Equal(t, 5, len(objs)) a := assert.New(t) @@ -113,54 +132,6 @@ func TestEvalComponents(t *testing.T) { a.Equal("subdir-config-map2", obj.GetName()) } -func TestEvalComponentsClean(t *testing.T) { - objs, err := Components([]model.Component{ - { - Name: "a", - Files: []string{"testdata/components/a.json"}, - }, - }, Context{Env: "dev", CleanMode: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) - require.Nil(t, err) - require.Equal(t, 1, len(objs)) - a := assert.New(t) - - obj := objs[0] - a.Equal("a", obj.Component()) - a.Equal("dev", obj.Environment()) - a.Equal("", obj.GroupVersionKind().Group) - a.Equal("v1", obj.GroupVersionKind().Version) - a.Equal("ConfigMap", obj.GroupVersionKind().Kind) - a.Equal("", obj.GetNamespace()) - a.Equal("json-config-map", obj.GetName()) - a.Equal("", obj.ToUnstructured().GetAnnotations()["team"]) - a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"]) - a.Equal("", obj.ToUnstructured().GetLabels()["qbec.io/component"]) -} - -func TestEvalComponentsComponentLabel(t *testing.T) { - objs, err := Components([]model.Component{ - { - Name: "a", - Files: []string{"testdata/components/a.json"}, - }, - }, Context{Env: "dev", CleanMode: true, AddComponentLabel: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) - require.Nil(t, err) - require.Equal(t, 1, len(objs)) - a := assert.New(t) - - obj := objs[0] - a.Equal("a", obj.Component()) - a.Equal("dev", obj.Environment()) - a.Equal("", obj.GroupVersionKind().Group) - a.Equal("v1", obj.GroupVersionKind().Version) - a.Equal("ConfigMap", obj.GroupVersionKind().Kind) - a.Equal("", obj.GetNamespace()) - a.Equal("json-config-map", obj.GetName()) - a.Equal("", obj.ToUnstructured().GetAnnotations()["team"]) - a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"]) - a.Equal("a", obj.ToUnstructured().GetLabels()["qbec.io/component"]) -} - func TestEvalComponentsEdges(t *testing.T) { goodComponents := []model.Component{ {Name: "g1", Files: []string{"testdata/good-components/g1.jsonnet"}}, @@ -262,10 +233,9 @@ func TestEvalComponentsEdges(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ret, err := evalComponents(test.components, Context{ - Env: "dev", + ret, err := evalComponents(test.components, decorate(Context{ Concurrency: test.concurrency, - }, postProc{}) + }), postProc{}, producer) test.asserter(t, ret, err) }) } @@ -277,7 +247,7 @@ func TestEvalComponentsBadJson(t *testing.T) { Name: "bad", Files: []string{"testdata/components/bad.json"}, }, - }, Context{Env: "dev"}) + }, decorate(Context{}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), "invalid character") } @@ -288,7 +258,7 @@ func TestEvalComponentsBadPosProcessor(t *testing.T) { Name: "bad", Files: []string{"testdata/components/good.json"}, }, - }, Context{Env: "dev", PostProcessFile: "foo/bar.jsonnet"}) + }, decorate(Context{PostProcessFile: "foo/bar.jsonnet"}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), "read post-eval file:") } @@ -299,7 +269,7 @@ func TestEvalComponentsBadYaml(t *testing.T) { Name: "bad", Files: []string{"testdata/components/bad.yaml"}, }, - }, Context{Env: "dev"}) + }, decorate(Context{}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), "did not find expected node content") } @@ -310,7 +280,7 @@ func TestEvalComponentsBadObjects(t *testing.T) { Name: "bad", Files: []string{"testdata/components/bad-objects.yaml"}, }, - }, Context{Env: "dev"}) + }, decorate(Context{}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), `non-kubernetes object found while evaluating path "$[0].foo" (found "string"`) } @@ -321,7 +291,7 @@ func TestEvalComponentsBadMetadata(t *testing.T) { Name: "bad-metadata", Files: []string{"testdata/components/bad-metadata.yaml"}, }, - }, Context{Env: "dev"}) + }, decorate(Context{}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), `/v1, Kind=ConfigMap, Name=subdir-config-map1: .metadata.annotations accessor error`) } @@ -332,7 +302,7 @@ func TestEvalComponentsBadPostProc(t *testing.T) { Name: "bad-postproc", Files: []string{"testdata/components/b.yaml"}, }, - }, Context{Env: "dev", PostProcessFile: "testdata/components/bad-pp.libsonnet"}) + }, decorate(Context{PostProcessFile: "testdata/components/bad-pp.libsonnet"}), producer) require.NotNil(t, err) require.Contains(t, err.Error(), `post-eval did not return an object`) } @@ -406,7 +376,7 @@ func TestEvalPostProcessor(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx := Context{Env: "dev"} + ctx := decorate(Context{}) pp := postProc{ctx: ctx, code: test.code, file: "pp.jsonnet"} ret, err := pp.run(obj) test.asserter(t, ret, err) diff --git a/internal/model/app.go b/internal/model/app.go index 6a923996..ccc486d5 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -268,6 +268,8 @@ func (a *App) LibPaths() []string { return a.inner.Spec.LibPaths } +// AddComponentLabel returns if the qbec component name should be added as an object label in addition to the +// standard annotation. func (a *App) AddComponentLabel() bool { return a.inner.Spec.AddComponentLabel } diff --git a/internal/model/k8s.go b/internal/model/k8s.go index 69808ec6..3fe65a7e 100644 --- a/internal/model/k8s.go +++ b/internal/model/k8s.go @@ -133,22 +133,31 @@ func NewK8sObject(data map[string]interface{}) K8sObject { return &ko{Unstructured: toUnstructured(data)} } +// LocalAttrs are the attributes used to create local k8s objects. +type LocalAttrs struct { + App string + Tag string + Component string + Env string + SetComponentLabel bool +} + // NewK8sLocalObject wraps a K8sLocalObject implementation around the unstructured object data specified as a bag // of attributes for the supplied application, component and environment. -func NewK8sLocalObject(data map[string]interface{}, app, tag, component, env string, setComponentAsLabel bool) K8sLocalObject { +func NewK8sLocalObject(data map[string]interface{}, attrs LocalAttrs) K8sLocalObject { base := toUnstructured(data) - ret := &ko{Unstructured: base, app: app, tag: tag, comp: component, env: env} + ret := &ko{Unstructured: base, app: attrs.App, tag: attrs.Tag, comp: attrs.Component, env: attrs.Env} labels := base.GetLabels() if labels == nil { labels = map[string]string{} } - labels[QbecNames.ApplicationLabel] = app - if tag != "" { - labels[QbecNames.TagLabel] = tag + labels[QbecNames.ApplicationLabel] = attrs.App + if attrs.Tag != "" { + labels[QbecNames.TagLabel] = attrs.Tag } - labels[QbecNames.EnvironmentLabel] = env - if setComponentAsLabel { - labels[QbecNames.ComponentLabel] = component + labels[QbecNames.EnvironmentLabel] = attrs.Env + if attrs.SetComponentLabel { + labels[QbecNames.ComponentLabel] = attrs.Component } base.SetLabels(labels) @@ -156,7 +165,7 @@ func NewK8sLocalObject(data map[string]interface{}, app, tag, component, env str if anns == nil { anns = map[string]string{} } - anns[QbecNames.ComponentAnnotation] = component + anns[QbecNames.ComponentAnnotation] = attrs.Component base.SetAnnotations(anns) return ret } diff --git a/internal/model/k8s_test.go b/internal/model/k8s_test.go index dae742a5..4984bb2b 100644 --- a/internal/model/k8s_test.go +++ b/internal/model/k8s_test.go @@ -64,7 +64,7 @@ func TestK8sObject(t *testing.T) { } func TestK8sLocalObject(t *testing.T) { - obj := NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1", false) + obj := NewK8sLocalObject(toData(cm), LocalAttrs{App: "app1", Tag: "", Component: "c1", Env: "e1"}) a := assert.New(t) a.Equal("app1", obj.Application()) a.Equal("c1", obj.Component()) @@ -80,7 +80,7 @@ func TestK8sLocalObject(t *testing.T) { } func TestK8sLocalObjectWithTag(t *testing.T) { - obj := NewK8sLocalObject(toData(cm), "app1", "t1", "c1", "e1", false) + obj := NewK8sLocalObject(toData(cm), LocalAttrs{App: "app1", Tag: "t1", Component: "c1", Env: "e1"}) a := assert.New(t) a.Equal("app1", obj.Application()) a.Equal("c1", obj.Component()) @@ -95,7 +95,7 @@ func TestK8sLocalObjectWithTag(t *testing.T) { } func TestK8sLocalObjectWithComponentLabel(t *testing.T) { - obj := NewK8sLocalObject(toData(cm), "app1", "t1", "c1", "e1", true) + obj := NewK8sLocalObject(toData(cm), LocalAttrs{App: "app1", Tag: "t1", Component: "c1", Env: "e1", SetComponentLabel: true}) a := assert.New(t) a.Equal("app1", obj.Application()) a.Equal("c1", obj.Component()) diff --git a/internal/objsort/sort_test.go b/internal/objsort/sort_test.go index a177f32a..ff93aa11 100644 --- a/internal/objsort/sort_test.go +++ b/internal/objsort/sort_test.go @@ -42,7 +42,7 @@ func object(d data) model.K8sLocalObject { "namespace": d.namespace, "name": d.name, }, - }, "app1", "", d.component, "dev", false) + }, model.LocalAttrs{App: "app1", Tag: "", Component: d.component, Env: "dev"}) } func TestBasicSort(t *testing.T) { diff --git a/internal/remote/pristine.go b/internal/remote/pristine.go index 5979fd27..3a28dd50 100644 --- a/internal/remote/pristine.go +++ b/internal/remote/pristine.go @@ -115,7 +115,12 @@ func (k qbecPristine) createFromPristine(pristine model.K8sLocalObject) (model.K } annotations[model.QbecNames.PristineAnnotation] = zipped annotated.SetAnnotations(annotations) - return model.NewK8sLocalObject(annotated.Object, pristine.Application(), pristine.Tag(), pristine.Component(), pristine.Environment(), false), nil + return model.NewK8sLocalObject(annotated.Object, model.LocalAttrs{ + App: pristine.Application(), + Tag: pristine.Tag(), + Component: pristine.Component(), + Env: pristine.Environment(), + }), nil } const kubectlLastConfig = "kubectl.kubernetes.io/last-applied-configuration" diff --git a/internal/remote/pristine_test.go b/internal/remote/pristine_test.go index 2bb5e1ac..969ed7a1 100644 --- a/internal/remote/pristine_test.go +++ b/internal/remote/pristine_test.go @@ -192,7 +192,7 @@ func TestPristineReaderNoFallback(t *testing.T) { func TestCreateFromPristine(t *testing.T) { un := loadFile(t, "input.yaml") p := qbecPristine{} - obj := model.NewK8sLocalObject(un.Object, "app", "", "comp1", "dev", false) + obj := model.NewK8sLocalObject(un.Object, model.LocalAttrs{App: "app", Tag: "", Component: "comp1", Env: "dev"}) ret, err := p.createFromPristine(obj) require.Nil(t, err) a := assert.New(t) diff --git a/internal/types/secrets.go b/internal/types/secrets.go index 7699c218..e5bd0e22 100644 --- a/internal/types/secrets.go +++ b/internal/types/secrets.go @@ -96,5 +96,10 @@ func HideSensitiveLocalInfo(in model.K8sLocalObject) (model.K8sLocalObject, bool if !changed { return in, false } - return model.NewK8sLocalObject(obj.Object, in.Application(), in.Tag(), in.Component(), in.Environment(), false), true + return model.NewK8sLocalObject(obj.Object, model.LocalAttrs{ + App: in.Application(), + Tag: in.Tag(), + Component: in.Component(), + Env: in.Environment(), + }), true } diff --git a/internal/types/secrets_test.go b/internal/types/secrets_test.go index 038b9227..66bf3e7e 100644 --- a/internal/types/secrets_test.go +++ b/internal/types/secrets_test.go @@ -60,8 +60,8 @@ func toData(s string) map[string]interface{} { } func TestSecrets(t *testing.T) { - cmObj := model.NewK8sLocalObject(toData(cm), "app1", "", "c1", "e1", false) - secretObj := model.NewK8sLocalObject(toData(secret), "", "app1", "c1", "e1", false) + cmObj := model.NewK8sLocalObject(toData(cm), model.LocalAttrs{App: "app1", Tag: "", Component: "c1", Env: "e1"}) + secretObj := model.NewK8sLocalObject(toData(secret), model.LocalAttrs{App: "app1", Component: "c1", Env: "e1"}) a := assert.New(t) a.False(HasSensitiveInfo(cmObj.ToUnstructured())) a.True(HasSensitiveInfo(secretObj.ToUnstructured())) From 99727548cfc52373cf8baabbd5e0dc79e6f27a5b Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 25 Dec 2020 16:37:49 -0800 Subject: [PATCH 36/38] allow doublestar patterns in glob importer (#191) --- go.mod | 1 + go.sum | 3 +++ internal/vm/importers/glob.go | 3 ++- internal/vm/importers/glob_test.go | 22 +++++++++++++++++++ .../testdata/example2/inc1/subdir/a.json | 3 +++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 internal/vm/importers/testdata/example2/inc1/subdir/a.json diff --git a/go.mod b/go.mod index 5c94139d..219f17a3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/splunk/qbec go 1.15 require ( + github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect diff --git a/go.sum b/go.sum index a78a4c5a..c98687c6 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,9 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= +github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= diff --git a/internal/vm/importers/glob.go b/internal/vm/importers/glob.go index b7d77d36..f47b7658 100644 --- a/internal/vm/importers/glob.go +++ b/internal/vm/importers/glob.go @@ -10,6 +10,7 @@ import ( "sort" "strings" + "github.com/bmatcuk/doublestar/v2" "github.com/google/go-jsonnet" ) @@ -144,7 +145,7 @@ func (g *GlobImporter) Import(importedFrom, importedPath string) (contents jsonn }) }() - matches, err := filepath.Glob(globPath) + matches, err := doublestar.Glob(globPath) if err != nil { return contents, foundAt, fmt.Errorf("unable to expand glob %q, %v", globPath, err) } diff --git a/internal/vm/importers/glob_test.go b/internal/vm/importers/glob_test.go index 3bf40f4a..60ac4589 100644 --- a/internal/vm/importers/glob_test.go +++ b/internal/vm/importers/glob_test.go @@ -57,6 +57,28 @@ func TestGlobSimple(t *testing.T) { } } +func TestGlobDoublestar(t *testing.T) { + vm, _ := makeVM() + data := evaluateVirtual(t, vm, "testdata/caller.jsonnet", `import 'glob-import:example2/**/*.json'`) + expectedJSON := ` +{ + "example2/inc1/a.json": { + "a": "a" + }, + "example2/inc1/subdir/a.json": { + "a": "inner a" + }, + "example2/inc2/a.json": { + "a": "long form a" + } +} +` + var expected interface{} + err := json.Unmarshal([]byte(expectedJSON), &expected) + require.Nil(t, err) + assert.EqualValues(t, expected, data) +} + func TestDuplicateFileName(t *testing.T) { vm, _ := makeVM() data := evaluateVirtual(t, vm, "testdata/example2/caller.jsonnet", `import 'glob-import:inc?/*.json'`) diff --git a/internal/vm/importers/testdata/example2/inc1/subdir/a.json b/internal/vm/importers/testdata/example2/inc1/subdir/a.json new file mode 100644 index 00000000..83aff0fe --- /dev/null +++ b/internal/vm/importers/testdata/example2/inc1/subdir/a.json @@ -0,0 +1,3 @@ +{ + "a": "inner a" +} From ad51b2a79bc11d5787443bbb4c4cd16142295063 Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 25 Dec 2020 16:59:15 -0800 Subject: [PATCH 37/38] add native function to expose k8s label selector functionality (#192) * add native function to expose k8s label selector functionality * add negative tests --- internal/vm/nativefuncs.go | 30 ++++++++ internal/vm/nativefuncs_test.go | 117 ++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/internal/vm/nativefuncs.go b/internal/vm/nativefuncs.go index 61d4aa2a..ab8097ea 100644 --- a/internal/vm/nativefuncs.go +++ b/internal/vm/nativefuncs.go @@ -21,11 +21,14 @@ package vm import ( "bytes" "encoding/json" + "fmt" + "reflect" "regexp" "github.com/google/go-jsonnet" "github.com/google/go-jsonnet/ast" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/labels" ) // registerNativeFuncs adds kubecfg's native jsonnet functions to provided VM @@ -106,4 +109,31 @@ func registerNativeFuncs(vm *jsonnet.VM) { }, }) + vm.NativeFunction(&jsonnet.NativeFunction{ + Name: "labelsMatchSelector", + Params: []ast.Identifier{"labels", "selectorString"}, + Func: func(args []interface{}) (res interface{}, err error) { + lbls, ok := args[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("invalid labels type, %v, want a map", reflect.TypeOf(args[0])) + } + selStr, ok := args[1].(string) + if !ok { + return nil, fmt.Errorf("invalid selector of type %v, want a string", reflect.TypeOf(args[1])) + } + input := map[string]string{} + for k, v := range lbls { + val, ok := v.(string) + if !ok { + return nil, fmt.Errorf("invalid label map value, %v, want a string", reflect.TypeOf(v)) + } + input[k] = val + } + sel, err := labels.Parse(selStr) + if err != nil { + return false, fmt.Errorf("invalid label selector: '%s', %v", selStr, err) + } + return sel.Matches(labels.Set(input)), nil + }, + }) } diff --git a/internal/vm/nativefuncs_test.go b/internal/vm/nativefuncs_test.go index 53222c9d..e5213433 100644 --- a/internal/vm/nativefuncs_test.go +++ b/internal/vm/nativefuncs_test.go @@ -16,6 +16,8 @@ package vm import ( + "fmt" + "regexp" "testing" "github.com/google/go-jsonnet" @@ -112,3 +114,118 @@ func TestRegexQuoteMeta(t *testing.T) { x, err := vm.EvaluateSnippet("test", `std.native("escapeStringRegex")("[f]")`) check(t, err, x, `"\\[f\\]"`+"\n") } + +func TestLabelSelectorMatch(t *testing.T) { + vm := jsonnet.MakeVM() + registerNativeFuncs(vm) + tests := []struct { + name string + selector string + expected string + }{ + { + name: "presence", + selector: "env", + expected: "yes", + }, + { + name: "absence", + selector: "!env", + expected: "no", + }, + { + name: "and-presence", + selector: "env,region", + expected: "yes", + }, + { + name: "no-presence", + selector: "foo", + expected: "no", + }, + { + name: "equality", + selector: "region=us-west", + expected: "yes", + }, + { + name: "equality-no-match", + selector: "env=prod", + expected: "no", + }, + { + name: "and", + selector: "region=us-west,env=dev", + expected: "yes", + }, + { + name: "and-no-match", + selector: "region=us-west,!env", + expected: "no", + }, + { + name: "in", + selector: "env in (prod, dev)", + expected: "yes", + }, + { + name: "not-in", + selector: "env notin (prod, dev)", + expected: "no", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + code := fmt.Sprintf(` + local labels = { env: 'dev', region: 'us-west' }; + if std.native('labelsMatchSelector')(labels, '%s') then 'yes' else 'no' +`, test.selector) + ret, err := vm.EvaluateSnippet("test.jsonnet", code) + check(t, err, ret, fmt.Sprintf(`"%s"`+"\n", test.expected)) + }) + } +} + +func TestLabelSelectorNegative(t *testing.T) { + vm := jsonnet.MakeVM() + registerNativeFuncs(vm) + tests := []struct { + name string + code string + errMatch *regexp.Regexp + }{ + { + name: "bad map", + code: `std.native('labelsMatchSelector')([],'foo')`, + errMatch: regexp.MustCompile(`invalid labels type, \[\]interface {}, want a map`), + }, + { + name: "non-string map", + code: `std.native('labelsMatchSelector')({ foo: {} },'foo')`, + errMatch: regexp.MustCompile(`invalid label map value, map\[string\]interface {}, want a string`), + }, + { + name: "bad selector type", + code: `std.native('labelsMatchSelector')({},{})`, + errMatch: regexp.MustCompile(`invalid selector of type map\[string\]interface {}, want a string`), + }, + { + name: "bad selector", + code: `std.native('labelsMatchSelector')({},'!!env')`, + errMatch: regexp.MustCompile(`invalid label selector: '!!env'`), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := vm.EvaluateSnippet("test.jsonnet", test.code) + if err == nil { + t.Errorf("labelsMatchSelector succeeded on invalid input") + } + if !test.errMatch.MatchString(err.Error()) { + t.Errorf("message %q does not match %v", err.Error(), test.errMatch) + } + }) + } + +} From b72657cff2e96f240434b83366b87d90d2f30e4b Mon Sep 17 00:00:00 2001 From: Krishnan Anantheswaran Date: Fri, 25 Dec 2020 19:55:37 -0800 Subject: [PATCH 38/38] allow componentsDir to be a glob (#193) * allow componentsDir to be a glob This adds support for multiple component directories expressed as a glob, but does not introduce any namespace semantics. Components still have to be unqiue across all directories. * add tests --- internal/model/app.go | 120 +++++++++++------- internal/model/app_test.go | 28 ++++ .../multi-dir-app/components/README.md | 4 + .../multi-dir-app/components/dir1/a.jsonnet | 10 ++ .../components/dir2/b/index.jsonnet | 11 ++ .../model/testdata/multi-dir-app/qbec.yaml | 10 ++ internal/model/testdata/no-dirs-app/qbec.yaml | 10 ++ 7 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 internal/model/testdata/multi-dir-app/components/README.md create mode 100644 internal/model/testdata/multi-dir-app/components/dir1/a.jsonnet create mode 100644 internal/model/testdata/multi-dir-app/components/dir2/b/index.jsonnet create mode 100644 internal/model/testdata/multi-dir-app/qbec.yaml create mode 100644 internal/model/testdata/no-dirs-app/qbec.yaml diff --git a/internal/model/app.go b/internal/model/app.go index ccc486d5..930483f3 100644 --- a/internal/model/app.go +++ b/internal/model/app.go @@ -466,71 +466,95 @@ func (a *App) DeclaredTopLevelVars() map[string]interface{} { return ret } -// loadComponents loads metadata for all components for the app. -// The data is returned as a map keyed by component name. It does _not_ recurse -// into subdirectories. +// loadComponents loads metadata for all components for the app. It first expands the components directory +// for glob patterns and loads components from all directories that match. It does _not_ recurse +// into subdirectories. The data is returned as a map keyed by component name. +// Note that component names must be unique across all directories. Support for multiple directories is just a +// way to partition classes of components and does not introduce any namespace semantics. func (a *App) loadComponents() (map[string]Component, error) { var list []Component - dir := strings.TrimSuffix(filepath.Clean(a.inner.Spec.ComponentsDir), "/") - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if path == dir { - return nil - } - if info.IsDir() { - files, err := filepath.Glob(filepath.Join(path, "*")) + loadDirComponents := func(dir string) error { + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - var staticFiles []string - hasIndexJsonnet := false - hasIndexYAML := false - for _, f := range files { - stat, err := os.Stat(f) + if path == dir { + return nil + } + if info.IsDir() { + files, err := filepath.Glob(filepath.Join(path, "*")) if err != nil { return err } - if stat.IsDir() { - continue + var staticFiles []string + hasIndexJsonnet := false + hasIndexYAML := false + for _, f := range files { + stat, err := os.Stat(f) + if err != nil { + return err + } + if stat.IsDir() { + continue + } + switch filepath.Base(f) { + case "index.jsonnet": + hasIndexJsonnet = true + case "index.yaml": + hasIndexYAML = true + } + if strings.HasSuffix(f, ".json") || strings.HasSuffix(f, ".yaml") { + staticFiles = append(staticFiles, f) + } } - switch filepath.Base(f) { - case "index.jsonnet": - hasIndexJsonnet = true - case "index.yaml": - hasIndexYAML = true - } - if strings.HasSuffix(f, ".json") || strings.HasSuffix(f, ".yaml") { - staticFiles = append(staticFiles, f) + switch { + case hasIndexJsonnet: + list = append(list, Component{ + Name: filepath.Base(path), + Files: []string{filepath.Join(path, "index.jsonnet")}, + }) + case hasIndexYAML: + list = append(list, Component{ + Name: filepath.Base(path), + Files: staticFiles, + }) } + return filepath.SkipDir } - switch { - case hasIndexJsonnet: - list = append(list, Component{ - Name: filepath.Base(path), - Files: []string{filepath.Join(path, "index.jsonnet")}, - }) - case hasIndexYAML: + extension := filepath.Ext(path) + if supportedExtensions[extension] { list = append(list, Component{ - Name: filepath.Base(path), - Files: staticFiles, + Name: strings.TrimSuffix(filepath.Base(path), extension), + Files: []string{path}, }) } - return filepath.SkipDir - } - extension := filepath.Ext(path) - if supportedExtensions[extension] { - list = append(list, Component{ - Name: strings.TrimSuffix(filepath.Base(path), extension), - Files: []string{path}, - }) - } - return nil - }) + return nil + }) + return err + } + ds, err := filepath.Glob(a.inner.Spec.ComponentsDir) if err != nil { return nil, err } + var dirs []string + for _, d := range ds { + s, err := os.Stat(d) + if err != nil { + return nil, err + } + if s.IsDir() { + dirs = append(dirs, d) + } + } + if len(dirs) == 0 { + return nil, fmt.Errorf("no component directories found after expanding %s", a.inner.Spec.ComponentsDir) + } + for _, d := range dirs { + err := loadDirComponents(d) + if err != nil { + return nil, err + } + } m := make(map[string]Component, len(list)) for _, c := range list { if old, ok := m[c.Name]; ok { diff --git a/internal/model/app_test.go b/internal/model/app_test.go index 8c07aaf8..b0c33aed 100644 --- a/internal/model/app_test.go +++ b/internal/model/app_test.go @@ -238,6 +238,7 @@ func TestAppSimple(t *testing.T) { envs := app.Environments() a.Equal(4, len(envs)) + a.False(app.ClusterScopedLists()) } func TestAppWarnings(t *testing.T) { @@ -299,6 +300,33 @@ func TestAppComponentLoadSubdirs(t *testing.T) { a.Contains(comp.Files, filepath.Join("components", "comp2", "index.yaml")) } +func TestAppComponentLoadMultidirs(t *testing.T) { + reset := setPwd(t, "testdata/multi-dir-app") + defer reset() + app, err := NewApp("qbec.yaml", nil, "") + require.Nil(t, err) + comps, err := app.ComponentsForEnvironment("dev", nil, nil) + require.Nil(t, err) + a := assert.New(t) + a.Equal(2, len(comps)) + comp := comps[0] + a.Equal("a", comp.Name) + a.Equal(1, len(comp.Files)) + a.Contains(comp.Files, filepath.Join("components", "dir1", "a.jsonnet")) + comp = comps[1] + a.Equal("b", comp.Name) + a.Equal(1, len(comp.Files)) + a.Contains(comp.Files, filepath.Join("components", "dir2", "b", "index.jsonnet")) +} + +func TestAppComponentNoDirs(t *testing.T) { + reset := setPwd(t, "testdata/no-dirs-app") + defer reset() + _, err := NewApp("qbec.yaml", nil, "") + require.Error(t, err) + assert.Equal(t, "load components: no component directories found after expanding components/dir*", err.Error()) +} + func TestAppComponentLoadNegative(t *testing.T) { reset := setPwd(t, "../../examples/test-app") defer reset() diff --git a/internal/model/testdata/multi-dir-app/components/README.md b/internal/model/testdata/multi-dir-app/components/README.md new file mode 100644 index 00000000..07da9030 --- /dev/null +++ b/internal/model/testdata/multi-dir-app/components/README.md @@ -0,0 +1,4 @@ +### multi dirs + +A multi dir app is allowed to have files that are ignored. + diff --git a/internal/model/testdata/multi-dir-app/components/dir1/a.jsonnet b/internal/model/testdata/multi-dir-app/components/dir1/a.jsonnet new file mode 100644 index 00000000..6bb961a0 --- /dev/null +++ b/internal/model/testdata/multi-dir-app/components/dir1/a.jsonnet @@ -0,0 +1,10 @@ +{ + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'cm-a', + }, + data: { + foo: 'bar' + } +} diff --git a/internal/model/testdata/multi-dir-app/components/dir2/b/index.jsonnet b/internal/model/testdata/multi-dir-app/components/dir2/b/index.jsonnet new file mode 100644 index 00000000..d4f97961 --- /dev/null +++ b/internal/model/testdata/multi-dir-app/components/dir2/b/index.jsonnet @@ -0,0 +1,11 @@ +{ + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'cm-b', + }, + data: { + bar: 'baz' + } +} + diff --git a/internal/model/testdata/multi-dir-app/qbec.yaml b/internal/model/testdata/multi-dir-app/qbec.yaml new file mode 100644 index 00000000..b37a87ed --- /dev/null +++ b/internal/model/testdata/multi-dir-app/qbec.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: multi-dir-app +spec: + componentsDir: components/* + environments: + dev: + server: https://dev-server diff --git a/internal/model/testdata/no-dirs-app/qbec.yaml b/internal/model/testdata/no-dirs-app/qbec.yaml new file mode 100644 index 00000000..f1d9dd8f --- /dev/null +++ b/internal/model/testdata/no-dirs-app/qbec.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: qbec.io/v1alpha1 +kind: App +metadata: + name: no-dirs-app +spec: + componentsDir: components/dir* + environments: + dev: + server: https://dev-server