From 527a0d06dd1194723bdf422135da48fe5453cc74 Mon Sep 17 00:00:00 2001 From: Weston McNamee Date: Mon, 30 Mar 2020 09:59:50 -0700 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E2=9A=97=20=20first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + README.md | 95 +++++++++++++++- assets/cupcake.png | Bin 0 -> 33734 bytes examples/full/.gitignore | 21 ++++ examples/full/Dockerfile | 18 +++ examples/full/README.md | 13 +++ examples/full/frosting/greetings/hello.go | 34 ++++++ examples/full/frosting/greetings/namespace.go | 13 +++ examples/full/frosting/main.go | 28 +++++ examples/minimal/README.md | 13 +++ frosting.go | 55 +++++++++ go.mod | 10 ++ go.sum | 9 ++ ingredients.go | 67 +++++++++++ ingredients_test.go | 1 + namespaces.go | 104 ++++++++++++++++++ namespaces_test.go | 1 + resolver.go | 98 +++++++++++++++++ utils.go | 13 +++ 19 files changed, 594 insertions(+), 2 deletions(-) create mode 100644 assets/cupcake.png create mode 100644 examples/full/.gitignore create mode 100644 examples/full/Dockerfile create mode 100644 examples/full/README.md create mode 100644 examples/full/frosting/greetings/hello.go create mode 100644 examples/full/frosting/greetings/namespace.go create mode 100644 examples/full/frosting/main.go create mode 100644 examples/minimal/README.md create mode 100644 frosting.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ingredients.go create mode 100644 ingredients_test.go create mode 100644 namespaces.go create mode 100644 namespaces_test.go create mode 100644 resolver.go create mode 100644 utils.go diff --git a/.gitignore b/.gitignore index 66fd13c..00ee230 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# JetBrains/GoLand +/.idea diff --git a/README.md b/README.md index ebc013e..ec01033 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,93 @@ -# frosting -Enhance the operational tasks of your application with a little _frosting_ + +

+
+ playing card +
+ Frosting +
+

+ +

Enhance the operational tasks of your application with a little frosting 🧁

+ +

+ + + + + + +

+ +

+ Introduction • + Install • + How To Use • + Credits • + Support • + Related • + License +

+ +## Introduction + +`frost` is a workflow tool. Inspired by [Make](https://www.gnu.org/software/make/), [Mage](https://magefile.org/), [Task](https://taskfile.dev/) and others, but with some important differences. + +| Feature | Frost | Make | Mage | Go-Task | +|----------------------|-------|------|------|---------| +| Fast | ✔️ | ✔️ | ✔️ | ✔️ | +| Plugins | ✔️ | | ✔️ | | +| Bash Support | ✔️ | ✔️ | ✔️ | ✔️ | +| Target Flags | ✔️ | ✔️§ | ✔️§ | ✔️§§ | +| Autocomplete | ✔️ | ✔️ | | ✔️ | +| Parallelism | ✔️ | | ✔️† | | +| Imports (Namespaced) | ✔️ | ✔️ | ✔️‡ | ✔️‡ | +| *File | Go | Make | Go | Yaml | + +§ Target behavior can only be modified with environment variables + +§§ Target behavior can be modified with environment and can also can be templated with go templating prior to evaluation and execution. + +† Doesn't support limiting of parallelism ([#273](https://github.com/magefile/mage/pull/273)) + +‡ Mage doesn't currently support nested namespaces ([#152](https://github.com/magefile/mage/issues/152)), and Task support for imports is still experimental + +--- + +Make is fast, but for any sort of complex logic, I found myself writing bash scripts, and I agree with Mage in regards to make/bash: + +> Makefiles are hard to read and hard to write. Mostly because makefiles are essentially fancy bash scripts with significant white space and additional make-related syntax. Go is superior to bash for any non-trivial task involving branching, looping, anything that’s not just straight line execution of commands. + +Mage makes heavy use of code generation in order to create the resulting binary, and I found that to be a high barrier to entry for contributing to the project. + +I've been itching to write my own CLI for awhile now, and I think I finally have a reason to. + +## Install + +```go + +``` + +## How To Use + +```bash + +``` + +## Credits + +- spf13/cobra +- spf13/viper +- hashicorp/go-plugins +- hashicorp/go-versions + +## Support + +Buy Me A Coffee + +Paypal Donate + +_Icons made by [Freepik](https://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)_ + +## Related + +## License \ No newline at end of file diff --git a/assets/cupcake.png b/assets/cupcake.png new file mode 100644 index 0000000000000000000000000000000000000000..2ceb11367fc379058e2ed0e0a099748ee1c822ad GIT binary patch literal 33734 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
Vanw|`x3dy+HF@}BA(YL(L1hr%}+2gGEc8} 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 0 { + dequeuedNamespaces, err := nsQueue.Get(1) + if err != nil { + return nil, fmt.Errorf("problem dequeuing namespace: %w", err) + } + + ns := dequeuedNamespaces[0].(*NamespaceInfo) + if _, ok := visited[ns.id]; ok { + return nil, errors.New("found circular namespace reference") + } + + namespaces = append(namespaces, ns) + visited[ns.id] = true + + nsQueue.Put(ns.Children()) + } + + return namespaces, nil +} \ No newline at end of file diff --git a/namespaces_test.go b/namespaces_test.go new file mode 100644 index 0000000..9ed2a3b --- /dev/null +++ b/namespaces_test.go @@ -0,0 +1 @@ +package frosting diff --git a/resolver.go b/resolver.go new file mode 100644 index 0000000..fd79761 --- /dev/null +++ b/resolver.go @@ -0,0 +1,98 @@ +package frosting + +import ( + "errors" + "fmt" + mapset "github.com/deckarep/golang-set" +) + +type IngredientDependencyResolver interface { + Resolve() (IngredientGraph, error) + CurrentReady() IngredientGraph +} + +type ingredientDependencyResolver struct { + ingredients map[string]*IngredientInfo + resolved IngredientGraph +} + +func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDependencyResolver, error) { + r := &ingredientDependencyResolver{} + + r.ingredients = make(map[string]*IngredientInfo) + r.resolved = IngredientGraph{} + + for _, ing := range ingredients { + if ing.id == "" { + return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. Name: %s", t.Name) + } + r.ingredients[ing.id] = ing + } + + return r, nil +} + +func (r *ingredientDependencyResolver) Resolve() (IngredientGraph, error) { + // if we've already resolved, don't re-resolve + if len(r.resolved) >= 0 { + return r.resolved, nil + } + + ingredientDependencies := make(map[string]mapset.Set) + + // Populate the map + for _, ing := range r.ingredients { + ingredientDependencies[ing.id] = ing.dependencies.Clone() + } + + // Iteratively find and remove nodes from the graph which have no dependencies. + // If at some point there are still nodes in the graph and we cannot find + // nodes without dependencies, that means we have a circular dependency + for len(ingredientDependencies) != 0 { + // Get all nodes from the graph which have no dependencies + readySet := mapset.NewSet() + for id, deps := range ingredientDependencies { + if deps.Cardinality() == 0 { + readySet.Add(id) + } + } + + // If there aren't any ready nodes, then we have a circular dependency + if readySet.Cardinality() == 0 { + var g IngredientGraph + for id := range ingredientDependencies { + g = append(g, r.ingredients[id]) + } + + return g, errors.New("circular dependency found") + } + + // Remove the ready nodes and add them to the resolved graph + for id := range readySet.Iter() { + delete(ingredientDependencies, id.(string)) + r.resolved = append(r.resolved, r.ingredients[id.(string)]) + } + + // Also make sure to remove the ready nodes from the + // remaining node dependencies as well + for id, deps := range ingredientDependencies { + diff := deps.Difference(readySet) + ingredientDependencies[id] = diff + } + } + + return r.resolved, nil +} + +func (r *ingredientDependencyResolver) CurrentReady() IngredientGraph { + currentReady := IngredientGraph{} + for _, ing := range r.resolved { + if ing.currentDependencies.Cardinality() == 0 { + currentReady = append(currentReady, ing) + } + } + + return currentReady +} + +var _ IngredientDependencyResolver = &ingredientDependencyResolver{} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..b7478e3 --- /dev/null +++ b/utils.go @@ -0,0 +1,13 @@ +package frosting + +import ( + "math/rand" + "time" + "github.com/oklog/ulid/v2" +) + +func newULID() string { + t := time.Unix(1000000, 0) + entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) + return ulid.MustNew(ulid.Timestamp(t), entropy).String() +} From 97454eb35ebefc1d373cf875147448fab53acb31 Mon Sep 17 00:00:00 2001 From: Weston McNamee Date: Mon, 30 Mar 2020 11:10:30 -0700 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=F0=9F=9A=80=20=20resolver=20mvp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 --- examples/full/README.md | 4 ++- examples/full/frosting/main.go | 2 +- frosting.go | 47 ++++++++++++++++++++--------- go.mod | 1 + go.sum | 2 ++ ingredients.go | 19 ++++++------ namespaces.go | 55 +++++++++++++++++++--------------- resolver.go | 7 +++-- utils.go | 4 +-- 10 files changed, 88 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ec01033..35f7a5a 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ | Feature | Frost | Make | Mage | Go-Task | |----------------------|-------|------|------|---------| | Fast | ✔️ | ✔️ | ✔️ | ✔️ | -| Plugins | ✔️ | | ✔️ | | | Bash Support | ✔️ | ✔️ | ✔️ | ✔️ | | Target Flags | ✔️ | ✔️§ | ✔️§ | ✔️§§ | | Autocomplete | ✔️ | ✔️ | | ✔️ | @@ -76,9 +75,6 @@ I've been itching to write my own CLI for awhile now, and I think I finally have ## Credits - spf13/cobra -- spf13/viper -- hashicorp/go-plugins -- hashicorp/go-versions ## Support diff --git a/examples/full/README.md b/examples/full/README.md index 6af6b24..f595947 100644 --- a/examples/full/README.md +++ b/examples/full/README.md @@ -8,6 +8,8 @@ Imagine that this is the _root_ of your project repo. It doesn't matter what lan ## Getting Started -```shell +Compile into a binary +```shell +go build -o fr ./frosting ``` diff --git a/examples/full/frosting/main.go b/examples/full/frosting/main.go index b6fd044..0247907 100644 --- a/examples/full/frosting/main.go +++ b/examples/full/frosting/main.go @@ -23,6 +23,6 @@ func main() { client := fr.MustNew( greetings.GreetingsNamespace, ) - client.RootNamespace().MustAddIngredient(Build) + client.RootNamespace().MustAddIngredients(Build) client.Execute(os.Args[1:]...) } diff --git a/frosting.go b/frosting.go index 1c98afa..1132d36 100644 --- a/frosting.go +++ b/frosting.go @@ -1,10 +1,6 @@ package frosting -import ( - "errors" - mapset "github.com/deckarep/golang-set" - "github.com/golang-collections/go-datastructures/queue" -) +import "fmt" type ClientInfo struct { rootNamespace *NamespaceInfo @@ -17,22 +13,45 @@ type Client interface { } func (c *ClientInfo) Execute(args ...string) { - // ingredients := gatherIngredients(c.rootNamespace) + fmt.Printf("resolving namespaces...\n") + namespaces, err := resolveNamespaces(c.rootNamespace) + if err != nil { + panic(fmt.Errorf("unable to resolve namespaces: %w", err)) + } + fmt.Printf("%d namespaces found...\n", len(namespaces)) - //resolver := newIngredientDependencyResolver(ingredients) -} + fmt.Printf("gathering ingredients...\n") + ingredients := gatherIngredients(namespaces) + fmt.Printf("%d ingredients found...\n", len(ingredients)) + + resolver, err := newIngredientDependencyResolver(ingredients) + if err != nil { + panic(fmt.Errorf("unable to create ingredient resolver: %w", err)) + } + fmt.Printf("resolving ingredients...\n") + ingredientsResolved, err := resolver.Resolve() + if err != nil { + panic(fmt.Errorf("unable to resolve ingredients list and dependencies: %w", err)) + } + fmt.Printf("%d resolved ingredients...\n", len(ingredientsResolved)) + for _, ing := range ingredientsResolved { + fmt.Printf("%s:%s\n", ing.namespace.name, ing.Name) + } +} -func gatherIngredients(ns *NamespaceInfo) []*IngredientInfo { +func gatherIngredients(namespaces []*NamespaceInfo) []*IngredientInfo { ingredients := []*IngredientInfo{} - for _, ingFn := range ns.ingredientFns { - ingredients = append(ingredients, ingFn()) - } + for _, ns := range namespaces { + for _, ingFn := range ns.ingredientFns { + ingredient := ingFn() - for _, ns := range ns.Children() { - ingredients = append(ingredients, gatherIngredients(ns)...) + // set where we found the ingredient + ingredient.namespace = ns + ingredients = append(ingredients, ingredient) + } } return ingredients diff --git a/go.mod b/go.mod index 6b97a2e..3849152 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,5 @@ require ( github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 github.com/oklog/ulid v1.3.1 github.com/oklog/ulid/v2 v2.0.2 + gopkg.in/eapache/queue.v1 v1.1.0 ) diff --git a/go.sum b/go.sum index 4a52fdf..73223eb 100644 --- a/go.sum +++ b/go.sum @@ -7,3 +7,5 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +gopkg.in/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc= +gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= diff --git a/ingredients.go b/ingredients.go index bcc2930..55e3b41 100644 --- a/ingredients.go +++ b/ingredients.go @@ -2,19 +2,20 @@ package frosting import ( "errors" - "github.com/deckarep/golang-set" -) -type IngredientGraph []*IngredientInfo + mapset "github.com/deckarep/golang-set" +) type IngredientFn func() *IngredientInfo type IngredientInfo struct { - Name string - Fn func() error + Name string + Fn func() error + + namespace *NamespaceInfo + id string + dependencyFns []IngredientFn - id string - dependencyFns []IngredientFn // set of IngredientInfo's dependencies mapset.Set currentDependencies mapset.Set @@ -32,8 +33,8 @@ func MustNewIngredientInfo(name string, options ...func(ing *IngredientInfo)) *I } ing := &IngredientInfo{ - Name: name, - id: newULID(), + Name: name, + id: newULID(), dependencies: mapset.NewSet(), } diff --git a/namespaces.go b/namespaces.go index 8ba178e..3102255 100644 --- a/namespaces.go +++ b/namespaces.go @@ -2,9 +2,9 @@ package frosting import ( "errors" - "fmt" + "gopkg.in/eapache/queue.v1" + mapset "github.com/deckarep/golang-set" - "github.com/golang-collections/go-datastructures/queue" ) type Namespace interface { @@ -12,14 +12,14 @@ type Namespace interface { ID() string Parent() *NamespaceInfo Children() []*NamespaceInfo - MustAddIngredient(ing IngredientFn) + MustAddIngredients(ingFns ...IngredientFn) } type NamespaceInfo struct { - name string - id string - parent *NamespaceInfo - children mapset.Set + name string + id string + parent *NamespaceInfo + children mapset.Set ingredientFns []IngredientFn } @@ -40,12 +40,14 @@ func (n *NamespaceInfo) Children() []*NamespaceInfo { return children } -func (n *NamespaceInfo) MustAddIngredient(ing IngredientFn) { - if ing == nil { - panic(errors.New("ingredient cannot be nil")) - } +func (n *NamespaceInfo) MustAddIngredients(ing ...IngredientFn) { + for _, ingFn := range ing { + if ingFn == nil { + panic(errors.New("ingredient cannot be nil")) + } - n.ingredientFns = append(n.ingredientFns, ing) + n.ingredientFns = append(n.ingredientFns, ingFn) + } } func (n *NamespaceInfo) ID() string { @@ -59,10 +61,15 @@ func MustNewNamespace(name string, ingredientFns []IngredientFn, children ...*Na panic(errors.New("namespace name cannot be empty")) } + if ingredientFns == nil { + ingredientFns = []IngredientFn{} + } + n := &NamespaceInfo{ - name: name, - id: newULID(), + name: name, + id: newULID(), ingredientFns: ingredientFns, + children: mapset.NewSet(), } for _, child := range children { @@ -76,20 +83,18 @@ func resolveNamespaces(root *NamespaceInfo) ([]*NamespaceInfo, error) { namespaces := []*NamespaceInfo{} visited := make(map[string]bool) - nsQueue := queue.New(1) + nsQueue := queue.New() visited[root.ID()] = true namespaces = append(namespaces, root) - nsQueue.Put(root.Children()) + for _, ns := range root.Children() { + nsQueue.Add(ns) + } - for nsQueue.Len() > 0 { - dequeuedNamespaces, err := nsQueue.Get(1) - if err != nil { - return nil, fmt.Errorf("problem dequeuing namespace: %w", err) - } + for nsQueue.Length() > 0 { + ns := nsQueue.Remove().(*NamespaceInfo) - ns := dequeuedNamespaces[0].(*NamespaceInfo) if _, ok := visited[ns.id]; ok { return nil, errors.New("found circular namespace reference") } @@ -97,8 +102,10 @@ func resolveNamespaces(root *NamespaceInfo) ([]*NamespaceInfo, error) { namespaces = append(namespaces, ns) visited[ns.id] = true - nsQueue.Put(ns.Children()) + for _, ns := range ns.Children() { + nsQueue.Add(ns) + } } return namespaces, nil -} \ No newline at end of file +} diff --git a/resolver.go b/resolver.go index fd79761..3b38635 100644 --- a/resolver.go +++ b/resolver.go @@ -3,9 +3,12 @@ package frosting import ( "errors" "fmt" + mapset "github.com/deckarep/golang-set" ) +type IngredientGraph []*IngredientInfo + type IngredientDependencyResolver interface { Resolve() (IngredientGraph, error) CurrentReady() IngredientGraph @@ -24,7 +27,7 @@ func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDe for _, ing := range ingredients { if ing.id == "" { - return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. Name: %s", t.Name) + return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. Name: %s", ing.Name) } r.ingredients[ing.id] = ing } @@ -34,7 +37,7 @@ func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDe func (r *ingredientDependencyResolver) Resolve() (IngredientGraph, error) { // if we've already resolved, don't re-resolve - if len(r.resolved) >= 0 { + if len(r.resolved) > 0 { return r.resolved, nil } diff --git a/utils.go b/utils.go index b7478e3..d31f1be 100644 --- a/utils.go +++ b/utils.go @@ -1,13 +1,13 @@ package frosting import ( + "github.com/oklog/ulid/v2" "math/rand" "time" - "github.com/oklog/ulid/v2" ) func newULID() string { - t := time.Unix(1000000, 0) + t := time.Now() entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) return ulid.MustNew(ulid.Timestamp(t), entropy).String() } From 9a884f859ae052c86da4a5208b3006747c29479f Mon Sep 17 00:00:00 2001 From: Weston McNamee Date: Fri, 3 Apr 2020 16:15:11 -0700 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=E2=9A=97=20=20experimenting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 + examples/full/frosting/greetings/greetings.go | 32 +++ examples/full/frosting/greetings/hello.go | 34 --- examples/full/frosting/greetings/namespace.go | 13 -- examples/full/frosting/main.go | 50 +++-- examples/full/frosting/main_test.go | 1 + frosting.go | 150 ++++++++----- go.mod | 5 + go.sum | 158 ++++++++++++++ ingredient.go | 58 +++++ ingredient_test.go | 86 ++++++++ ingredientgroup.go | 7 + ingredients.go | 68 ------ ingredients_test.go | 1 - namespaces.go | 111 ---------- namespaces_test.go | 1 - resolver.go | 198 +++++++++--------- sh/shell.go | 138 ++++++++++++ utils.go | 30 +++ 19 files changed, 755 insertions(+), 394 deletions(-) create mode 100644 examples/full/frosting/greetings/greetings.go delete mode 100644 examples/full/frosting/greetings/hello.go delete mode 100644 examples/full/frosting/greetings/namespace.go create mode 100644 examples/full/frosting/main_test.go create mode 100644 ingredient.go create mode 100644 ingredient_test.go create mode 100644 ingredientgroup.go delete mode 100644 ingredients.go delete mode 100644 ingredients_test.go delete mode 100644 namespaces.go delete mode 100644 namespaces_test.go create mode 100644 sh/shell.go diff --git a/README.md b/README.md index 35f7a5a..d39f0fc 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,14 @@ I've been itching to write my own CLI for awhile now, and I think I finally have - spf13/cobra +### Reading + + +- https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables +- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis +- https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully +- https://dave.cheney.net/tag/logging + ## Support Buy Me A Coffee diff --git a/examples/full/frosting/greetings/greetings.go b/examples/full/frosting/greetings/greetings.go new file mode 100644 index 0000000..ea15102 --- /dev/null +++ b/examples/full/frosting/greetings/greetings.go @@ -0,0 +1,32 @@ +package greetings + +import ( + "context" + "fmt" + "github.com/cakehappens/frosting" +) + +const ( + Hello = "hello" + GoodBye = "goodbye" +) + +func NewHelloIngredient() *frosting.Ingredient { + return &frosting.Ingredient{ + Name: Hello, + RunFn: func(ctx context.Context) error { + fmt.Println("Hello World...") + return nil + }, + } +} + +func NewGoodByeIngredient() *frosting.Ingredient { + return &frosting.Ingredient{ + Name: GoodBye, + RunFn: func(ctx context.Context) error { + fmt.Println("GoodBye World...") + return nil + }, + } +} diff --git a/examples/full/frosting/greetings/hello.go b/examples/full/frosting/greetings/hello.go deleted file mode 100644 index 082f5b3..0000000 --- a/examples/full/frosting/greetings/hello.go +++ /dev/null @@ -1,34 +0,0 @@ -package greetings - -import ( - "fmt" - fr "github.com/cakehappens/frosting" -) - -func Hello() *fr.IngredientInfo { - return fr.MustNewIngredientInfo( - "hello", - func(ing *fr.IngredientInfo) { - ing.Fn = func() error { - fmt.Println("Hello World...") - return nil - } - }, - ) -} - -func Goodbye() *fr.IngredientInfo { - return fr.MustNewIngredientInfo( - "goodbye", - func(ing *fr.IngredientInfo) { - ing.AddDependencies( - Hello, - ) - - ing.Fn = func() error { - fmt.Println("Goodbye World...") - return nil - } - }, - ) -} diff --git a/examples/full/frosting/greetings/namespace.go b/examples/full/frosting/greetings/namespace.go deleted file mode 100644 index 7e51842..0000000 --- a/examples/full/frosting/greetings/namespace.go +++ /dev/null @@ -1,13 +0,0 @@ -package greetings - -import ( - fr "github.com/cakehappens/frosting" -) - -var GreetingsNamespace = fr.MustNewNamespace( - "greetings", - []fr.IngredientFn{ - Hello, - Goodbye, - }, -) diff --git a/examples/full/frosting/main.go b/examples/full/frosting/main.go index 0247907..a301c13 100644 --- a/examples/full/frosting/main.go +++ b/examples/full/frosting/main.go @@ -1,28 +1,48 @@ package main import ( + "context" "fmt" - fr "github.com/cakehappens/frosting" - "github.com/cakehappens/frosting/examples/full/frosting/greetings" - "os" + "github.com/cakehappens/frosting" ) -func Build() *fr.IngredientInfo { - return fr.MustNewIngredientInfo( - "build", - func(ing *fr.IngredientInfo) { - ing.Fn = func() error { - fmt.Println("Building...") - return nil - } +func NewBuildIngredient() *frosting.Ingredient { + ing := &frosting.Ingredient{ + Name: "build", + RunFn: func(ctx context.Context) error { + fmt.Println("Building...") + return nil }, + } + + ing.MustSetDependencies( + NewTestIngredient(), ) + + return ing +} + +func NewTestIngredient() *frosting.Ingredient { + return &frosting.Ingredient{ + Name: "test", + RunFn: func(ctx context.Context) error { + fmt.Println("Testing...") + return nil + }, + } } func main() { - client := fr.MustNew( - greetings.GreetingsNamespace, + f := frosting.New("fr") + f.MustAddIngredientGroups( + &frosting.IngredientGroup{ + Header: "stuff", + Namespace: "", + Ingredients: []*frosting.Ingredient{ + NewBuildIngredient(), + }, + }, ) - client.RootNamespace().MustAddIngredients(Build) - client.Execute(os.Args[1:]...) + + f.Execute("foo") } diff --git a/examples/full/frosting/main_test.go b/examples/full/frosting/main_test.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/examples/full/frosting/main_test.go @@ -0,0 +1 @@ +package main diff --git a/frosting.go b/frosting.go index 1132d36..a053c97 100644 --- a/frosting.go +++ b/frosting.go @@ -1,74 +1,120 @@ package frosting -import "fmt" +import ( + "context" + "errors" + "fmt" + "github.com/spf13/cobra" + "os" + "os/signal" + //"reflect" + //"runtime" + //"strings" + "syscall" + + "github.com/fatih/color" + "github.com/oklog/run" + //"gopkg.in/eapache/queue.v1" +) type ClientInfo struct { - rootNamespace *NamespaceInfo - ingredientFns []IngredientFn + defaultIngredient *Ingredient + ingredientGroups []*IngredientGroup } -type Client interface { - Execute(args ...string) - RootNamespace() *NamespaceInfo +func New(binaryName string) *ClientInfo { + return &ClientInfo{ + defaultIngredient: &Ingredient{ + cobraCommand: &cobra.Command{ + Use: binaryName, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + fmt.Println("no args.. running help...") + runHelp(cmd, args) + return nil + } + + fmt.Printf("running: %s\n", args) + return nil + }, + }, + }, + } } -func (c *ClientInfo) Execute(args ...string) { - fmt.Printf("resolving namespaces...\n") - namespaces, err := resolveNamespaces(c.rootNamespace) - if err != nil { - panic(fmt.Errorf("unable to resolve namespaces: %w", err)) - } - fmt.Printf("%d namespaces found...\n", len(namespaces)) +func runHelp(cmd *cobra.Command, args []string) { + cmd.Help() +} - fmt.Printf("gathering ingredients...\n") - ingredients := gatherIngredients(namespaces) - fmt.Printf("%d ingredients found...\n", len(ingredients)) +func (c *ClientInfo) MustAddIngredientGroups(ingGrps ...*IngredientGroup) { + ingGrpNsMap := make(map[string]bool) + ingGrpHeaderMap := make(map[string]bool) - resolver, err := newIngredientDependencyResolver(ingredients) - if err != nil { - panic(fmt.Errorf("unable to create ingredient resolver: %w", err)) - } + for _, ingGrp := range ingGrps { + if ingGrp.Header == "" { + panic(fmt.Errorf("ingredient groups must have a non-empty header. found empty header for: %+v", ingGrp)) + } - fmt.Printf("resolving ingredients...\n") - ingredientsResolved, err := resolver.Resolve() - if err != nil { - panic(fmt.Errorf("unable to resolve ingredients list and dependencies: %w", err)) - } - fmt.Printf("%d resolved ingredients...\n", len(ingredientsResolved)) + if ingGrpNsMap[ingGrp.Namespace] { + panic(fmt.Errorf("duplicate namespace: %s", ingGrp.Namespace)) + } + + if ingGrpHeaderMap[ingGrp.Header] { + panic(fmt.Errorf("duplicate header: %s", ingGrp.Header)) + } - for _, ing := range ingredientsResolved { - fmt.Printf("%s:%s\n", ing.namespace.name, ing.Name) + ingGrpNsMap[ingGrp.Namespace] = true + ingGrpHeaderMap[ingGrp.Header] = true + c.ingredientGroups = append(c.ingredientGroups, ingGrp) } } -func gatherIngredients(namespaces []*NamespaceInfo) []*IngredientInfo { - ingredients := []*IngredientInfo{} - - for _, ns := range namespaces { - for _, ingFn := range ns.ingredientFns { - ingredient := ingFn() - - // set where we found the ingredient - ingredient.namespace = ns - ingredients = append(ingredients, ingredient) - } +func (c *ClientInfo) Execute(args ...string) { + ctx, cancel := context.WithCancel(context.Background()) + + runGroup := run.Group{} + { + cancelInterrupt := make(chan struct{}) + runGroup.Add( + createSignalWatcher(ctx, cancelInterrupt, cancel), + func(error) { + close(cancelInterrupt) + }) + } + { + runGroup.Add(func() error { + return c.defaultIngredient.cobraCommand.ExecuteContext(ctx) + }, func(error) { + cancel() + }) } - return ingredients -} + err := runGroup.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "exit reason: %s\n", err) + os.Exit(1) + } -func (c *ClientInfo) RootNamespace() *NamespaceInfo { - return c.rootNamespace + color.New(color.FgGreen).Fprintln(os.Stderr, "Done!") } -var _ Client = &ClientInfo{} - -func MustNew(namespaces ...*NamespaceInfo) *ClientInfo { - rootNs := MustNewNamespace("root", []IngredientFn{}, namespaces...) - - c := &ClientInfo{ - rootNamespace: rootNs, +// This function just sits and waits for ctrl-C +func createSignalWatcher(ctx context.Context, cancelInterruptChan <-chan struct{}, cancel context.CancelFunc) func() error { + return func() error { + c := make(chan os.Signal, 1) + + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + select { + case sig := <-c: + err := errors.New(fmt.Sprintf("received signal %s", sig)) + fmt.Fprintf(os.Stderr, "%s\n", err) + signal.Stop(c) + cancel() + return err + case <-ctx.Done(): + return nil + case <-cancelInterruptChan: + return nil + } } - - return c } diff --git a/go.mod b/go.mod index 3849152..e502961 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,13 @@ go 1.14 require ( github.com/deckarep/golang-set v1.7.1 + github.com/fatih/color v1.9.0 github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 + github.com/oklog/run v1.1.0 github.com/oklog/ulid v1.3.1 github.com/oklog/ulid/v2 v2.0.2 + github.com/spf13/cobra v0.0.7 + github.com/stretchr/testify v1.5.1 + golang.org/x/tools v0.0.0-20200331202046-9d5940d49312 // indirect gopkg.in/eapache/queue.v1 v1.1.0 ) diff --git a/go.sum b/go.sum index 73223eb..5f84e22 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,169 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +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/go-semver v0.2.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/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +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/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/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/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_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/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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/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/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +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-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-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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312 h1:2PHG+Ia3gK1K2kjxZnSylizb//eyaMG8gDFbOG7wLV8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc= gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/ingredient.go b/ingredient.go new file mode 100644 index 0000000..872d688 --- /dev/null +++ b/ingredient.go @@ -0,0 +1,58 @@ +package frosting + +import ( + "context" + "errors" + "fmt" + "github.com/spf13/cobra" + "gopkg.in/eapache/queue.v1" +) + +type RunFn func(ctx context.Context) error + +type Ingredient struct { + Name string + RunFn RunFn + Dependencies func() []*Ingredient + + cobraCommand *cobra.Command +} + +func (ing *Ingredient) MustSetDependencies(deps ...*Ingredient) { + for _, dep := range deps { + if dep == ing { + panic(errors.New("ingredient cannot be a dependency of itself")) + } + } + + ing.Dependencies = func() []*Ingredient { + return deps + } +} + +// this probably only is needed from root ingredients +func (ing *Ingredient) resolveAllDeps() ([]*Ingredient, error) { + var deps []*Ingredient + visited := make(map[string]bool) + + ingQueue := queue.New() + + ingQueue.Add(ing) + + for ingQueue.Length() > 0 { + dep := ingQueue.Remove().(*Ingredient) + + if visited[dep.Name] { + return nil, fmt.Errorf("found circular reference via: %s", dep.Name) + } + + deps = append(deps, dep) + visited[dep.Name] = true + + for _, d := range dep.Dependencies() { + ingQueue.Add(d) + } + } + + return deps, nil +} diff --git a/ingredient_test.go b/ingredient_test.go new file mode 100644 index 0000000..305450f --- /dev/null +++ b/ingredient_test.go @@ -0,0 +1,86 @@ +package frosting + +import ( + "github.com/stretchr/testify/assert" + "reflect" + "testing" +) + +func TestMustNewIngredientInfo(t *testing.T) { + type args struct { + name string + options []func(ing *IngredientInfo) + } + doesNotPanicTests := []struct { + name string + args args + want *IngredientInfo + }{ + { + name: "name provided no options", + args: args{ + name: "test", + }, + }, + { + name: "nil options", + args: args{ + name: "test", + options: nil, + }, + }, + { + name: "empty options", + args: args{ + name: "test", + options: []func(ing *IngredientInfo){}, + }, + }, + { + name: "options do nothing", + args: args{ + name: "test", + options: []func(ing *IngredientInfo){ + func(ing *IngredientInfo) { + + }, + }, + }, + }, + } + for _, tt := range doesNotPanicTests { + t.Run(tt.name, func(t *testing.T) { + assert.NotPanics( + t, + func() { + if got := MustNewIngredientInfo(tt.args.name, tt.args.options...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MustNewIngredientInfo() = %v, want %v", got, tt.want) + } + }, + ) + }) + } + + panicTests := []struct { + name string + args args + want *IngredientInfo + }{ + { + name: "name is empty", + args: args{ + name: "", + }, + }, + } + for _, tt := range panicTests { + t.Run(tt.name, func(t *testing.T) { + assert.Panics( + t, + func() { + MustNewIngredientInfo(tt.args.name, tt.args.options...) + }, + ) + }) + } +} diff --git a/ingredientgroup.go b/ingredientgroup.go new file mode 100644 index 0000000..951e9f2 --- /dev/null +++ b/ingredientgroup.go @@ -0,0 +1,7 @@ +package frosting + +type IngredientGroup struct { + Header string + Namespace string + Ingredients []*Ingredient +} diff --git a/ingredients.go b/ingredients.go deleted file mode 100644 index 55e3b41..0000000 --- a/ingredients.go +++ /dev/null @@ -1,68 +0,0 @@ -package frosting - -import ( - "errors" - - mapset "github.com/deckarep/golang-set" -) - -type IngredientFn func() *IngredientInfo - -type IngredientInfo struct { - Name string - Fn func() error - - namespace *NamespaceInfo - id string - dependencyFns []IngredientFn - - // set of IngredientInfo's - dependencies mapset.Set - currentDependencies mapset.Set -} - -func (ing *IngredientInfo) AddDependencies(fns ...IngredientFn) { - for _, fn := range fns { - ing.dependencyFns = append(ing.dependencyFns, fn) - } -} - -func MustNewIngredientInfo(name string, options ...func(ing *IngredientInfo)) *IngredientInfo { - if name == "" { - panic(errors.New("name cannot be empty")) - } - - ing := &IngredientInfo{ - Name: name, - id: newULID(), - dependencies: mapset.NewSet(), - } - - for _, fn := range options { - fn(ing) - } - - if ing.Fn == nil { - ing.Fn = func() error { - return nil - } - } - - ing.currentDependencies = ing.dependencies.Clone() - - return ing -} - -type Ingredient interface { - Satisfied() bool -} - -func (ing *IngredientInfo) Satisfied() bool { - if ing.currentDependencies.Cardinality() == 0 { - return true - } - - return false -} - -var _ Ingredient = &IngredientInfo{} diff --git a/ingredients_test.go b/ingredients_test.go deleted file mode 100644 index 9ed2a3b..0000000 --- a/ingredients_test.go +++ /dev/null @@ -1 +0,0 @@ -package frosting diff --git a/namespaces.go b/namespaces.go deleted file mode 100644 index 3102255..0000000 --- a/namespaces.go +++ /dev/null @@ -1,111 +0,0 @@ -package frosting - -import ( - "errors" - "gopkg.in/eapache/queue.v1" - - mapset "github.com/deckarep/golang-set" -) - -type Namespace interface { - Name() string - ID() string - Parent() *NamespaceInfo - Children() []*NamespaceInfo - MustAddIngredients(ingFns ...IngredientFn) -} - -type NamespaceInfo struct { - name string - id string - parent *NamespaceInfo - children mapset.Set - ingredientFns []IngredientFn -} - -func (n *NamespaceInfo) Name() string { - return n.name -} - -func (n *NamespaceInfo) Parent() *NamespaceInfo { - return n.parent -} - -func (n *NamespaceInfo) Children() []*NamespaceInfo { - children := []*NamespaceInfo{} - for child := range n.children.Iter() { - children = append(children, child.(*NamespaceInfo)) - } - - return children -} - -func (n *NamespaceInfo) MustAddIngredients(ing ...IngredientFn) { - for _, ingFn := range ing { - if ingFn == nil { - panic(errors.New("ingredient cannot be nil")) - } - - n.ingredientFns = append(n.ingredientFns, ingFn) - } -} - -func (n *NamespaceInfo) ID() string { - return n.id -} - -var _ Namespace = &NamespaceInfo{} - -func MustNewNamespace(name string, ingredientFns []IngredientFn, children ...*NamespaceInfo) *NamespaceInfo { - if name == "" { - panic(errors.New("namespace name cannot be empty")) - } - - if ingredientFns == nil { - ingredientFns = []IngredientFn{} - } - - n := &NamespaceInfo{ - name: name, - id: newULID(), - ingredientFns: ingredientFns, - children: mapset.NewSet(), - } - - for _, child := range children { - n.children.Add(child) - } - - return n -} - -func resolveNamespaces(root *NamespaceInfo) ([]*NamespaceInfo, error) { - namespaces := []*NamespaceInfo{} - - visited := make(map[string]bool) - nsQueue := queue.New() - - visited[root.ID()] = true - namespaces = append(namespaces, root) - - for _, ns := range root.Children() { - nsQueue.Add(ns) - } - - for nsQueue.Length() > 0 { - ns := nsQueue.Remove().(*NamespaceInfo) - - if _, ok := visited[ns.id]; ok { - return nil, errors.New("found circular namespace reference") - } - - namespaces = append(namespaces, ns) - visited[ns.id] = true - - for _, ns := range ns.Children() { - nsQueue.Add(ns) - } - } - - return namespaces, nil -} diff --git a/namespaces_test.go b/namespaces_test.go deleted file mode 100644 index 9ed2a3b..0000000 --- a/namespaces_test.go +++ /dev/null @@ -1 +0,0 @@ -package frosting diff --git a/resolver.go b/resolver.go index 3b38635..f7af6f9 100644 --- a/resolver.go +++ b/resolver.go @@ -1,101 +1,101 @@ package frosting -import ( - "errors" - "fmt" - - mapset "github.com/deckarep/golang-set" -) - -type IngredientGraph []*IngredientInfo - -type IngredientDependencyResolver interface { - Resolve() (IngredientGraph, error) - CurrentReady() IngredientGraph -} - -type ingredientDependencyResolver struct { - ingredients map[string]*IngredientInfo - resolved IngredientGraph -} - -func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDependencyResolver, error) { - r := &ingredientDependencyResolver{} - - r.ingredients = make(map[string]*IngredientInfo) - r.resolved = IngredientGraph{} - - for _, ing := range ingredients { - if ing.id == "" { - return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. Name: %s", ing.Name) - } - r.ingredients[ing.id] = ing - } - - return r, nil -} - -func (r *ingredientDependencyResolver) Resolve() (IngredientGraph, error) { - // if we've already resolved, don't re-resolve - if len(r.resolved) > 0 { - return r.resolved, nil - } - - ingredientDependencies := make(map[string]mapset.Set) - - // Populate the map - for _, ing := range r.ingredients { - ingredientDependencies[ing.id] = ing.dependencies.Clone() - } - - // Iteratively find and remove nodes from the graph which have no dependencies. - // If at some point there are still nodes in the graph and we cannot find - // nodes without dependencies, that means we have a circular dependency - for len(ingredientDependencies) != 0 { - // Get all nodes from the graph which have no dependencies - readySet := mapset.NewSet() - for id, deps := range ingredientDependencies { - if deps.Cardinality() == 0 { - readySet.Add(id) - } - } - - // If there aren't any ready nodes, then we have a circular dependency - if readySet.Cardinality() == 0 { - var g IngredientGraph - for id := range ingredientDependencies { - g = append(g, r.ingredients[id]) - } - - return g, errors.New("circular dependency found") - } - - // Remove the ready nodes and add them to the resolved graph - for id := range readySet.Iter() { - delete(ingredientDependencies, id.(string)) - r.resolved = append(r.resolved, r.ingredients[id.(string)]) - } - - // Also make sure to remove the ready nodes from the - // remaining node dependencies as well - for id, deps := range ingredientDependencies { - diff := deps.Difference(readySet) - ingredientDependencies[id] = diff - } - } - - return r.resolved, nil -} - -func (r *ingredientDependencyResolver) CurrentReady() IngredientGraph { - currentReady := IngredientGraph{} - for _, ing := range r.resolved { - if ing.currentDependencies.Cardinality() == 0 { - currentReady = append(currentReady, ing) - } - } - - return currentReady -} - -var _ IngredientDependencyResolver = &ingredientDependencyResolver{} +//import ( +// "errors" +// "fmt" +// +// mapset "github.com/deckarep/golang-set" +//) +// +//type IngredientGraph []Ingredient +// +//type IngredientDependencyResolver interface { +// Resolve() (IngredientGraph, error) +// CurrentReady() IngredientGraph +//} +// +//type ingredientDependencyResolver struct { +// ingredients map[string]*IngredientInfo +// resolved IngredientGraph +//} +// +//func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDependencyResolver, error) { +// r := &ingredientDependencyResolver{} +// +// r.ingredients = make(map[string]*IngredientInfo) +// r.resolved = IngredientGraph{} +// +// for _, ing := range ingredients { +// if ing.id == "" { +// return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. name: %s", ing.name) +// } +// r.ingredients[ing.id] = ing +// } +// +// return r, nil +//} +// +//func (r *ingredientDependencyResolver) Resolve() (IngredientGraph, error) { +// // if we've already resolved, don't re-resolve +// if len(r.resolved) > 0 { +// return r.resolved, nil +// } +// +// ingredientDependencies := make(map[string]mapset.Set) +// +// // Populate the map +// for _, ing := range r.ingredients { +// ingredientDependencies[ing.id] = ing.dependencies.Clone() +// } +// +// // Iteratively find and remove nodes from the graph which have no dependencies. +// // If at some point there are still nodes in the graph and we cannot find +// // nodes without dependencies, that means we have a circular dependency +// for len(ingredientDependencies) != 0 { +// // Get all nodes from the graph which have no dependencies +// readySet := mapset.NewSet() +// for id, deps := range ingredientDependencies { +// if deps.Cardinality() == 0 { +// readySet.Add(id) +// } +// } +// +// // If there aren't any ready nodes, then we have a circular dependency +// if readySet.Cardinality() == 0 { +// var g IngredientGraph +// for id := range ingredientDependencies { +// g = append(g, r.ingredients[id]) +// } +// +// return g, errors.New("circular dependency found") +// } +// +// // Remove the ready nodes and add them to the resolved graph +// for id := range readySet.Iter() { +// delete(ingredientDependencies, id.(string)) +// r.resolved = append(r.resolved, r.ingredients[id.(string)]) +// } +// +// // Also make sure to remove the ready nodes from the +// // remaining node dependencies as well +// for id, deps := range ingredientDependencies { +// diff := deps.Difference(readySet) +// ingredientDependencies[id] = diff +// } +// } +// +// return r.resolved, nil +//} +// +//func (r *ingredientDependencyResolver) CurrentReady() IngredientGraph { +// currentReady := IngredientGraph{} +// for _, ing := range r.resolved { +// if ing.currentDependencies.Cardinality() == 0 { +// currentReady = append(currentReady, ing) +// } +// } +// +// return currentReady +//} +// +//var _ IngredientDependencyResolver = &ingredientDependencyResolver{} diff --git a/sh/shell.go b/sh/shell.go new file mode 100644 index 0000000..e82e294 --- /dev/null +++ b/sh/shell.go @@ -0,0 +1,138 @@ +package sh + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" +) + +type Waiter func(cmd *exec.Cmd, stdout, stderr io.ReadCloser, stdin io.WriteCloser) error + +type WritePipeHandler func(writer io.WriteCloser) error + +type ReadPipeHandler func(reader io.ReadCloser) error + +// For use when y +type MultiReadPipeHandler func(readers ...io.ReadCloser) error + +type RunOptions struct { + Args []string + Env map[string]string + Waiter Waiter +} + +type OptionFn func(opts *RunOptions) + +// Merges os.Environment variables to Env map passed to command +func OptionWithOsEnv(opts *RunOptions) error { + for _, item := range os.Environ() { + split := strings.SplitN(item, "=", 2) + opts.Env[split[0]] = split[1] + } + return nil +} + +// Run provides an easy to use API on top of RunLow +// By default, nothing is done to stdout, stderr, stdin +// so if you want to consume them, you must bind to them using Waiter func +// If Waiter remains unset, it defaults to cmd.Wait() +func Run(ctx context.Context, cmd string, options ...OptionFn) error { + runOpts := &RunOptions{} + + for _, optFn := range options { + optFn(runOpts) + } + + if runOpts.Waiter == nil { + runOpts.Waiter = func(cmd *exec.Cmd, stdout, stderr io.ReadCloser, stdin io.WriteCloser) error { + return cmd.Wait() + } + } + + if runOpts.Env == nil { + runOpts.Env = make(map[string]string) + } + + if runOpts.Args == nil { + runOpts.Args = make([]string, 0) + } + + cmdEnv := make([]string, len(runOpts.Env)) + + for k, v := range runOpts.Env { + cmdEnv = append(cmdEnv, fmt.Sprintf("%s=%s", k, v)) + } + + code, err, ran := RunLow(ctx, cmdEnv, runOpts.Waiter, cmd, runOpts.Args...) + + if !ran { + return fmt.Errorf("command failed to run: %w", err) + } + + if err != nil { + return fmt.Errorf("command failed with status code %d: %w", code, err) + } + + return nil +} + +// Run is the lowest level function to execute an external command +// since this function uses cmd.Start, it does not natively block +// you must provide that functionality +// No environment variables are explicitly passed to the command besides those provided +// Environment variables provided must be in the form of KEY=VALUE +func RunLow( + ctx context.Context, + env []string, + waiter Waiter, + cmd string, + args ...string, + ) (code int, err error, ran bool) { + + c := exec.CommandContext(ctx, cmd, args...) + c.Env = env + + stdOutPipe, err := c.StdoutPipe() + if err != nil { + return -1, fmt.Errorf("could not create stdout pipe for cmd: %w", err), false + } + + stdErrPipe, err := c.StderrPipe() + if err != nil { + return -1, fmt.Errorf("could not create stderr pipe for cmd: %w", err), false + } + + stdInPipe, err := c.StdinPipe() + if err != nil { + return -1, fmt.Errorf("could not create stdin pipe for cmd: %w", err), false + } + + err = c.Start() + if err != nil { + return -1, err, false + } + + done := make(chan struct{}) + go func() { + + done <- struct{}{} + }() + <-done + + c.Wait() + + err = waiter(c, stdOutPipe, stdErrPipe, stdInPipe) + + if err == nil { + return 0, nil, true + } + + if err, ok := err.(*exec.ExitError); ok { + return err.ExitCode(), err, true + } + + return -1, err, false +} diff --git a/utils.go b/utils.go index d31f1be..da2a1bd 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package frosting import ( "github.com/oklog/ulid/v2" "math/rand" + "runtime" "time" ) @@ -11,3 +12,32 @@ func newULID() string { entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) return ulid.MustNew(ulid.Timestamp(t), entropy).String() } + +func getFrame(skipFrames int) runtime.Frame { + // We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame + targetFrameIndex := skipFrames + 2 + + // Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need + programCounters := make([]uintptr, targetFrameIndex+2) + n := runtime.Callers(0, programCounters) + + frame := runtime.Frame{Function: "unknown"} + if n > 0 { + frames := runtime.CallersFrames(programCounters[:n]) + for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ { + var frameCandidate runtime.Frame + frameCandidate, more = frames.Next() + if frameIndex == targetFrameIndex { + frame = frameCandidate + } + } + } + + return frame +} + +// MyCaller returns the caller of the function that called it :) +func myCaller() string { + // Skip GetCallerFunctionName and the function to get the caller of + return getFrame(2).Function +} From 7dbca481429bc07c32a843749ead48c529df2a81 Mon Sep 17 00:00:00 2001 From: Weston McNamee Date: Fri, 3 Apr 2020 19:55:30 -0700 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=E2=9C=A8=20=20add=20k8s=20comm?= =?UTF-8?q?and=20templates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frosting.go | 40 ++++++---- go.mod | 1 + go.sum | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 14 deletions(-) diff --git a/frosting.go b/frosting.go index a053c97..467175d 100644 --- a/frosting.go +++ b/frosting.go @@ -15,6 +15,7 @@ import ( "github.com/fatih/color" "github.com/oklog/run" //"gopkg.in/eapache/queue.v1" + "k8s.io/kubectl/pkg/util/templates" ) type ClientInfo struct { @@ -22,22 +23,31 @@ type ClientInfo struct { ingredientGroups []*IngredientGroup } +func newRootCommand(binaryName string) *cobra.Command { + cmd := &cobra.Command{ + Use: binaryName, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + runHelp(cmd, args) + return nil + } + + fmt.Printf("running: %s\n", args) + return nil + }, + } + + groups := templates.CommandGroups{} + + cmd.AddCommand() + + return cmd +} + func New(binaryName string) *ClientInfo { return &ClientInfo{ defaultIngredient: &Ingredient{ - cobraCommand: &cobra.Command{ - Use: binaryName, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - fmt.Println("no args.. running help...") - runHelp(cmd, args) - return nil - } - - fmt.Printf("running: %s\n", args) - return nil - }, - }, + cobraCommand: newRootCommand(binaryName), }, } } @@ -83,7 +93,9 @@ func (c *ClientInfo) Execute(args ...string) { } { runGroup.Add(func() error { - return c.defaultIngredient.cobraCommand.ExecuteContext(ctx) + rootC := c.defaultIngredient.cobraCommand + rootC.SetArgs(os.Args[1:]) + return rootC.ExecuteContext(ctx) }, func(error) { cancel() }) diff --git a/go.mod b/go.mod index e502961..657739e 100644 --- a/go.mod +++ b/go.mod @@ -13,4 +13,5 @@ require ( github.com/stretchr/testify v1.5.1 golang.org/x/tools v0.0.0-20200331202046-9d5940d49312 // indirect gopkg.in/eapache/queue.v1 v1.1.0 + k8s.io/kubectl v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index 5f84e22..ee184be 100644 --- a/go.sum +++ b/go.sum @@ -1,61 +1,146 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +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/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +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/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +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/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +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.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/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/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/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/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-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/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 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 h1:w3NnFcKR5241cfmQU5ZZAsf0xcpId6mWOupTvJlUX2U= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +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.2.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.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 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/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +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.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= 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/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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/konsorten/go-windows-terminal-sequences v1.0.1/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/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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/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/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -63,97 +148,181 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe 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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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= +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/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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +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.11.0/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/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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/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/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/client_model v0.2.0/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/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +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/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.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 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-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-20190611184440-5c40567a22f8/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 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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/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-20181114220301-adae6a3d119a/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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/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-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-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-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-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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/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/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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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-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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/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-20200331202046-9d5940d49312 h1:2PHG+Ia3gK1K2kjxZnSylizb//eyaMG8gDFbOG7wLV8= golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 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/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -161,9 +330,47 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc= gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +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= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= +k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= +k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4= +k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +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/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kubectl v0.18.0 h1:hu52Ndq/d099YW+3sS3VARxFz61Wheiq8K9S7oa82Dk= +k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= +k8s.io/kubernetes v1.18.0 h1:rVe+edi5GwutPQJ4KIZq1Nk506nmnfyz/KOZVCLv7Yo= +k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +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= From 4d6533fbbd2ee684627629bc041a6c3311b98307 Mon Sep 17 00:00:00 2001 From: Wes McNamee Date: Thu, 9 Apr 2020 09:09:16 -0700 Subject: [PATCH 5/6] iterating... --- README.md | 176 ++++++++++++++---- docs/README.md | 1 + docs/getting-started.md | 99 ++++++++++ examples/full/.gitignore | 1 + examples/full/README.md | 2 + examples/full/frosting/greetings/greetings.go | 32 ---- examples/full/frosting/main.go | 40 ++-- frosting.go | 102 ++++++---- go.mod | 9 +- go.sum | 25 +-- ingredient.go | 96 ++++++---- ingredientgroup.go | 35 +++- resolver.go | 173 ++++++++--------- test/helpers.go | 4 + utils.go => util/util.go | 13 +- 15 files changed, 519 insertions(+), 289 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/getting-started.md delete mode 100644 examples/full/frosting/greetings/greetings.go create mode 100644 test/helpers.go rename utils.go => util/util.go (77%) diff --git a/README.md b/README.md index d39f0fc..36edb2e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@


- playing card + playing card
Frosting
@@ -10,11 +10,17 @@

Enhance the operational tasks of your application with a little frosting 🧁

+ + + + - + - - + +

@@ -28,70 +34,158 @@ License

-## Introduction - -`frost` is a workflow tool. Inspired by [Make](https://www.gnu.org/software/make/), [Mage](https://magefile.org/), [Task](https://taskfile.dev/) and others, but with some important differences. +## 👋 Introduction -| Feature | Frost | Make | Mage | Go-Task | -|----------------------|-------|------|------|---------| -| Fast | ✔️ | ✔️ | ✔️ | ✔️ | -| Bash Support | ✔️ | ✔️ | ✔️ | ✔️ | -| Target Flags | ✔️ | ✔️§ | ✔️§ | ✔️§§ | -| Autocomplete | ✔️ | ✔️ | | ✔️ | -| Parallelism | ✔️ | | ✔️† | | -| Imports (Namespaced) | ✔️ | ✔️ | ✔️‡ | ✔️‡ | -| *File | Go | Make | Go | Yaml | +`frosting` is library that lets you quickly and easily create a CLI for your code repositories (like how a `Makefile` enables you to run `make build`). Inspired by [Make][make], [Mage][mage], [Task][taskfile] and others. -§ Target behavior can only be modified with environment variables +## 🎯 Features -§§ Target behavior can be modified with environment and can also can be templated with go templating prior to evaluation and execution. +| Feature | Frost | Make | Mage | Go-Task | +|----------------------------------|-------|------|------|---------| +| *File | Go | Make | Go | Yaml | +| Bash Support | 🧁 | 🐮 | 🧙 | 🐹 | +| Target-Specific Vars | 🧁 | 🐮 | 🧙 | 🐹 | +| Namespaces | 🧁 | 🐮 | | 🐹 | +| Imported Targets | 🧁 | 🐮 | 🧙 | 🐹‡ | +| Bash/Zsh Autocomplete | 🧁 | 🐮 | | 🐹 | +| Parallelism | 🧁 | | 🧙† | | +| No Custom DSL to Learn | 🧁 | | 🧙 | | +| Target Args | 🧁 | | | | +| Target Flags | 🧁 | | | | +| Target-Specific Help | 🧁 | | | | +| [Color Support][color] | 🧁 | | | | +| Doc Generation | 🧁 | | | | +| [Interative Terminal UI][tview] | 🧁 | | | | +| [go-prompt][gpt] Integration | 🧁 | | | | +| [Progress Bars][pb] | 🧁 | | | | +| [Spinners][spin] | 🧁 | | | | -† Doesn't support limiting of parallelism ([#273](https://github.com/magefile/mage/pull/273)) +† Yes, but with some limitations ([#273](https://github.com/magefile/mage/pull/273)) -‡ Mage doesn't currently support nested namespaces ([#152](https://github.com/magefile/mage/issues/152)), and Task support for imports is still experimental +‡ Task support for imports is still experimental ---- +## 💡 Philosophy -Make is fast, but for any sort of complex logic, I found myself writing bash scripts, and I agree with Mage in regards to make/bash: +Mage puts it nicely in regards to make/bash: > Makefiles are hard to read and hard to write. Mostly because makefiles are essentially fancy bash scripts with significant white space and additional make-related syntax. Go is superior to bash for any non-trivial task involving branching, looping, anything that’s not just straight line execution of commands. Mage makes heavy use of code generation in order to create the resulting binary, and I found that to be a high barrier to entry for contributing to the project. -I've been itching to write my own CLI for awhile now, and I think I finally have a reason to. +I've been itching to write my own CLI for awhile now, and I think I finally landed on a great use case. -## Install +## ⚡️ Quickstart ```go +package main + +import ( + "context" + "fmt" + "github.com/cakehappens/frosting" + "github.com/cakehappens/frosting/ingredient" +) + +func NewBuildIngredient() *ingredient.Ingredient { + return ingredient.MustNew( + "build", + func(ctx context.Context) error { + fmt.Println("Building...") + return nil + }, + ingredient.WithDependencies(NewTestIngredient), + ) +} + +func NewTestIngredient() *ingredient.Ingredient { + return ingredient.MustNew( + "test", + func(ctx context.Context) error { + fmt.Println("Testing...") + return nil + }, + ) +} + +func main() { + f := frosting.New("frost") + f.MustAddIngredientGroups( + ingredient.MustNewGroup( + "", + "Main Stuff:", + ingredient.Includes( + NewBuildIngredient, + NewTestIngredient, + ), + ), + ) + + f.Execute("foo") +} ``` -## How To Use - ```bash - +go build -o frost +frost # prints help +frost build # runs build ingredient +frost build --help # prints build-target help ``` -## Credits +## 🎓 Docs -- spf13/cobra +see [docs](docs) for more info, or look at the [godocs](https://pkg.go.dev/github.com/cakehappens/frosting) -### Reading +## 👀 Examples +see [examples](examples) -- https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables -- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis -- https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully -- https://dave.cheney.net/tag/logging - -## Support +## 🌟 Contribute -Buy Me A Coffee +I'll definitely get some templates/guidelines setup soon... -Paypal Donate +## 🤗 Support -_Icons made by [Freepik](https://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)_ + + + -## Related +## 📖 Reading -## License \ No newline at end of file +- https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables +- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis +- https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully +- https://dave.cheney.net/tag/logging +- https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run + +## 💕 Related + +- [Mage][mage] +- [Taskfile][taskfile] +- [Make][make] + +## 📜 Credits + +- [spf13/cobra][cobra] +- [spf13/viper][viper] +- [tivo/tview][tview] +- [theckman/yackspin][spin] +- [c-bata/go-prompt][gpt] +- [gosuri/uiprogress][pb] +- [fatih/color][color] +- Icons made by [Freepik](https://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/) + +## ⚖️ License + +[Apache License, Version 2.0, http://www.apache.org/licenses/](LICENSE) + +[make]: https://www.gnu.org/software/make/ +[taskfile]: https://taskfile.dev/ +[mage]: https://magefile.org/ +[cobra]: https://github.com/spf13/cobra +[viper]: https://github.com/spf13/viper +[gpt]: https://github.com/c-bata/go-prompt +[spin]: https://github.com/theckman/yacspin +[pb]: https://github.com/gosuri/uiprogress +[color]: https://github.com/fatih/color +[tview]: https://github.com/rivo/tview diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d0fa60e --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +# Docs diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..f203f9e --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,99 @@ +# Architecture + +## Quick Start + +`frosting` is made up of `Ingredient`s (pun intended). An ingredient is analogous to a `target` in a make file. + +### Library Code Example + +📌 _Let the consumer define the name of the target_ + +🏷️ _Ingredients are ** **globally unique by name** **, to allow different ingredients to have the same dependencies, but ensuring we don't call the dependent ingredient more than once._ + +🛑 _Don't perform any initialization logic within the function that returns the ingredient. This function may be called multiple times, and the resulting ingredient struct may be tossed out based on the globally unique rule above_ + +```go +func NewBuildIngredient(name) *ingredient.Ingredient { + return ingredient.MustNew( + name, + func(ctx context.Context) error { + fmt.Println("Building...") + return nil + }, + ) +} +``` + +Ingredients may be grouped as well, but regardless of group, they are still ** **globally unique by name** **. A group is only to assist when getting help. + +``` +$ frost help +Add a little frosting to your operational tasks.. + +Basic Commands (Beginner): + build Build Things + test Test Things +``` + +```go +func NewFooIngredientGroup() *frosting.IngredientGroup { + return ingredient.MustNewGroup( + "Basic Commands (Beginner):", + ingredient.Includes( + NewBuildIngredient("build"), + NewTestIngredient("test"), + ), + ), +} +``` + +### Declaring Dependencies + +The recommended way to define dependencies is to first declare the name of ingredient as a constant. Then use that constant when setting dependencies. + +```go + +const Build = "build" + +func NewBuildIngredient() *ingredient.Ingredient { + return ingredient.MustNew( + Build, + func(ctx context.Context) error { + fmt.Println("Building...") + return nil + }, + ingredient.WithDependency( + + ) + ) +} + +const Test = "test" + +func NewBuildIngredient(name) *ingredient.Ingredient { + return ingredient.MustNew( + name, + func(ctx context.Context) error { + fmt.Println("Building...") + return nil + }, + ingredient.WithDependencies( + Build + ) + ) +} +``` + +You may also declare dependencies that should be run serially. These dependencies will implicitly be made dependencies of each other. The following will result in ingredients running in this order: + +``` +A -> B -> C -> This +``` + +```go +ingredient.WithSerialDependencies( + A, + B, + C +) +``` diff --git a/examples/full/.gitignore b/examples/full/.gitignore index 6cc8981..0c498aa 100644 --- a/examples/full/.gitignore +++ b/examples/full/.gitignore @@ -19,3 +19,4 @@ # Frosting Binary /fr +/frost diff --git a/examples/full/README.md b/examples/full/README.md index f595947..60076c0 100644 --- a/examples/full/README.md +++ b/examples/full/README.md @@ -6,6 +6,8 @@ Lots of Bells and Whistles Imagine that this is the _root_ of your project repo. It doesn't matter what language, node, python, go, etc. +The recommended approach is to put all of your frosting in a subfolder, as to not interfere if you are building a go application. + ## Getting Started Compile into a binary diff --git a/examples/full/frosting/greetings/greetings.go b/examples/full/frosting/greetings/greetings.go deleted file mode 100644 index ea15102..0000000 --- a/examples/full/frosting/greetings/greetings.go +++ /dev/null @@ -1,32 +0,0 @@ -package greetings - -import ( - "context" - "fmt" - "github.com/cakehappens/frosting" -) - -const ( - Hello = "hello" - GoodBye = "goodbye" -) - -func NewHelloIngredient() *frosting.Ingredient { - return &frosting.Ingredient{ - Name: Hello, - RunFn: func(ctx context.Context) error { - fmt.Println("Hello World...") - return nil - }, - } -} - -func NewGoodByeIngredient() *frosting.Ingredient { - return &frosting.Ingredient{ - Name: GoodBye, - RunFn: func(ctx context.Context) error { - fmt.Println("GoodBye World...") - return nil - }, - } -} diff --git a/examples/full/frosting/main.go b/examples/full/frosting/main.go index a301c13..1c9ef11 100644 --- a/examples/full/frosting/main.go +++ b/examples/full/frosting/main.go @@ -6,42 +6,42 @@ import ( "github.com/cakehappens/frosting" ) +const Build = "build" + func NewBuildIngredient() *frosting.Ingredient { - ing := &frosting.Ingredient{ - Name: "build", - RunFn: func(ctx context.Context) error { + return frosting.MustNew( + Build, + func(ctx context.Context) error { fmt.Println("Building...") return nil }, - } - - ing.MustSetDependencies( - NewTestIngredient(), + //ingredient.WithDependencies(Test), + frosting.WithHelpDescriptions("buildShort", "buildLong"), ) - - return ing } +const Test = "test" + func NewTestIngredient() *frosting.Ingredient { - return &frosting.Ingredient{ - Name: "test", - RunFn: func(ctx context.Context) error { + return frosting.MustNew( + Test, + func(ctx context.Context) error { fmt.Println("Testing...") return nil }, - } + ) } func main() { - f := frosting.New("fr") + f := frosting.New("frost") f.MustAddIngredientGroups( - &frosting.IngredientGroup{ - Header: "stuff", - Namespace: "", - Ingredients: []*frosting.Ingredient{ + frosting.MustNewGroup( + "Main Stuff:", + frosting.Includes( NewBuildIngredient(), - }, - }, + NewTestIngredient(), + ), + ), ) f.Execute("foo") diff --git a/frosting.go b/frosting.go index 467175d..cba6335 100644 --- a/frosting.go +++ b/frosting.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" "os" "os/signal" //"reflect" @@ -14,41 +15,33 @@ import ( "github.com/fatih/color" "github.com/oklog/run" - //"gopkg.in/eapache/queue.v1" - "k8s.io/kubectl/pkg/util/templates" ) type ClientInfo struct { - defaultIngredient *Ingredient - ingredientGroups []*IngredientGroup + rootCommand *cobra.Command + ingredientGroups []*IngredientGroup + ingredients map[string]*Ingredient + commands map[string]*cobra.Command + commandsPerIngredientGroup map[*IngredientGroup][]*cobra.Command } func newRootCommand(binaryName string) *cobra.Command { cmd := &cobra.Command{ - Use: binaryName, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - runHelp(cmd, args) - return nil - } - - fmt.Printf("running: %s\n", args) - return nil - }, + Use: binaryName, + Short: "Frosting!", + Long: "CakeHappens/Frosting!", + Run: runHelp, } - groups := templates.CommandGroups{} - - cmd.AddCommand() - return cmd } func New(binaryName string) *ClientInfo { return &ClientInfo{ - defaultIngredient: &Ingredient{ - cobraCommand: newRootCommand(binaryName), - }, + rootCommand: newRootCommand(binaryName), + ingredients: make(map[string]*Ingredient), + commands: make(map[string]*cobra.Command), + commandsPerIngredientGroup: make(map[*IngredientGroup][]*cobra.Command), } } @@ -57,25 +50,51 @@ func runHelp(cmd *cobra.Command, args []string) { } func (c *ClientInfo) MustAddIngredientGroups(ingGrps ...*IngredientGroup) { - ingGrpNsMap := make(map[string]bool) - ingGrpHeaderMap := make(map[string]bool) - for _, ingGrp := range ingGrps { - if ingGrp.Header == "" { - panic(fmt.Errorf("ingredient groups must have a non-empty header. found empty header for: %+v", ingGrp)) + if ingGrp == nil { + panic(errors.New("cannot add nil ingredientGroup")) } - if ingGrpNsMap[ingGrp.Namespace] { - panic(fmt.Errorf("duplicate namespace: %s", ingGrp.Namespace)) + c.ingredientGroups = append(c.ingredientGroups, ingGrps...) + + for _, ing := range ingGrp.ingredients { + c.ingredients[ing.name] = ing } + } +} + +func (c *ClientInfo) Ingredients() map[string]*Ingredient { + return c.ingredients +} - if ingGrpHeaderMap[ingGrp.Header] { - panic(fmt.Errorf("duplicate header: %s", ingGrp.Header)) +func (c *ClientInfo) Commands() map[string]*cobra.Command { + if len(c.commands) == 0 { + for _, ingGroup := range c.ingredientGroups { + for _, ing := range ingGroup.ingredients { + cmd := createCommandFromIngredient(ing) + c.commandsPerIngredientGroup[ingGroup] = append(c.commandsPerIngredientGroup[ingGroup], cmd) + c.commands[ing.name] = cmd + } } + } + + return c.commands +} + +func (c *ClientInfo) CommandsPerIngredientGroup() map[*IngredientGroup][]*cobra.Command { + return c.commandsPerIngredientGroup +} - ingGrpNsMap[ingGrp.Namespace] = true - ingGrpHeaderMap[ingGrp.Header] = true - c.ingredientGroups = append(c.ingredientGroups, ingGrp) +func (c *ClientInfo) createCommandFromIngredient(ing *Ingredient) *cobra.Command { + return &cobra.Command{ + Use: ing.name, + Aliases: ing.aliases, + Short: ing.short, + Long: ing.long, + Example: ing.example, + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, } } @@ -93,8 +112,23 @@ func (c *ClientInfo) Execute(args ...string) { } { runGroup.Add(func() error { - rootC := c.defaultIngredient.cobraCommand + rootC := c.rootCommand rootC.SetArgs(os.Args[1:]) + + cmdGroups := templates.CommandGroups{} + + for ingGroup, cmds := range c.commandsPerIngredientGroup { + rootC.AddCommand(cmds...) + cmdGroups = append(cmdGroups, templates.CommandGroup{ + Message: ingGroup.header, + Commands: cmds, + }) + } + + // add all commands from the cmdGroups as subcommands to the root + cmdGroups.Add(rootC) + templates.ActsAsRootCommand(rootC, nil, cmdGroups...) + return rootC.ExecuteContext(ctx) }, func(error) { cancel() diff --git a/go.mod b/go.mod index 657739e..db5e9d2 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,14 @@ module github.com/cakehappens/frosting go 1.14 require ( - github.com/deckarep/golang-set v1.7.1 + github.com/deckarep/golang-set v1.7.1 // indirect github.com/fatih/color v1.9.0 - github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 github.com/oklog/run v1.1.0 - github.com/oklog/ulid v1.3.1 github.com/oklog/ulid/v2 v2.0.2 github.com/spf13/cobra v0.0.7 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 - golang.org/x/tools v0.0.0-20200331202046-9d5940d49312 // indirect + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect gopkg.in/eapache/queue.v1 v1.1.0 - k8s.io/kubectl v0.18.0 // indirect + k8s.io/kubectl v0.18.0 ) diff --git a/go.sum b/go.sum index ee184be..e1e0293 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 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/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= @@ -49,6 +50,7 @@ github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0 h1:w3NnFcKR5241cfm github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 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= @@ -80,8 +82,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= 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= @@ -90,6 +90,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb 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/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/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= @@ -97,6 +98,7 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA// 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -118,6 +120,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -128,11 +131,14 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V 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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/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= @@ -176,6 +182,7 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -198,6 +205,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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= @@ -229,7 +237,6 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT 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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -240,7 +247,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf 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-20190611184440-5c40567a22f8/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 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -248,7 +254,6 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk 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/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= @@ -275,7 +280,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ 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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -310,15 +314,11 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/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-20200331202046-9d5940d49312 h1:2PHG+Ia3gK1K2kjxZnSylizb//eyaMG8gDFbOG7wLV8= -golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 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= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -327,6 +327,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc= gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= @@ -341,6 +342,7 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/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= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 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= @@ -362,7 +364,6 @@ k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kubectl v0.18.0 h1:hu52Ndq/d099YW+3sS3VARxFz61Wheiq8K9S7oa82Dk= k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= -k8s.io/kubernetes v1.18.0 h1:rVe+edi5GwutPQJ4KIZq1Nk506nmnfyz/KOZVCLv7Yo= k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= diff --git a/ingredient.go b/ingredient.go index 872d688..b3fa162 100644 --- a/ingredient.go +++ b/ingredient.go @@ -2,57 +2,89 @@ package frosting import ( "context" - "errors" - "fmt" "github.com/spf13/cobra" - "gopkg.in/eapache/queue.v1" + flag "github.com/spf13/pflag" ) type RunFn func(ctx context.Context) error +type IngredientOption func(ing *Ingredient) + +type IngredientFn func() *Ingredient + type Ingredient struct { - Name string - RunFn RunFn - Dependencies func() []*Ingredient + name string + runFn RunFn + aliases []string + short string + long string + example string + dependencies []string + serialDependencies []string + flagsFn func(flagSet *flag.FlagSet) + command *cobra.Command + ready bool + ran bool +} - cobraCommand *cobra.Command +func (ing *Ingredient) Name() string { + return ing.name } -func (ing *Ingredient) MustSetDependencies(deps ...*Ingredient) { - for _, dep := range deps { - if dep == ing { - panic(errors.New("ingredient cannot be a dependency of itself")) - } - } +func (ing *Ingredient) Dependencies() []string { + return ing.dependencies +} - ing.Dependencies = func() []*Ingredient { - return deps +func (ing *Ingredient) SerialDependencies() []string { + return ing.serialDependencies +} + +func WithDependencies(deps ...string) IngredientOption { + return func(ing *Ingredient) { + ing.dependencies = append(ing.dependencies, deps...) } } -// this probably only is needed from root ingredients -func (ing *Ingredient) resolveAllDeps() ([]*Ingredient, error) { - var deps []*Ingredient - visited := make(map[string]bool) +func WithSerialDependencies(deps ...string) IngredientOption { + return func(ing *Ingredient) { + ing.serialDependencies = append(ing.serialDependencies, deps...) + } +} - ingQueue := queue.New() +func WithAliases(aliases ...string) IngredientOption { + return func(ing *Ingredient) { + ing.aliases = append(ing.aliases, aliases...) + } +} - ingQueue.Add(ing) +func WithHelpDescriptions(short, long string) IngredientOption { + return func(ing *Ingredient) { + ing.short = short + ing.long = long + } +} - for ingQueue.Length() > 0 { - dep := ingQueue.Remove().(*Ingredient) +func WithExampleHelp(example string) IngredientOption { + return func(ing *Ingredient) { + ing.example = example + } +} - if visited[dep.Name] { - return nil, fmt.Errorf("found circular reference via: %s", dep.Name) - } +func WithFlags(fn func(set *flag.FlagSet)) IngredientOption { + return func(ing *Ingredient) { + ing.flagsFn = fn + } +} - deps = append(deps, dep) - visited[dep.Name] = true +func MustNewIngredient(name string, runFn RunFn, opts ...IngredientOption) *Ingredient { + ing := &Ingredient{ + name: name, + runFn: runFn, + } - for _, d := range dep.Dependencies() { - ingQueue.Add(d) - } + for _, o := range opts { + o(ing) } - return deps, nil + return ing } diff --git a/ingredientgroup.go b/ingredientgroup.go index 951e9f2..2f58650 100644 --- a/ingredientgroup.go +++ b/ingredientgroup.go @@ -1,7 +1,36 @@ package frosting type IngredientGroup struct { - Header string - Namespace string - Ingredients []*Ingredient + header string + ingredients []*Ingredient +} + +func (grp *IngredientGroup) Header() string { + return grp.header +} + +func (grp *IngredientGroup) Ingredients() []*Ingredient { + return grp.ingredients +} + +type IngredientGroupOption func(grp *IngredientGroup) + +type IngredientGroupFn func() *IngredientGroup + +func Includes(ingredients ...*Ingredient) IngredientGroupOption { + return func(grp *IngredientGroup) { + grp.ingredients = append(grp.ingredients, ingredients...) + } +} + +func MustNewGroup(header string, opts ...IngredientGroupOption) *IngredientGroup { + grp := &IngredientGroup{ + header: header, + } + + for _, o := range opts { + o(grp) + } + + return grp } diff --git a/resolver.go b/resolver.go index f7af6f9..b1f8f5d 100644 --- a/resolver.go +++ b/resolver.go @@ -1,101 +1,76 @@ package frosting -//import ( -// "errors" -// "fmt" -// -// mapset "github.com/deckarep/golang-set" -//) -// -//type IngredientGraph []Ingredient -// -//type IngredientDependencyResolver interface { -// Resolve() (IngredientGraph, error) -// CurrentReady() IngredientGraph -//} -// -//type ingredientDependencyResolver struct { -// ingredients map[string]*IngredientInfo -// resolved IngredientGraph -//} -// -//func newIngredientDependencyResolver(ingredients IngredientGraph) (*ingredientDependencyResolver, error) { -// r := &ingredientDependencyResolver{} -// -// r.ingredients = make(map[string]*IngredientInfo) -// r.resolved = IngredientGraph{} -// -// for _, ing := range ingredients { -// if ing.id == "" { -// return nil, fmt.Errorf("ingredientInfo does not have id. Create only using NewIngredientInfo. name: %s", ing.name) -// } -// r.ingredients[ing.id] = ing -// } -// -// return r, nil -//} -// -//func (r *ingredientDependencyResolver) Resolve() (IngredientGraph, error) { -// // if we've already resolved, don't re-resolve -// if len(r.resolved) > 0 { -// return r.resolved, nil -// } -// -// ingredientDependencies := make(map[string]mapset.Set) -// -// // Populate the map -// for _, ing := range r.ingredients { -// ingredientDependencies[ing.id] = ing.dependencies.Clone() -// } -// -// // Iteratively find and remove nodes from the graph which have no dependencies. -// // If at some point there are still nodes in the graph and we cannot find -// // nodes without dependencies, that means we have a circular dependency -// for len(ingredientDependencies) != 0 { -// // Get all nodes from the graph which have no dependencies -// readySet := mapset.NewSet() -// for id, deps := range ingredientDependencies { -// if deps.Cardinality() == 0 { -// readySet.Add(id) -// } -// } -// -// // If there aren't any ready nodes, then we have a circular dependency -// if readySet.Cardinality() == 0 { -// var g IngredientGraph -// for id := range ingredientDependencies { -// g = append(g, r.ingredients[id]) -// } -// -// return g, errors.New("circular dependency found") -// } -// -// // Remove the ready nodes and add them to the resolved graph -// for id := range readySet.Iter() { -// delete(ingredientDependencies, id.(string)) -// r.resolved = append(r.resolved, r.ingredients[id.(string)]) -// } -// -// // Also make sure to remove the ready nodes from the -// // remaining node dependencies as well -// for id, deps := range ingredientDependencies { -// diff := deps.Difference(readySet) -// ingredientDependencies[id] = diff -// } -// } -// -// return r.resolved, nil -//} -// -//func (r *ingredientDependencyResolver) CurrentReady() IngredientGraph { -// currentReady := IngredientGraph{} -// for _, ing := range r.resolved { -// if ing.currentDependencies.Cardinality() == 0 { -// currentReady = append(currentReady, ing) -// } -// } -// -// return currentReady -//} -// -//var _ IngredientDependencyResolver = &ingredientDependencyResolver{} +import ( + "errors" + mapset "github.com/deckarep/golang-set" +) + +type ingredientDependencyResolver struct { + ingredients map[string]*Ingredient + resolved []*Ingredient +} + +func newIngredientDependencyResolver(ingredientList []*Ingredient) (*ingredientDependencyResolver, error) { + r := &ingredientDependencyResolver{} + + r.ingredients = make(map[string]*Ingredient) + r.resolved = []*Ingredient{} + + for _, ingredient := range ingredientList { + r.ingredients[ingredient.name] = ingredient + } + + return r, nil +} + +func (r *ingredientDependencyResolver) Resolve() ([]*Ingredient, error) { + // if we've already resolved, don't re-resolve + if len(r.resolved) > 0 { + return r.resolved, nil + } + + ingredientDependencies := make(map[string]mapset.Set) + + // Populate the map + for _, ingredient := range r.ingredients { + ingredientDependencies[ingredient.name] = ingredient.dependencies + } + + // Iteratively find and remove nodes from the graph which have no dependencies. + // If at some point there are still nodes in the graph and we cannot find + // nodes without dependencies, that means we have a circular dependency + for len(ingredientDependencies) != 0 { + // Get all nodes from the graph which have no dependencies + readySet := mapset.NewSet() + for id, deps := range ingredientDependencies { + if deps.Cardinality() == 0 { + readySet.Add(id) + } + } + + // If there aren't any ready nodes, then we have a circular dependency + if readySet.Cardinality() == 0 { + var g []*Ingredient + for id := range ingredientDependencies { + g = append(g, r.ingredients[id]) + } + + return g, errors.New("circular dependency found") + } + + // Remove the ready nodes and add them to the resolved graph + for id := range readySet.Iter() { + delete(ingredientDependencies, id.(string)) + r.resolved = append(r.resolved, r.ingredients[id.(string)]) + } + + // Also make sure to remove the ready nodes from the + // remaining node dependencies as well + for id, deps := range ingredientDependencies { + diff := deps.Difference(readySet) + ingredientDependencies[id] = diff + } + } + + return r.resolved, nil +} diff --git a/test/helpers.go b/test/helpers.go new file mode 100644 index 0000000..ba296bf --- /dev/null +++ b/test/helpers.go @@ -0,0 +1,4 @@ +// This contains some test helpers to +// help you be confident you are shipping +// quality ingredients +package test diff --git a/utils.go b/util/util.go similarity index 77% rename from utils.go rename to util/util.go index da2a1bd..4c0ff8a 100644 --- a/utils.go +++ b/util/util.go @@ -1,18 +1,9 @@ -package frosting +package util import ( - "github.com/oklog/ulid/v2" - "math/rand" "runtime" - "time" ) -func newULID() string { - t := time.Now() - entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) - return ulid.MustNew(ulid.Timestamp(t), entropy).String() -} - func getFrame(skipFrames int) runtime.Frame { // We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame targetFrameIndex := skipFrames + 2 @@ -37,7 +28,7 @@ func getFrame(skipFrames int) runtime.Frame { } // MyCaller returns the caller of the function that called it :) -func myCaller() string { +func MyCaller() string { // Skip GetCallerFunctionName and the function to get the caller of return getFrame(2).Function } From a1b2b9491330ad349144015b0b66fd835213efb7 Mon Sep 17 00:00:00 2001 From: Wes McNamee Date: Sat, 11 Apr 2020 16:03:09 -0700 Subject: [PATCH 6/6] adding tests --- README.md | 56 ++- command.go | 29 ++ docs/getting-started.md | 95 ++-- docs/internals.md | 7 + examples/full/frosting/main.go | 48 -- examples/{full => minimal}/.gitignore | 0 examples/minimal/README.md | 8 +- examples/minimal/frosting/go.mod | 7 + examples/minimal/frosting/go.sum | 376 +++++++++++++++ examples/minimal/frosting/main.go | 55 +++ .../{full => minimal}/frosting/main_test.go | 0 frosting.go | 94 ++-- generators/generators.go | 6 + generators/queue.go | 62 +++ generators/set.go | 91 ++++ go.mod | 7 +- go.sum | 12 +- ingredient.go | 56 +-- ingredient_queue.go | 62 +++ ingredient_set.go | 91 ++++ ingredient_test.go | 86 ---- ingredientgroup.go | 36 -- resolver.go | 157 ++++-- resolver_test.go | 455 ++++++++++++++++++ test/helpers_test.go | 1 + util/util.go | 34 -- 26 files changed, 1493 insertions(+), 438 deletions(-) create mode 100644 command.go create mode 100644 docs/internals.md delete mode 100644 examples/full/frosting/main.go rename examples/{full => minimal}/.gitignore (100%) create mode 100644 examples/minimal/frosting/go.mod create mode 100644 examples/minimal/frosting/go.sum create mode 100644 examples/minimal/frosting/main.go rename examples/{full => minimal}/frosting/main_test.go (100%) create mode 100644 generators/generators.go create mode 100644 generators/queue.go create mode 100644 generators/set.go create mode 100644 ingredient_queue.go create mode 100644 ingredient_set.go delete mode 100644 ingredient_test.go delete mode 100644 ingredientgroup.go create mode 100644 resolver_test.go create mode 100644 test/helpers_test.go delete mode 100644 util/util.go diff --git a/README.md b/README.md index 36edb2e..1ccc90d 100644 --- a/README.md +++ b/README.md @@ -82,47 +82,58 @@ package main import ( "context" "fmt" + "os" + "github.com/cakehappens/frosting" - "github.com/cakehappens/frosting/ingredient" ) -func NewBuildIngredient() *ingredient.Ingredient { - return ingredient.MustNew( - "build", - func(ctx context.Context) error { +func NewBuildIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient { + return frosting.MustNewIngredient( + name, + func(ctx context.Context, ing *frosting.Ingredient) error { fmt.Println("Building...") return nil }, - ingredient.WithDependencies(NewTestIngredient), + append([]frosting.IngredientOption{ + frosting.WithHelpDescriptions("buildShort", "buildLong"), + }, options...)..., ) } -func NewTestIngredient() *ingredient.Ingredient { - return ingredient.MustNew( - "test", - func(ctx context.Context) error { +func NewTestIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient { + return frosting.MustNewIngredient( + name, + func(ctx context.Context, ing *frosting.Ingredient) error { fmt.Println("Testing...") return nil }, + options..., ) } func main() { f := frosting.New("frost") - f.MustAddIngredientGroups( - ingredient.MustNewGroup( - "", - "Main Stuff:", - ingredient.Includes( - NewBuildIngredient, - NewTestIngredient, - ), - ), - ) - f.Execute("foo") + { + build := NewBuildIngredient("build") + + // test depends on build + test := NewTestIngredient( + "test", + frosting.WithDependencies(build), + ) + + f.Group( + "Basic Commands (Beginner):", + build, + test, + ) + } + + f.Execute(os.Args[1:]...) } + ``` ```bash @@ -158,11 +169,12 @@ I'll definitely get some templates/guidelines setup soon... - https://dave.cheney.net/tag/logging - https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run -## 💕 Related +## 💕 Related & Inspiration - [Mage][mage] - [Taskfile][taskfile] - [Make][make] +- [Makem.sh][https://github.com/alphapapa/makem.sh] ## 📜 Credits diff --git a/command.go b/command.go new file mode 100644 index 0000000..46d12c3 --- /dev/null +++ b/command.go @@ -0,0 +1,29 @@ +package frosting + +import "github.com/spf13/cobra" + +type defaultCommandBuilder struct{} + +func newSimpleCommandBuilder() *defaultCommandBuilder { + return &defaultCommandBuilder{} +} + +type CommandBuilder interface { + Build(ingredient *Ingredient) (*cobra.Command, error) +} + +func (c *defaultCommandBuilder) Build(ingredient *Ingredient) (*cobra.Command, error) { + cmd := &cobra.Command{ + Use: ingredient.name, + Short: ingredient.short, + Long: ingredient.long, + Example: ingredient.example, + Aliases: ingredient.aliases, + } + + if ingredient.flagsFn != nil { + ingredient.flagsFn(cmd.Flags()) + } + + return cmd, nil +} diff --git a/docs/getting-started.md b/docs/getting-started.md index f203f9e..936cdcb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,25 +6,27 @@ ### Library Code Example -📌 _Let the consumer define the name of the target_ +📌 _Let the caller define the name of the target_ 🏷️ _Ingredients are ** **globally unique by name** **, to allow different ingredients to have the same dependencies, but ensuring we don't call the dependent ingredient more than once._ -🛑 _Don't perform any initialization logic within the function that returns the ingredient. This function may be called multiple times, and the resulting ingredient struct may be tossed out based on the globally unique rule above_ - ```go -func NewBuildIngredient(name) *ingredient.Ingredient { - return ingredient.MustNew( +func NewBuildIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient { + return frosting.MustNewIngredient( name, - func(ctx context.Context) error { + func(ctx context.Context, ing *frosting.Ingredient) error { fmt.Println("Building...") return nil }, + append([]frosting.IngredientOption{ + frosting.WithHelpDescriptions("buildShort", "buildLong"), + }, options...)..., ) } ``` -Ingredients may be grouped as well, but regardless of group, they are still ** **globally unique by name** **. A group is only to assist when getting help. +Next, mix ingredients into frosting by adding them to a group. This provides a nice interface for getting help about related ingredients. +Ingredients are still ** **globally unique by name** ** regardless of group. ``` $ frost help @@ -36,64 +38,27 @@ Basic Commands (Beginner): ``` ```go -func NewFooIngredientGroup() *frosting.IngredientGroup { - return ingredient.MustNewGroup( - "Basic Commands (Beginner):", - ingredient.Includes( - NewBuildIngredient("build"), - NewTestIngredient("test"), - ), - ), +func main() { + f := frosting.New("frost") + + { + build := NewBuildIngredient("build") + + // test depends on build + test := NewTestIngredient( + "test", + frosting.WithDependencies(build), + ) + + f.Group( + "Basic Commands (Beginner):", + build, + test, + ) + } + + f.Execute(os.Args[1:]...) } ``` -### Declaring Dependencies - -The recommended way to define dependencies is to first declare the name of ingredient as a constant. Then use that constant when setting dependencies. - -```go - -const Build = "build" - -func NewBuildIngredient() *ingredient.Ingredient { - return ingredient.MustNew( - Build, - func(ctx context.Context) error { - fmt.Println("Building...") - return nil - }, - ingredient.WithDependency( - - ) - ) -} - -const Test = "test" - -func NewBuildIngredient(name) *ingredient.Ingredient { - return ingredient.MustNew( - name, - func(ctx context.Context) error { - fmt.Println("Building...") - return nil - }, - ingredient.WithDependencies( - Build - ) - ) -} -``` - -You may also declare dependencies that should be run serially. These dependencies will implicitly be made dependencies of each other. The following will result in ingredients running in this order: - -``` -A -> B -> C -> This -``` - -```go -ingredient.WithSerialDependencies( - A, - B, - C -) -``` +👀 For more information, check out the [examples](../examples)! diff --git a/docs/internals.md b/docs/internals.md new file mode 100644 index 0000000..4543798 --- /dev/null +++ b/docs/internals.md @@ -0,0 +1,7 @@ +# Internals + +Once ingredients are defined and "imported" into a frosting client (see [getting-started](./getting-started.md)), + +```go + +``` diff --git a/examples/full/frosting/main.go b/examples/full/frosting/main.go deleted file mode 100644 index 1c9ef11..0000000 --- a/examples/full/frosting/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "fmt" - "github.com/cakehappens/frosting" -) - -const Build = "build" - -func NewBuildIngredient() *frosting.Ingredient { - return frosting.MustNew( - Build, - func(ctx context.Context) error { - fmt.Println("Building...") - return nil - }, - //ingredient.WithDependencies(Test), - frosting.WithHelpDescriptions("buildShort", "buildLong"), - ) -} - -const Test = "test" - -func NewTestIngredient() *frosting.Ingredient { - return frosting.MustNew( - Test, - func(ctx context.Context) error { - fmt.Println("Testing...") - return nil - }, - ) -} - -func main() { - f := frosting.New("frost") - f.MustAddIngredientGroups( - frosting.MustNewGroup( - "Main Stuff:", - frosting.Includes( - NewBuildIngredient(), - NewTestIngredient(), - ), - ), - ) - - f.Execute("foo") -} diff --git a/examples/full/.gitignore b/examples/minimal/.gitignore similarity index 100% rename from examples/full/.gitignore rename to examples/minimal/.gitignore diff --git a/examples/minimal/README.md b/examples/minimal/README.md index ea13447..ccc2e17 100644 --- a/examples/minimal/README.md +++ b/examples/minimal/README.md @@ -6,8 +6,12 @@ The Newbie Tutorial Imagine that this is the _root_ of your project repo. It doesn't matter what language, node, python, go, etc. +The recommended approach is to put frosting in a subfolder, as to not interfere if you are building a go application. + ## Getting Started -```shell +Compile into a binary -``` +```shell +go build -o frost ./frosting +``` \ No newline at end of file diff --git a/examples/minimal/frosting/go.mod b/examples/minimal/frosting/go.mod new file mode 100644 index 0000000..bda88a6 --- /dev/null +++ b/examples/minimal/frosting/go.mod @@ -0,0 +1,7 @@ +module github.com/cakehappens/frosting/examples/minimal/frosting + +go 1.14 + +replace github.com/cakehappens/frosting => ../../.. + +require github.com/cakehappens/frosting v0.0.0-00010101000000-000000000000 // indirect diff --git a/examples/minimal/frosting/go.sum b/examples/minimal/frosting/go.sum new file mode 100644 index 0000000..733ebb2 --- /dev/null +++ b/examples/minimal/frosting/go.sum @@ -0,0 +1,376 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +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/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +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/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +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/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +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.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/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/brianvoe/gofakeit/v4 v4.3.0/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= +github.com/cakehappens/frosting v0.0.0-20200328222114-d61721e8a280 h1:4Lu/XnXigL+GBbx0fC0LrMTq7zAAFdEDPDtuTaGyPlc= +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/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +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/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-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/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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +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 h1:w3NnFcKR5241cfmQU5ZZAsf0xcpId6mWOupTvJlUX2U= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +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.2.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.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +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/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +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.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +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/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/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/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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= +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/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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/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.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +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/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/client_model v0.2.0/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/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +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/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +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= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +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-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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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/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-20181114220301-adae6a3d119a/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= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/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-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-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-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-20190423024810-112230192c58/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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/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/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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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-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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +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/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/oleiade/lane.v1 v1.0.0/go.mod h1:e9mCiNjxfTGlkjxn/TPK3JUwhjKjby5cjXuGotH/QlE= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +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/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= +gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/gotestsum v0.4.1/go.mod h1:ACRGSBOwzQHHbvEpXgNlM3czSphBTLy5sdgF1/j9yFU= +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= +k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= +k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= +k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4= +k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +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/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kubectl v0.18.0 h1:hu52Ndq/d099YW+3sS3VARxFz61Wheiq8K9S7oa82Dk= +k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= +k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +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/examples/minimal/frosting/main.go b/examples/minimal/frosting/main.go new file mode 100644 index 0000000..ab9e243 --- /dev/null +++ b/examples/minimal/frosting/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/cakehappens/frosting" +) + +func NewBuildIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient { + return frosting.MustNewIngredient( + name, + func(ctx context.Context, ing *frosting.Ingredient) error { + fmt.Println("Building...") + return nil + }, + append([]frosting.IngredientOption{ + frosting.WithHelpDescriptions("buildShort", "buildLong"), + }, options...)..., + ) +} + +func NewTestIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient { + return frosting.MustNewIngredient( + name, + func(ctx context.Context, ing *frosting.Ingredient) error { + fmt.Println("Testing...") + return nil + }, + options..., + ) +} + +func main() { + f := frosting.New("frost") + + { + build := NewBuildIngredient("build") + + // test depends on build + test := NewTestIngredient( + "test", + frosting.WithDependencies(build), + ) + + f.Group( + "Basic Commands (Beginner):", + build, + test, + ) + } + + f.Execute(os.Args[1:]...) +} diff --git a/examples/full/frosting/main_test.go b/examples/minimal/frosting/main_test.go similarity index 100% rename from examples/full/frosting/main_test.go rename to examples/minimal/frosting/main_test.go diff --git a/frosting.go b/frosting.go index cba6335..b71b077 100644 --- a/frosting.go +++ b/frosting.go @@ -18,11 +18,9 @@ import ( ) type ClientInfo struct { - rootCommand *cobra.Command - ingredientGroups []*IngredientGroup - ingredients map[string]*Ingredient - commands map[string]*cobra.Command - commandsPerIngredientGroup map[*IngredientGroup][]*cobra.Command + rootCommand *cobra.Command + ingredientGroups []*ingredientGroup + ingredients map[string]*Ingredient } func newRootCommand(binaryName string) *cobra.Command { @@ -38,10 +36,8 @@ func newRootCommand(binaryName string) *cobra.Command { func New(binaryName string) *ClientInfo { return &ClientInfo{ - rootCommand: newRootCommand(binaryName), - ingredients: make(map[string]*Ingredient), - commands: make(map[string]*cobra.Command), - commandsPerIngredientGroup: make(map[*IngredientGroup][]*cobra.Command), + rootCommand: newRootCommand(binaryName), + ingredients: make(map[string]*Ingredient), } } @@ -49,55 +45,39 @@ func runHelp(cmd *cobra.Command, args []string) { cmd.Help() } -func (c *ClientInfo) MustAddIngredientGroups(ingGrps ...*IngredientGroup) { - for _, ingGrp := range ingGrps { - if ingGrp == nil { - panic(errors.New("cannot add nil ingredientGroup")) - } - - c.ingredientGroups = append(c.ingredientGroups, ingGrps...) - - for _, ing := range ingGrp.ingredients { - c.ingredients[ing.name] = ing - } +func (c *ClientInfo) Group(header string, ingredients ...*Ingredient) { + grp := &ingredientGroup{ + header: header, + ingredients: ingredients, } -} -func (c *ClientInfo) Ingredients() map[string]*Ingredient { - return c.ingredients -} + c.ingredientGroups = append(c.ingredientGroups, grp) -func (c *ClientInfo) Commands() map[string]*cobra.Command { - if len(c.commands) == 0 { - for _, ingGroup := range c.ingredientGroups { - for _, ing := range ingGroup.ingredients { - cmd := createCommandFromIngredient(ing) - c.commandsPerIngredientGroup[ingGroup] = append(c.commandsPerIngredientGroup[ingGroup], cmd) - c.commands[ing.name] = cmd - } + for _, ing := range ingredients { + if _, ok := c.ingredients[ing.name]; ok { + panic(fmt.Errorf("ingredient with name already added: %s", ing.name)) } - } - return c.commands -} - -func (c *ClientInfo) CommandsPerIngredientGroup() map[*IngredientGroup][]*cobra.Command { - return c.commandsPerIngredientGroup -} - -func (c *ClientInfo) createCommandFromIngredient(ing *Ingredient) *cobra.Command { - return &cobra.Command{ - Use: ing.name, - Aliases: ing.aliases, - Short: ing.short, - Long: ing.long, - Example: ing.example, - RunE: func(cmd *cobra.Command, args []string) error { - return nil - }, + c.ingredients[ing.name] = ing } } +//func mergeIngredientMaps(ingredientMaps ...map[string]*Ingredient) (map[string]*Ingredient, error) { +// newMap := make(map[string]*Ingredient) +// +// for _, ingMap := range ingredientMaps { +// for name, ing := range ingMap { +// if _, ok := newMap[name]; ok { +// return nil, fmt.Errorf("ingredient with name already added: %s", name) +// } +// +// newMap[name] = ing +// } +// } +// +// return newMap, nil +//} + func (c *ClientInfo) Execute(args ...string) { ctx, cancel := context.WithCancel(context.Background()) @@ -117,13 +97,13 @@ func (c *ClientInfo) Execute(args ...string) { cmdGroups := templates.CommandGroups{} - for ingGroup, cmds := range c.commandsPerIngredientGroup { - rootC.AddCommand(cmds...) - cmdGroups = append(cmdGroups, templates.CommandGroup{ - Message: ingGroup.header, - Commands: cmds, - }) - } + //for ingGroup, cmds := range c.commandsPerIngredientGroup { + // rootC.AddCommand(cmds...) + // cmdGroups = append(cmdGroups, templates.CommandGroup{ + // Message: ingGroup.header, + // Commands: cmds, + // }) + //} // add all commands from the cmdGroups as subcommands to the root cmdGroups.Add(rootC) diff --git a/generators/generators.go b/generators/generators.go new file mode 100644 index 0000000..e8d7233 --- /dev/null +++ b/generators/generators.go @@ -0,0 +1,6 @@ +package generators + +import "github.com/cheekybits/genny/generic" + +// Item the type of the queue +type Item generic.Type diff --git a/generators/queue.go b/generators/queue.go new file mode 100644 index 0000000..d39c4f8 --- /dev/null +++ b/generators/queue.go @@ -0,0 +1,62 @@ +package generators + +//go:generate genny -pkg frosting -in queue.go -out ../ingredient_queue.go gen "Item=Ingredient" + +import ( + "sync" +) + +// ItemQueue the queue of Items +type ItemQueue struct { + items []*Item + lock sync.RWMutex +} + +// New creates a new ItemQueue +func NewItemQueue() *ItemQueue { + return &ItemQueue{ + items: []*Item{}, + } +} + +// Enqueue adds an Item to the end of the queue +func (s *ItemQueue) Enqueue(t *Item) { + s.lock.Lock() + defer s.lock.Unlock() + s.items = append(s.items, t) +} + +// Dequeue removes an Item from the start of the queue +func (s *ItemQueue) Dequeue() (*Item, bool) { + s.lock.Lock() + defer s.lock.Unlock() + + if len(s.items) > 0 { + item := s.items[0] + s.items = s.items[1:len(s.items)] + return item, true + } + + return nil, false +} + +// Front returns the item next in the queue, without removing it +func (s *ItemQueue) Front() *Item { + s.lock.RLock() + defer s.lock.RUnlock() + return s.items[0] +} + +// IsEmpty returns true if the queue is empty +func (s *ItemQueue) IsEmpty() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.Length() == 0 +} + +// Size returns the number of Items in the queue +func (s *ItemQueue) Length() int { + s.lock.RLock() + defer s.lock.RUnlock() + return len(s.items) +} diff --git a/generators/set.go b/generators/set.go new file mode 100644 index 0000000..4135b84 --- /dev/null +++ b/generators/set.go @@ -0,0 +1,91 @@ +package generators + +//go:generate genny -pkg frosting -in set.go -out ../ingredient_set.go gen "Item=Ingredient" + +import ( + "sync" +) + +// ItemQueue the queue of Items +type ItemSet struct { + items map[*Item]bool + lock sync.RWMutex +} + +// New creates a new ItemQueue +func NewItemSet(items ...*Item) *ItemSet { + set := &ItemSet{ + items: make(map[*Item]bool), + } + + for _, item := range items { + set.items[item] = true + } + + return set +} + +// Enqueue adds an Item to the end of the queue +func (s *ItemSet) Add(t *Item) { + s.lock.Lock() + defer s.lock.Unlock() + s.items[t] = true +} + +// Dequeue removes an Item from the start of the queue +func (s *ItemSet) Remove(t *Item) { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.items, t) +} + +func (s *ItemSet) Contains(t *Item) bool { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.items[t] +} + +// Front returns the item next in the queue, without removing it +func (s *ItemSet) Cardinality() int { + s.lock.RLock() + defer s.lock.RUnlock() + return len(s.items) +} + +func (s *ItemSet) Items() []*Item { + s.lock.RLock() + defer s.lock.RUnlock() + + var itemList []*Item + for key, _ := range s.items { + itemList = append(itemList, key) + } + + return itemList +} + +// Set Difference: The relative complement or set difference of sets A and B, +// denoted A – B, is the set of all elements in A that are not in B. +// In set-builder notation, A – B = {x ∈ U : x ∈ A and x ∉ B}= A ∩ B'. +func (s *ItemSet) Difference(other *ItemSet) *ItemSet { + s.lock.RLock() + defer s.lock.RUnlock() + + diff := NewItemSet() + + for key, _ := range s.items { + if !other.items[key] { + diff.items[key] = true + } + } + + return diff +} + +func (s *ItemSet) Clone() *ItemSet { + s.lock.RLock() + defer s.lock.RUnlock() + + return NewItemSet(s.Items()...) +} diff --git a/go.mod b/go.mod index db5e9d2..1875089 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,15 @@ module github.com/cakehappens/frosting go 1.14 require ( - github.com/deckarep/golang-set v1.7.1 // indirect + github.com/brianvoe/gofakeit/v4 v4.3.0 + github.com/cheekybits/genny v1.0.0 + github.com/deckarep/golang-set v1.7.1 github.com/fatih/color v1.9.0 + github.com/kr/pretty v0.1.0 github.com/oklog/run v1.1.0 - github.com/oklog/ulid/v2 v2.0.2 github.com/spf13/cobra v0.0.7 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect - gopkg.in/eapache/queue.v1 v1.1.0 k8s.io/kubectl v0.18.0 ) diff --git a/go.sum b/go.sum index e1e0293..31fcf99 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,12 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/brianvoe/gofakeit/v4 v4.3.0 h1:y8octMlc4cmDra6sIst89NHEjZuWYmZDl9H0i5wjTvY= +github.com/brianvoe/gofakeit/v4 v4.3.0/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= 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/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 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= @@ -122,6 +126,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/imdario/mergo v0.3.5/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/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 v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= @@ -170,15 +175,12 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= -github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= 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.11.0/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/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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= @@ -279,6 +281,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ 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-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -313,6 +316,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -329,8 +333,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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/eapache/queue.v1 v1.1.0 h1:EldqoJEGtXYiVCMRo2C9mePO2UUGnYn2+qLmlQSqPdc= -gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/ingredient.go b/ingredient.go index b3fa162..2778525 100644 --- a/ingredient.go +++ b/ingredient.go @@ -2,55 +2,32 @@ package frosting import ( "context" - "github.com/spf13/cobra" + flag "github.com/spf13/pflag" ) -type RunFn func(ctx context.Context) error +type RunFn func(ctx context.Context, ing *Ingredient) error type IngredientOption func(ing *Ingredient) type IngredientFn func() *Ingredient type Ingredient struct { - name string - runFn RunFn - aliases []string - short string - long string - example string - dependencies []string - serialDependencies []string - flagsFn func(flagSet *flag.FlagSet) - command *cobra.Command - ready bool - ran bool + name string + runFn RunFn + aliases []string + short string + long string + example string + dependencies []*Ingredient + flagsFn func(flagSet *flag.FlagSet) + ran bool } func (ing *Ingredient) Name() string { return ing.name } -func (ing *Ingredient) Dependencies() []string { - return ing.dependencies -} - -func (ing *Ingredient) SerialDependencies() []string { - return ing.serialDependencies -} - -func WithDependencies(deps ...string) IngredientOption { - return func(ing *Ingredient) { - ing.dependencies = append(ing.dependencies, deps...) - } -} - -func WithSerialDependencies(deps ...string) IngredientOption { - return func(ing *Ingredient) { - ing.serialDependencies = append(ing.serialDependencies, deps...) - } -} - func WithAliases(aliases ...string) IngredientOption { return func(ing *Ingredient) { ing.aliases = append(ing.aliases, aliases...) @@ -70,6 +47,12 @@ func WithExampleHelp(example string) IngredientOption { } } +func WithDependencies(deps ...*Ingredient) IngredientOption { + return func(ing *Ingredient) { + ing.dependencies = deps + } +} + func WithFlags(fn func(set *flag.FlagSet)) IngredientOption { return func(ing *Ingredient) { ing.flagsFn = fn @@ -88,3 +71,8 @@ func MustNewIngredient(name string, runFn RunFn, opts ...IngredientOption) *Ingr return ing } + +type ingredientGroup struct { + header string + ingredients []*Ingredient +} diff --git a/ingredient_queue.go b/ingredient_queue.go new file mode 100644 index 0000000..76482f3 --- /dev/null +++ b/ingredient_queue.go @@ -0,0 +1,62 @@ +// This file was automatically generated by genny. +// Any changes will be lost if this file is regenerated. +// see https://github.com/cheekybits/genny + +package frosting + +import "sync" + +// IngredientQueue the queue of Ingredients +type IngredientQueue struct { + items []*Ingredient + lock sync.RWMutex +} + +// New creates a new IngredientQueue +func NewIngredientQueue() *IngredientQueue { + return &IngredientQueue{ + items: []*Ingredient{}, + } +} + +// Enqueue adds an Ingredient to the end of the queue +func (s *IngredientQueue) Enqueue(t *Ingredient) { + s.lock.Lock() + defer s.lock.Unlock() + s.items = append(s.items, t) +} + +// Dequeue removes an Ingredient from the start of the queue +func (s *IngredientQueue) Dequeue() (*Ingredient, bool) { + s.lock.Lock() + defer s.lock.Unlock() + + if len(s.items) > 0 { + item := s.items[0] + s.items = s.items[1:len(s.items)] + return item, true + } + + return nil, false +} + +// Front returns the item next in the queue, without removing it +func (s *IngredientQueue) Front() *Ingredient { + s.lock.RLock() + defer s.lock.RUnlock() + return s.items[0] +} + +// IsEmpty returns true if the queue is empty +func (s *IngredientQueue) IsEmpty() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.Length() == 0 +} + +// Size returns the number of Ingredients in the queue +func (s *IngredientQueue) Length() int { + s.lock.RLock() + defer s.lock.RUnlock() + return len(s.items) +} diff --git a/ingredient_set.go b/ingredient_set.go new file mode 100644 index 0000000..37f669c --- /dev/null +++ b/ingredient_set.go @@ -0,0 +1,91 @@ +// This file was automatically generated by genny. +// Any changes will be lost if this file is regenerated. +// see https://github.com/cheekybits/genny + +package frosting + +import "sync" + +// IngredientQueue the queue of Ingredients +type IngredientSet struct { + items map[*Ingredient]bool + lock sync.RWMutex +} + +// New creates a new IngredientQueue +func NewIngredientSet(items ...*Ingredient) *IngredientSet { + set := &IngredientSet{ + items: make(map[*Ingredient]bool), + } + + for _, item := range items { + set.items[item] = true + } + + return set +} + +// Enqueue adds an Ingredient to the end of the queue +func (s *IngredientSet) Add(t *Ingredient) { + s.lock.Lock() + defer s.lock.Unlock() + s.items[t] = true +} + +// Dequeue removes an Ingredient from the start of the queue +func (s *IngredientSet) Remove(t *Ingredient) { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.items, t) +} + +func (s *IngredientSet) Contains(t *Ingredient) bool { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.items[t] +} + +// Front returns the item next in the queue, without removing it +func (s *IngredientSet) Cardinality() int { + s.lock.RLock() + defer s.lock.RUnlock() + return len(s.items) +} + +func (s *IngredientSet) Ingredients() []*Ingredient { + s.lock.RLock() + defer s.lock.RUnlock() + + var itemList []*Ingredient + for key, _ := range s.items { + itemList = append(itemList, key) + } + + return itemList +} + +// Set Difference: The relative complement or set difference of sets A and B, +// denoted A – B, is the set of all elements in A that are not in B. +// In set-builder notation, A – B = {x ∈ U : x ∈ A and x ∉ B}= A ∩ B'. +func (s *IngredientSet) Difference(other *IngredientSet) *IngredientSet { + s.lock.RLock() + defer s.lock.RUnlock() + + diff := NewIngredientSet() + + for key, _ := range s.items { + if !other.items[key] { + diff.items[key] = true + } + } + + return diff +} + +func (s *IngredientSet) Clone() *IngredientSet { + s.lock.RLock() + defer s.lock.RUnlock() + + return NewIngredientSet(s.Ingredients()...) +} diff --git a/ingredient_test.go b/ingredient_test.go deleted file mode 100644 index 305450f..0000000 --- a/ingredient_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package frosting - -import ( - "github.com/stretchr/testify/assert" - "reflect" - "testing" -) - -func TestMustNewIngredientInfo(t *testing.T) { - type args struct { - name string - options []func(ing *IngredientInfo) - } - doesNotPanicTests := []struct { - name string - args args - want *IngredientInfo - }{ - { - name: "name provided no options", - args: args{ - name: "test", - }, - }, - { - name: "nil options", - args: args{ - name: "test", - options: nil, - }, - }, - { - name: "empty options", - args: args{ - name: "test", - options: []func(ing *IngredientInfo){}, - }, - }, - { - name: "options do nothing", - args: args{ - name: "test", - options: []func(ing *IngredientInfo){ - func(ing *IngredientInfo) { - - }, - }, - }, - }, - } - for _, tt := range doesNotPanicTests { - t.Run(tt.name, func(t *testing.T) { - assert.NotPanics( - t, - func() { - if got := MustNewIngredientInfo(tt.args.name, tt.args.options...); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MustNewIngredientInfo() = %v, want %v", got, tt.want) - } - }, - ) - }) - } - - panicTests := []struct { - name string - args args - want *IngredientInfo - }{ - { - name: "name is empty", - args: args{ - name: "", - }, - }, - } - for _, tt := range panicTests { - t.Run(tt.name, func(t *testing.T) { - assert.Panics( - t, - func() { - MustNewIngredientInfo(tt.args.name, tt.args.options...) - }, - ) - }) - } -} diff --git a/ingredientgroup.go b/ingredientgroup.go deleted file mode 100644 index 2f58650..0000000 --- a/ingredientgroup.go +++ /dev/null @@ -1,36 +0,0 @@ -package frosting - -type IngredientGroup struct { - header string - ingredients []*Ingredient -} - -func (grp *IngredientGroup) Header() string { - return grp.header -} - -func (grp *IngredientGroup) Ingredients() []*Ingredient { - return grp.ingredients -} - -type IngredientGroupOption func(grp *IngredientGroup) - -type IngredientGroupFn func() *IngredientGroup - -func Includes(ingredients ...*Ingredient) IngredientGroupOption { - return func(grp *IngredientGroup) { - grp.ingredients = append(grp.ingredients, ingredients...) - } -} - -func MustNewGroup(header string, opts ...IngredientGroupOption) *IngredientGroup { - grp := &IngredientGroup{ - header: header, - } - - for _, o := range opts { - o(grp) - } - - return grp -} diff --git a/resolver.go b/resolver.go index b1f8f5d..1edb258 100644 --- a/resolver.go +++ b/resolver.go @@ -2,75 +2,140 @@ package frosting import ( "errors" - mapset "github.com/deckarep/golang-set" + "fmt" ) -type ingredientDependencyResolver struct { - ingredients map[string]*Ingredient - resolved []*Ingredient +type DependencyResolver interface { + Ready() int + Dequeue() (*Ingredient, bool) + Length() int + NotifyComplete(ingredient *Ingredient) } -func newIngredientDependencyResolver(ingredientList []*Ingredient) (*ingredientDependencyResolver, error) { - r := &ingredientDependencyResolver{} - - r.ingredients = make(map[string]*Ingredient) - r.resolved = []*Ingredient{} +type defaultDependencyResolver struct { + ingredients map[string]*Ingredient + // values are those dependents of the key + dependentsGraph map[*Ingredient]*IngredientSet + // values are those parents of the key + parentsGraph map[*Ingredient]*IngredientSet + unready *IngredientSet + inflight *IngredientSet + ready *IngredientQueue +} - for _, ingredient := range ingredientList { - r.ingredients[ingredient.name] = ingredient +func newDefaultDependencyResolver() *defaultDependencyResolver { + return &defaultDependencyResolver{ + ingredients: make(map[string]*Ingredient), + dependentsGraph: make(map[*Ingredient]*IngredientSet), + parentsGraph: make(map[*Ingredient]*IngredientSet), + ready: NewIngredientQueue(), + unready: NewIngredientSet(), + inflight: NewIngredientSet(), } - - return r, nil } -func (r *ingredientDependencyResolver) Resolve() ([]*Ingredient, error) { - // if we've already resolved, don't re-resolve - if len(r.resolved) > 0 { - return r.resolved, nil - } +func (r *defaultDependencyResolver) Load(ingredients map[string]*Ingredient) error { + // we'll use a temporary map to check for circular dependencies + tmpIngredientDependencies := make(map[*Ingredient]*IngredientSet) + for _, node := range ingredients { + r.ingredients[node.name] = node - ingredientDependencies := make(map[string]mapset.Set) + dependencySet := NewIngredientSet() + for _, dep := range node.dependencies { + dependencySet.Add(dep) + if parentSet, ok := r.parentsGraph[dep]; ok { + parentSet.Add(node) + } else { + r.parentsGraph[dep] = NewIngredientSet(node) + } + } + r.dependentsGraph[node] = dependencySet + tmpIngredientDependencies[node] = dependencySet.Clone() - // Populate the map - for _, ingredient := range r.ingredients { - ingredientDependencies[ingredient.name] = ingredient.dependencies + if len(node.dependencies) == 0 { + r.ready.Enqueue(node) + } else { + r.unready.Add(node) + } } - // Iteratively find and remove nodes from the graph which have no dependencies. - // If at some point there are still nodes in the graph and we cannot find - // nodes without dependencies, that means we have a circular dependency - for len(ingredientDependencies) != 0 { - // Get all nodes from the graph which have no dependencies - readySet := mapset.NewSet() - for id, deps := range ingredientDependencies { + // check for circular dependency + for len(tmpIngredientDependencies) != 0 { + // readyset type is *Ingredient + readySet := NewIngredientSet() + for ing, deps := range tmpIngredientDependencies { if deps.Cardinality() == 0 { - readySet.Add(id) + readySet.Add(ingredients[ing.name]) } } - // If there aren't any ready nodes, then we have a circular dependency if readySet.Cardinality() == 0 { - var g []*Ingredient - for id := range ingredientDependencies { - g = append(g, r.ingredients[id]) - } - - return g, errors.New("circular dependency found") + return errors.New("circular dependency found in ingredients") } - // Remove the ready nodes and add them to the resolved graph - for id := range readySet.Iter() { - delete(ingredientDependencies, id.(string)) - r.resolved = append(r.resolved, r.ingredients[id.(string)]) + for _, ing := range readySet.Ingredients() { + delete(tmpIngredientDependencies, ing) } - // Also make sure to remove the ready nodes from the - // remaining node dependencies as well - for id, deps := range ingredientDependencies { + for parent, deps := range tmpIngredientDependencies { diff := deps.Difference(readySet) - ingredientDependencies[id] = diff + tmpIngredientDependencies[parent] = diff + } + } + + return nil +} + +func (r *defaultDependencyResolver) Ready() int { + return r.ready.Length() +} + +func (r *defaultDependencyResolver) Dequeue() (*Ingredient, bool) { + // remove item from ready queue + val, ok := r.ready.Dequeue() + if !ok { + return nil, false + } + // remove item from unready + r.unready.Remove(val) + + // add item to inflight + r.inflight.Add(val) + + return val, true +} + +// Length returns number of items that are left to be completed +// Items can be in a state of ready, unready or inflight +// Once marked as complete via NotifyComplete +// they are no longer tracked +func (r *defaultDependencyResolver) Length() int { + return r.ready.Length() + r.unready.Cardinality() + r.inflight.Cardinality() +} + +// whenever a node is marked complete, we can look at that nodes that depend on it (parent) +// and check if there are any other dependencies for not yet satisfied +// if there are no dependencies for that parent, add that node to the ready set +func (r *defaultDependencyResolver) NotifyComplete(ingredient *Ingredient) error { + // remove item from inflight + if !r.inflight.Contains(ingredient) { + return fmt.Errorf("tried to notify completion of an ingredient not in-flight: %s", ingredient.name) + } + r.inflight.Remove(ingredient) + + if parents, ok := r.parentsGraph[ingredient]; ok { + for _, p := range parents.Ingredients() { + if !r.dependentsGraph[p].Contains(ingredient) { + return fmt.Errorf("something went horribly wrong. Dependent or Parent Graph is malformed. Tried to remove ingredient from parent dependencies") + } + r.dependentsGraph[p].Remove(ingredient) + + if r.dependentsGraph[p].Cardinality() == 0 { + r.ready.Enqueue(p) + r.unready.Remove(p) + } } } - return r.resolved, nil + return nil } diff --git a/resolver_test.go b/resolver_test.go new file mode 100644 index 0000000..b485cd7 --- /dev/null +++ b/resolver_test.go @@ -0,0 +1,455 @@ +package frosting + +import ( + gofakeit "github.com/brianvoe/gofakeit/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func init() { + gofakeit.Seed(0) +} + +func Test_defaultDependencyResolver_Dequeue(t *testing.T) { + type testData struct { + name string + arrange func(r *defaultDependencyResolver) + preassert func(t *testing.T, r *defaultDependencyResolver) + assert func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) + } + + tests := []testData{ + { + name: "when ready is empty, return nil, false", + arrange: func(r *defaultDependencyResolver) {}, + preassert: func(t *testing.T, r *defaultDependencyResolver) {}, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + assert.Nil(t, ing, "there should be no ready ingredients") + assert.False(t, ok, "no ingredient was returned") + }, + }, + func() testData { + const name = "when ready has 1 item, return item, true" + + expectedIngredient := &Ingredient{name: gofakeit.Word()} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[expectedIngredient.name] = expectedIngredient + r.ready.Enqueue(expectedIngredient) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) {}, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + if assert.NotNil(t, ing, "because there an ingredient ready") { + assert.Equal(t, expectedIngredient, ing) + } + + assert.True(t, ok, "because an ingredient was returned") + }, + } + }(), + func() testData { + const name = "when ready has multiple items, return item, true" + + expectedIngredient := &Ingredient{name: "a"} + ing2 := &Ingredient{name: "b"} + ing3 := &Ingredient{name: "c"} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[expectedIngredient.name] = expectedIngredient + r.ingredients[ing2.name] = ing2 + r.ingredients[ing3.name] = ing3 + + r.ready.Enqueue(expectedIngredient) + r.ready.Enqueue(ing2) + r.ready.Enqueue(ing3) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) {}, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + if assert.NotNil(t, ing, "because there an ingredient ready") { + assert.Equal(t, expectedIngredient, ing) + } + + assert.True(t, ok, "because an ingredient was returned") + }, + } + }(), + func() testData { + const name = "when there are ingredients but none of them are ready, returns nil, false" + + ing1 := &Ingredient{name: "a"} + ing2 := &Ingredient{name: "b"} + ing3 := &Ingredient{name: "c"} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[ing1.name] = ing1 + r.ingredients[ing2.name] = ing2 + r.ingredients[ing3.name] = ing3 + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) {}, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + assert.Nil(t, ing, "because there are no ingredients ready") + assert.False(t, ok, "because no ingredient was returned") + }, + } + }(), + func() testData { + const name = "dequeued item is removed from ready" + + ing1 := &Ingredient{name: "a"} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[ing1.name] = ing1 + r.ready.Enqueue(ing1) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) { + assert.Equal(t, 1, r.ready.Length(), "because item was enqueued") + }, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + assert.True(t, r.ready.IsEmpty(), "because item was dequeued") + }, + } + }(), + func() testData { + const name = "dequeued item is added to inflight" + + ing1 := &Ingredient{name: "a"} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[ing1.name] = ing1 + r.ready.Enqueue(ing1) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) { + assert.Equal(t, 0, r.inflight.Cardinality(), "because nothing has been dequeued") + }, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + if assert.Equal(t, 1, r.inflight.Cardinality(), "because item was dequeued") { + assert.Contains(t, r.inflight.Ingredients(), ing1) + } + }, + } + }(), + func() testData { + const name = "dequeued item is removed from unready" + + ing1 := &Ingredient{name: "a"} + + return testData{ + name: name, + arrange: func(r *defaultDependencyResolver) { + r.ingredients[ing1.name] = ing1 + r.unready.Add(ing1) + r.ready.Enqueue(ing1) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) { + assert.Equal(t, 1, r.unready.Cardinality(), "because ingredient started as unready") + }, + assert: func(t *testing.T, r *defaultDependencyResolver, ing *Ingredient, ok bool) { + assert.Equal(t, 0, r.unready.Cardinality(), "because item transitioned to inflight") + }, + } + }(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newDefaultDependencyResolver() + tt.arrange(r) + tt.preassert(t, r) + got1, got2 := r.Dequeue() + tt.assert(t, r, got1, got2) + }) + } +} + +func Test_defaultDependencyResolver_Length(t *testing.T) { + type testData struct { + name string + arrange func(r *defaultDependencyResolver) + preassert func(t *testing.T, r *defaultDependencyResolver) + assert func(t *testing.T, r *defaultDependencyResolver, got int) + } + + tests := []testData{ + func() testData { + return testData{ + name: "when everything is empty, returns 0", + arrange: func(r *defaultDependencyResolver) { + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) { + }, + assert: func(t *testing.T, r *defaultDependencyResolver, got int) { + assert.Equal(t, 0, got, "because everything is empty") + }, + } + }(), + func() testData { + return testData{ + name: "sums ready, unready, and inflight", + arrange: func(r *defaultDependencyResolver) { + a := &Ingredient{name: "a"} + b := &Ingredient{name: "b"} + c := &Ingredient{name: "c"} + d := &Ingredient{name: "d"} + e := &Ingredient{name: "e"} + f := &Ingredient{name: "f"} + g := &Ingredient{name: "g"} + h := &Ingredient{name: "h"} + i := &Ingredient{name: "i"} + + r.unready.Add(a) + r.inflight.Add(b) + r.inflight.Add(c) + r.inflight.Add(d) + r.ready.Enqueue(e) + r.ready.Enqueue(f) + r.ready.Enqueue(g) + r.ready.Enqueue(h) + r.ready.Enqueue(i) + }, + preassert: func(t *testing.T, r *defaultDependencyResolver) { + + }, + assert: func(t *testing.T, r *defaultDependencyResolver, got int) { + assert.Equal(t, 9, got, "because there is 1 unready + 3 inflight + 5 ready") + }, + } + }(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newDefaultDependencyResolver() + tt.arrange(r) + tt.preassert(t, r) + got := r.Length() + tt.assert(t, r, got) + }) + } +} + +func Test_defaultDependencyResolver_NotifyComplete(t *testing.T) { + type testData struct { + name string + args *Ingredient + arrange func(r *defaultDependencyResolver) + preassert func(t *testing.T, r *defaultDependencyResolver) + assert func(t *testing.T, r *defaultDependencyResolver) + } + + //assert := assert.New(t) + + tests := []testData{ + //func() testData { + // return testData{ + // name: "", + // } + //}(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newDefaultDependencyResolver() + tt.arrange(r) + tt.preassert(t, r) + r.NotifyComplete(tt.args) + tt.assert(t, r) + }) + } +} + +func Test_defaultDependencyResolver_Load(t *testing.T) { + type testData struct { + name string + args map[string]*Ingredient + arrange func(r *defaultDependencyResolver) + preassert func(t *testing.T, r *defaultDependencyResolver) + assert func(t *testing.T, r *defaultDependencyResolver, got error) + } + + tests := []testData{ + //func() testData { + // return testData{} + //}(), + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newDefaultDependencyResolver() + tt.arrange(r) + tt.preassert(t, r) + err := r.Load(tt.args) + tt.assert(t, r, err) + }) + } +} + +func Test_defaultDependencyResolver_AcceptanceTests(t *testing.T) { + t.Run("single ingredient scenario", func(t *testing.T) { + require := require.New(t) + + input := make(map[string]*Ingredient) + ing := &Ingredient{name: "a"} + input[ing.name] = ing + + r := newDefaultDependencyResolver() + + err := r.Load(input) + require.NoError(err, "because there should be no circular dependency") + require.Equal(1, r.Length(), "because one ingredient has been loaded") + + val, ok := r.Dequeue() + + require.Equal(ing, val, "because single ingredient dequeued") + require.True(ok, "because ingredient was returned") + + require.Equal(1, r.Length(), "because one ingredient is in flight") + + r.NotifyComplete(val) + + require.Equal(0, r.Length(), "because one ingredient has completed") + }) + + t.Run("single ingredient depends on self", func(t *testing.T) { + input := make(map[string]*Ingredient) + ing := &Ingredient{name: "a"} + ing.dependencies = append(ing.dependencies, ing) + input[ing.name] = ing + + r := newDefaultDependencyResolver() + + err := r.Load(input) + require.Error(t, err, "because there is a circular dependency") + }) + + t.Run("ingredient circle (circular dependency)", func(t *testing.T) { + input := make(map[string]*Ingredient) + + ingA := &Ingredient{name: "a"} + input[ingA.name] = ingA + + ingB := &Ingredient{name: "b"} + input[ingB.name] = ingB + + ingC := &Ingredient{name: "c"} + input[ingC.name] = ingC + + // A -> B + ingA.dependencies = append(ingA.dependencies, ingB) + + // B -> C + ingB.dependencies = append(ingB.dependencies, ingC) + + // C -> A + ingC.dependencies = append(ingC.dependencies, ingA) + + r := newDefaultDependencyResolver() + + err := r.Load(input) + require.Error(t, err, "because there is a circular dependency") + }) + + t.Run("ingredients tree with circular dependency", func(t *testing.T) { + input := make(map[string]*Ingredient) + + ingA := &Ingredient{name: "a"} + input[ingA.name] = ingA + + ingB := &Ingredient{name: "b"} + input[ingB.name] = ingB + + ingC := &Ingredient{name: "c"} + input[ingC.name] = ingC + + // A -> B + ingA.dependencies = append(ingA.dependencies, ingB) + + // A -> C + ingA.dependencies = append(ingA.dependencies, ingC) + + // C -> A + ingC.dependencies = append(ingC.dependencies, ingA) + + r := newDefaultDependencyResolver() + + err := r.Load(input) + require.Error(t, err, "because there is a circular dependency") + }) + + t.Run("ingredients tree, can dequeue multiple ready", func(t *testing.T) { + require := require.New(t) + + input := make(map[string]*Ingredient) + + ingA := &Ingredient{name: "a"} + input[ingA.name] = ingA + + ingB := &Ingredient{name: "b"} + input[ingB.name] = ingB + + ingC := &Ingredient{name: "c"} + input[ingC.name] = ingC + + // A -> B + // A -> C + ingA.dependencies = append(ingA.dependencies, []*Ingredient{ingB, ingC}...) + + r := newDefaultDependencyResolver() + + err := r.Load(input) + total := len(input) + require.Equal(3, total, "because we have 3 ingredients") + require.Equal(total, r.Length(), "because we have 3 ingredients") + numCompleted := 0 + + require.NoError(err, "because there should be no circular dependency") + require.Equal(total, r.Length(), "because %d ingredients have been loaded", total) + + require.Equal(2, r.Ready(), "because b & c are without dependencies") + + val1, ok := r.Dequeue() + require.True(ok, "because the dequeue should be successful") + require.NotEqual(ingA, val1, "because the first dequeue should be either b or c") + + val2, ok := r.Dequeue() + require.True(ok, "because the dequeue should be successful") + require.NotEqual(ingA, val1, "because the second dequeue should be either b or c") + + require.NotEqual(val1, val2, "because only b and c should have been dequeued") + + require.Equal(total, r.Length(), "because nothing has been completed", total) + + err = r.NotifyComplete(val1) + require.NoError(err, "because the ingredient should be in-flight") + numCompleted += 1 + + require.Equal(total-numCompleted, r.Length(), "because %d ingredients have been completed", numCompleted) + + _, ok = r.Dequeue() + require.False(ok, "because 'a' still has an incomplete dependency") + + err = r.NotifyComplete(val2) + require.NoError(err, "because the ingredient should be in-flight") + numCompleted += 1 + + require.Equal(total-numCompleted, r.Length(), "because %d ingredients have been completed", numCompleted) + + val3, ok := r.Dequeue() + require.True(ok, "because the dequeue should be successful") + require.Equal(ingA, val3, "because 'a' should have been returned") + + err = r.NotifyComplete(val3) + require.NoError(err, "because the ingredient should be in-flight") + numCompleted += 1 + + require.Equal(total-numCompleted, r.Length(), "because %d ingredients have been completed", numCompleted) + + require.Equal(0, r.Length(), "because all ingredients have completed") + }) +} diff --git a/test/helpers_test.go b/test/helpers_test.go new file mode 100644 index 0000000..56e5404 --- /dev/null +++ b/test/helpers_test.go @@ -0,0 +1 @@ +package test diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 4c0ff8a..0000000 --- a/util/util.go +++ /dev/null @@ -1,34 +0,0 @@ -package util - -import ( - "runtime" -) - -func getFrame(skipFrames int) runtime.Frame { - // We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame - targetFrameIndex := skipFrames + 2 - - // Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need - programCounters := make([]uintptr, targetFrameIndex+2) - n := runtime.Callers(0, programCounters) - - frame := runtime.Frame{Function: "unknown"} - if n > 0 { - frames := runtime.CallersFrames(programCounters[:n]) - for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ { - var frameCandidate runtime.Frame - frameCandidate, more = frames.Next() - if frameIndex == targetFrameIndex { - frame = frameCandidate - } - } - } - - return frame -} - -// MyCaller returns the caller of the function that called it :) -func MyCaller() string { - // Skip GetCallerFunctionName and the function to get the caller of - return getFrame(2).Function -}