mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-19 12:58:16 +00:00
Compare commits
79 Commits
api/v0.3.2
...
cmd/config
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7adf7eb271 | ||
|
|
06b091b175 | ||
|
|
18d3b9ad8b | ||
|
|
35e24067fc | ||
|
|
32c959cde0 | ||
|
|
a61d478f0d | ||
|
|
983ac2be31 | ||
|
|
37ee56fc9a | ||
|
|
ade4f8969c | ||
|
|
5ad69d27e3 | ||
|
|
dc6e31c23f | ||
|
|
af27ada685 | ||
|
|
474dfc916b | ||
|
|
b1122a3e0b | ||
|
|
863eca1c32 | ||
|
|
2e895c147e | ||
|
|
af131c7471 | ||
|
|
09ec25b045 | ||
|
|
7ac573ae51 | ||
|
|
02dbc0da98 | ||
|
|
bb09f82f3c | ||
|
|
778f92ca0d | ||
|
|
4152a91609 | ||
|
|
2e118b7c68 | ||
|
|
72eda992bd | ||
|
|
230e0ca752 | ||
|
|
14eb524b9e | ||
|
|
81d62f90bf | ||
|
|
d71d2df364 | ||
|
|
34f21f44a1 | ||
|
|
070e128e47 | ||
|
|
2e5222f8e2 | ||
|
|
186df6f7c8 | ||
|
|
5d3a904283 | ||
|
|
065a4b7e90 | ||
|
|
1a330f89d9 | ||
|
|
4655c01c9b | ||
|
|
1d3c3995ed | ||
|
|
bf03669e94 | ||
|
|
7f52c814a8 | ||
|
|
62e5abd437 | ||
|
|
ecff981d1c | ||
|
|
0d1e085680 | ||
|
|
dae3ebcafe | ||
|
|
8b3723603c | ||
|
|
3ff8d4a099 | ||
|
|
2fc340db62 | ||
|
|
936dd090e6 | ||
|
|
7bbcba5d23 | ||
|
|
d7a6e35fec | ||
|
|
ed31a60e9b | ||
|
|
569fafba81 | ||
|
|
ae458d0c80 | ||
|
|
3af514fa9f | ||
|
|
3bb7c1ccc7 | ||
|
|
f364030557 | ||
|
|
52efd8c932 | ||
|
|
83e75a0f0a | ||
|
|
e02c48abd0 | ||
|
|
39c42d71f0 | ||
|
|
d5aac922d9 | ||
|
|
c801958d40 | ||
|
|
f9a4d5a14e | ||
|
|
4c6b995435 | ||
|
|
9a62d866f3 | ||
|
|
b2812838bf | ||
|
|
891ba0f461 | ||
|
|
cfcf885031 | ||
|
|
21f7fa07c0 | ||
|
|
92f4a09e0b | ||
|
|
e59e477702 | ||
|
|
4264ad0ad4 | ||
|
|
2554cd00eb | ||
|
|
383244cd63 | ||
|
|
b9da33afd4 | ||
|
|
23e339b86c | ||
|
|
9555009df8 | ||
|
|
91a10c560c | ||
|
|
780cb19c4d |
12
Makefile
12
Makefile
@@ -17,6 +17,9 @@ verify-kustomize: \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-latest
|
||||
|
||||
.PHONY: verify-kustomize-e2e
|
||||
verify-kustomize-e2e: test-examples-e2e-kustomize-against-HEAD
|
||||
|
||||
# Other builds in this repo might want a different linter version.
|
||||
# Without one Makefile to rule them all, the different makes
|
||||
# cannot assume that golanci-lint is at the version they want
|
||||
@@ -48,6 +51,11 @@ $(MYGOBIN)/goimports:
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/goimports
|
||||
|
||||
# Install resource from whatever is checked out.
|
||||
$(MYGOBIN)/resource:
|
||||
cd cmd/resource; \
|
||||
go install .
|
||||
|
||||
# To pin pluginator, use this recipe instead:
|
||||
# cd api;
|
||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
||||
@@ -193,6 +201,10 @@ test-unit-kustomize-all: \
|
||||
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-e2e-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip $(MYGOBIN)/resource
|
||||
./hack/testExamplesE2EAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
|
||||
( \
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -45,7 +46,7 @@ func NewCrawlMode(s string) CrawlMode {
|
||||
return CrawlUser
|
||||
case "github-repo":
|
||||
return CrawlRepo
|
||||
case "":
|
||||
case "index+github":
|
||||
return CrawlIndexAndGithub
|
||||
case "index":
|
||||
return CrawlIndex
|
||||
@@ -56,30 +57,33 @@ func NewCrawlMode(s string) CrawlMode {
|
||||
}
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
fmt.Printf("Usage: %s [mode] [githubUser|githubRepo]\n", os.Args[0])
|
||||
fmt.Printf("\tmode can be one of [github-user, github-repo, index, github]\n")
|
||||
fmt.Printf("%s: crawl all the documents in the index and crawling all the kustomization files on Github\n", os.Args[0])
|
||||
fmt.Printf("%s index: crawl all the documents in the index\n", os.Args[0])
|
||||
fmt.Printf("%s gihub: crawl all the kustomization files on Github\n", os.Args[0])
|
||||
fmt.Printf("%s github-user <github-user>: Crawl all the kustomization files in all the repositories of a Github user\n", os.Args[0])
|
||||
fmt.Printf("\tFor example, %s github-user kubernetes-sigs\n", os.Args[0])
|
||||
fmt.Printf("%s github-repo <github-repo>: Crawl all the kustomization files in a Github repo\n", os.Args[0])
|
||||
fmt.Printf("\tFor example, %s github-repo kubernetes-sigs/kustomize\n", os.Args[0])
|
||||
}
|
||||
|
||||
func main() {
|
||||
indexNamePtr := flag.String(
|
||||
"index", "kustomize", "The name of the ElasticSearch index.")
|
||||
modePtr := flag.String("mode", "index+github",
|
||||
`The crawling mode, which can be one of [github-user, github-repo, index, github, index+github].
|
||||
* github-user: crawl all the kustomization files in all the repositories of a Github user (--github-user must be specified for this mode).
|
||||
* github-repo: crawl all the kustomization files in a Github repository (--github-repo must be specified for this mode).
|
||||
* index: crawl all the documents in the index.
|
||||
* gihub: crawl all the kustomization files on Github.
|
||||
* index+github: crawl all the documents in the index and crawling all the kustomization files on Github.`)
|
||||
githubUserPtr := flag.String("github-user", "",
|
||||
"A github user name (e.g., kubernetes-sigs). This flag is required for the `github-user` mode.")
|
||||
githubRepoPtr := flag.String("github-repo", "",
|
||||
"A github repository name (e.g., kubernetes-sigs/kustomize). This flag is required for the `github-repo` mode.")
|
||||
flag.Parse()
|
||||
|
||||
githubToken := os.Getenv(githubAccessTokenVar)
|
||||
if githubToken == "" {
|
||||
fmt.Printf("Must set the variable '%s' to make github requests.\n",
|
||||
log.Printf("Must set the variable '%s' to make github requests.\n",
|
||||
githubAccessTokenVar)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
idx, err := index.NewKustomizeIndex(ctx)
|
||||
idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not create an index: %v\n", err)
|
||||
log.Printf("Could not create an index: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,7 +91,7 @@ func main() {
|
||||
cache, err := redis.DialURL(cacheURL)
|
||||
clientCache := &http.Client{}
|
||||
if err != nil {
|
||||
fmt.Printf("Error: redis could not make a connection: %v\n", err)
|
||||
log.Printf("Error: redis could not make a connection: %v\n", err)
|
||||
} else {
|
||||
clientCache = httpclient.NewClient(cache)
|
||||
}
|
||||
@@ -108,10 +112,10 @@ func main() {
|
||||
case *doc.KustomizationDocument:
|
||||
switch mode {
|
||||
case index.Delete:
|
||||
fmt.Println("Deleting: ", d)
|
||||
log.Printf("Deleting: %v", d)
|
||||
return idx.Delete(d.ID())
|
||||
default:
|
||||
fmt.Println("Inserting: ", d)
|
||||
log.Printf("Inserting: %v", d)
|
||||
return idx.Put(d.ID(), d)
|
||||
}
|
||||
default:
|
||||
@@ -121,14 +125,9 @@ func main() {
|
||||
|
||||
// seen tracks the IDs of all the documents in the index.
|
||||
// This helps avoid indexing a given document multiple times.
|
||||
seen := make(map[string]struct{})
|
||||
seen := crawler.NewSeenMap()
|
||||
|
||||
var mode CrawlMode
|
||||
if len(os.Args) == 1 {
|
||||
mode = CrawlIndexAndGithub
|
||||
} else {
|
||||
mode = NewCrawlMode(os.Args[1])
|
||||
}
|
||||
mode := NewCrawlMode(*modePtr)
|
||||
|
||||
ghCrawlerConstructor := func(user, repo string) crawler.Crawler {
|
||||
if user != "" {
|
||||
@@ -169,7 +168,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
fmt.Printf("Error iterating: %v\n", err)
|
||||
log.Fatalf("getSeedDocsFunc Error iterating: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,21 +186,21 @@ func main() {
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlUser:
|
||||
if len(os.Args) < 3 {
|
||||
Usage()
|
||||
log.Fatalf("Please specify a github user!")
|
||||
if *githubUserPtr == "" {
|
||||
flag.Usage()
|
||||
log.Fatalf("Please specify a github user with the github-user flag!")
|
||||
}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor(os.Args[2], "")}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor(*githubUserPtr, "")}
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlRepo:
|
||||
if len(os.Args) < 3 {
|
||||
Usage()
|
||||
log.Fatalf("Please specify a github repo!")
|
||||
if *githubRepoPtr == "" {
|
||||
flag.Usage()
|
||||
log.Fatalf("Please specify a github repository with the github-repo flag!")
|
||||
}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", os.Args[2])}
|
||||
crawlers := []crawler.Crawler{ghCrawlerConstructor("", *githubRepoPtr)}
|
||||
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
|
||||
case CrawlUnknown:
|
||||
Usage()
|
||||
log.Fatalf("The crawler mode must be one of [github-user, github-repo, index, github]")
|
||||
flag.Usage()
|
||||
log.Fatalf("The --mode flag must be one of [github-user, github-repo, index, github, index+github].")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ func main() {
|
||||
|
||||
m := entry.(map[string]interface{})
|
||||
if payload, ok := m["textPayload"]; ok {
|
||||
// use fmt.Printf here instead of log.Printf to avoid the time and code location info the log package provides
|
||||
fmt.Printf("%s", payload)
|
||||
} else {
|
||||
log.Printf("the log entry does not have the `textPayload` field: %s\n", line)
|
||||
|
||||
@@ -2,5 +2,4 @@ configmapGenerator:
|
||||
- name: elasticsearch-config
|
||||
literals:
|
||||
- es-url="http://esbasic-master:9200"
|
||||
- kustomize-index-name="kustomize"
|
||||
- plugin-index-name="plugin"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
There are three ways of running the crawler job.
|
||||
The crawler job can run in one of the following mode:
|
||||
|
||||
# Crawling all the documents in the index and crawling all the kustomization files on Github
|
||||
|
||||
@@ -7,14 +7,13 @@ of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: []
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: [""]
|
||||
args: ["--mode=index+github"]
|
||||
```
|
||||
|
||||
# Crawling all the documents in the index
|
||||
@@ -23,7 +22,7 @@ The `command` and `args` field of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["index"]
|
||||
args: ["--mode=index"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files on Github
|
||||
@@ -32,7 +31,7 @@ The `command` and `args` field of the container should be:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["github"]
|
||||
args: ["--mode=github"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files in a Github repo
|
||||
@@ -41,7 +40,7 @@ The `command` and `args` field of the container should be like:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["github-repo", "kubernetes-sigs/kustomize"]
|
||||
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize"]
|
||||
```
|
||||
|
||||
# Crawling all the kustomization files in all the repositories of a Github user
|
||||
@@ -50,5 +49,5 @@ The `command` and `args` field of the container should be like:
|
||||
|
||||
```
|
||||
command: ["/crawler"]
|
||||
args: ["github-user", "kubernetes-sigs"]
|
||||
args: ["--github-user", "--github-user=kubernetes-sigs"]
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ spec:
|
||||
image: gcr.io/haiyanmeng-gke-dev/crawler:v1
|
||||
imagePullPolicy: Always
|
||||
command: ["/crawler"]
|
||||
args: ["github-repo", "kubernetes-sigs/kustomize"]
|
||||
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"]
|
||||
env:
|
||||
- name: GITHUB_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
|
||||
@@ -29,7 +29,7 @@ type Crawler interface {
|
||||
// Crawl returns when it is done processing. This method does not take
|
||||
// ownership of the channel. The channel is write only, and it
|
||||
// designates where the crawler should forward the documents.
|
||||
Crawl(ctx context.Context, output chan<- CrawledDocument) error
|
||||
Crawl(ctx context.Context, output chan<- CrawledDocument, seen SeenMap) error
|
||||
|
||||
// Get the document data given the FilePath, Repo, and Ref/Tag/Branch.
|
||||
FetchDocument(context.Context, *doc.Document) error
|
||||
@@ -47,6 +47,21 @@ type CrawledDocument interface {
|
||||
WasCached() bool
|
||||
}
|
||||
|
||||
type SeenMap map[string]struct{}
|
||||
|
||||
func (seen SeenMap) Seen(item string) bool {
|
||||
_, ok := seen[item]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (seen SeenMap) Add(item string) {
|
||||
seen[item] = struct{}{}
|
||||
}
|
||||
|
||||
func NewSeenMap() SeenMap {
|
||||
return make(map[string]struct{})
|
||||
}
|
||||
|
||||
type CrawlSeed []*doc.Document
|
||||
|
||||
type IndexFunc func(CrawledDocument, index.Mode) error
|
||||
@@ -69,9 +84,9 @@ func findMatch(d *doc.Document, crawlers []Crawler) Crawler {
|
||||
}
|
||||
|
||||
func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
|
||||
seen map[string]struct{}, stack *CrawlSeed) {
|
||||
seen SeenMap, stack *CrawlSeed) {
|
||||
|
||||
seen[cdoc.ID()] = struct{}{}
|
||||
seen.Add(cdoc.ID())
|
||||
|
||||
// Insert into index
|
||||
if err := indx(cdoc, index.InsertOrUpdate); err != nil {
|
||||
@@ -87,7 +102,7 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
|
||||
}
|
||||
|
||||
for _, dep := range deps {
|
||||
if _, ok := seen[dep.ID()]; ok {
|
||||
if seen.Seen(dep.ID()) {
|
||||
continue
|
||||
}
|
||||
*stack = append(*stack, dep)
|
||||
@@ -95,7 +110,7 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
|
||||
}
|
||||
|
||||
func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc,
|
||||
seen map[string]struct{}, stack *CrawlSeed) {
|
||||
seen SeenMap, stack *CrawlSeed) {
|
||||
|
||||
UpdatedDocCount := 0
|
||||
seenDocCount := 0
|
||||
@@ -105,6 +120,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
|
||||
SetCreatedErrCount := 0
|
||||
convErrCount := 0
|
||||
deleteDocCount := 0
|
||||
crawledDocCount := 0
|
||||
|
||||
// During the execution of the for loop, more Documents may be added into (*docsPtr).
|
||||
for len(*docsPtr) > 0 {
|
||||
@@ -114,7 +130,11 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
|
||||
// remove the last Document in (*docPtr)
|
||||
*docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)]
|
||||
|
||||
if _, ok := seen[tail.ID()]; ok {
|
||||
crawledDocCount++
|
||||
logger.Printf("Crawling doc %d: %s %s", crawledDocCount, tail.RepositoryURL, tail.FilePath)
|
||||
|
||||
if seen.Seen(tail.ID()) {
|
||||
logger.Printf("this doc has been seen before")
|
||||
seenDocCount++
|
||||
continue
|
||||
}
|
||||
@@ -132,7 +152,16 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Println("Crawling ", tail.RepositoryURL, tail.FilePath)
|
||||
// If the Document represents a kustomization root, FetchDcoument will change
|
||||
// the `filePath` field of the Document by adding `kustomization.yaml` or
|
||||
// `kustomization.yml` or `kustomization` into the the field.
|
||||
// Therefore, it is necessary to add the ID of the Document into seen before
|
||||
// calling FetchDocument. Otherwise, the binary may enter into an infinite loop
|
||||
// if a kustomization file points to its kustmozation root in its `resources` or
|
||||
// `bases` field.
|
||||
seen.Add(tail.ID())
|
||||
|
||||
|
||||
if err := match.FetchDocument(ctx, tail); err != nil {
|
||||
logger.Printf("FetchDocument failed on %s %s: %v",
|
||||
tail.RepositoryURL, tail.FilePath, err)
|
||||
@@ -141,7 +170,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
|
||||
cdoc := &doc.KustomizationDocument{
|
||||
Document: *tail,
|
||||
}
|
||||
seen[cdoc.ID()] = struct{}{}
|
||||
seen.Add(cdoc.ID())
|
||||
if err := indx(cdoc, index.Delete); err != nil {
|
||||
logger.Printf("Failed to delete %s %s: %v",
|
||||
cdoc.RepositoryURL, cdoc.FilePath, err)
|
||||
@@ -182,7 +211,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
|
||||
// CrawlFromSeed updates all the documents in seed, and crawls all the new
|
||||
// documents referred in the seed.
|
||||
func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
|
||||
conv Converter, indx IndexFunc, seen map[string]struct{}) {
|
||||
conv Converter, indx IndexFunc, seen SeenMap) {
|
||||
|
||||
// stack tracks the documents directly referred in other documents.
|
||||
stack := make(CrawlSeed, 0)
|
||||
@@ -218,7 +247,7 @@ func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
|
||||
// from the seed will be processed before any other documents from the
|
||||
// crawlers.
|
||||
func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
|
||||
crawlers []Crawler) []error {
|
||||
crawlers []Crawler, seen SeenMap) []error {
|
||||
|
||||
errs := make([]error, len(crawlers))
|
||||
wg := sync.WaitGroup{}
|
||||
@@ -252,7 +281,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
|
||||
}
|
||||
}()
|
||||
defer close(docs)
|
||||
errs[idx] = crawler.Crawl(ctx, docs)
|
||||
errs[idx] = crawler.Crawl(ctx, docs, seen)
|
||||
}(i, crawler, docs) // Copies the index and the crawler
|
||||
}
|
||||
|
||||
@@ -262,7 +291,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
|
||||
|
||||
// CrawlGithub crawls all the kustomization files on Github.
|
||||
func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
|
||||
indx IndexFunc, seen map[string]struct{}) {
|
||||
indx IndexFunc, seen SeenMap) {
|
||||
// stack tracks the documents directly referred in other documents.
|
||||
stack := make(CrawlSeed, 0)
|
||||
|
||||
@@ -274,8 +303,12 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
docCount := 0
|
||||
for cdoc := range ch {
|
||||
if _, ok := seen[cdoc.ID()]; ok {
|
||||
docCount++
|
||||
logger.Printf("Processing doc %d found on Github", docCount)
|
||||
if seen.Seen(cdoc.ID()) {
|
||||
logger.Printf("the doc has been seen before")
|
||||
continue
|
||||
}
|
||||
match := findMatch(cdoc.GetDocument(), crawlers)
|
||||
@@ -289,7 +322,7 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
|
||||
}()
|
||||
|
||||
logger.Println("processing the documents found from crawling github")
|
||||
if errs := CrawlGithubRunner(ctx, ch, crawlers); errs != nil {
|
||||
if errs := CrawlGithubRunner(ctx, ch, crawlers, seen); errs != nil {
|
||||
for _, err := range errs {
|
||||
logIfErr(err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -75,7 +76,7 @@ func newCrawler(matchPrefix string, err error,
|
||||
|
||||
// Crawl implements the Crawler interface for testing.
|
||||
func (c testCrawler) Crawl(_ context.Context,
|
||||
output chan<- CrawledDocument) error {
|
||||
output chan<- CrawledDocument, _ SeenMap) error {
|
||||
|
||||
for i, d := range c.docs {
|
||||
isResource := true
|
||||
@@ -110,7 +111,7 @@ func (s sortableDocs) Len() int {
|
||||
}
|
||||
|
||||
func TestCrawlGithubRunner(t *testing.T) {
|
||||
fmt.Println("testing CrawlGithubRunner")
|
||||
log.Println("testing CrawlGithubRunner")
|
||||
tests := []struct {
|
||||
tc []Crawler
|
||||
errs []error
|
||||
@@ -181,8 +182,9 @@ func TestCrawlGithubRunner(t *testing.T) {
|
||||
defer close(output)
|
||||
defer wg.Done()
|
||||
|
||||
seen := NewSeenMap()
|
||||
errs := CrawlGithubRunner(context.Background(),
|
||||
output, test.tc)
|
||||
output, test.tc, seen)
|
||||
|
||||
// Check that errors are returned as they should be.
|
||||
if !reflect.DeepEqual(errs, test.errs) {
|
||||
@@ -215,7 +217,7 @@ func TestCrawlGithubRunner(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCrawlFromSeed(t *testing.T) {
|
||||
fmt.Println("testing CrawlFromSeed")
|
||||
log.Println("testing CrawlFromSeed")
|
||||
|
||||
tests := []struct {
|
||||
seed CrawlSeed
|
||||
@@ -322,7 +324,7 @@ resources:
|
||||
visited[d.ID()]++
|
||||
return nil
|
||||
},
|
||||
make(map[string]struct{}),
|
||||
NewSeenMap(),
|
||||
)
|
||||
if lv, lc := len(visited), len(tc.corpus); lv != lc {
|
||||
t.Errorf("error: %d of %d documents visited.", lv, lc)
|
||||
|
||||
@@ -30,6 +30,8 @@ var logger = log.New(os.Stdout, "Github Crawler: ",
|
||||
type githubCrawler struct {
|
||||
client GhClient
|
||||
query Query
|
||||
// branchMap maps github repositories to their default branches
|
||||
branchMap map[string]string
|
||||
}
|
||||
|
||||
type GhClient struct {
|
||||
@@ -51,13 +53,22 @@ func NewCrawler(accessToken string, retryCount uint64, client *http.Client,
|
||||
},
|
||||
accessToken: accessToken,
|
||||
},
|
||||
query: query,
|
||||
query: query,
|
||||
branchMap: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (gc githubCrawler) SetDefaultBranch(repo, branch string) {
|
||||
gc.branchMap[repo] = branch
|
||||
}
|
||||
|
||||
func (gc githubCrawler) DefaultBranch(repo string) string {
|
||||
return gc.branchMap[repo]
|
||||
}
|
||||
|
||||
// Implements crawler.Crawler.
|
||||
func (gc githubCrawler) Crawl(
|
||||
ctx context.Context, output chan<- crawler.CrawledDocument) error {
|
||||
func (gc githubCrawler) Crawl(ctx context.Context,
|
||||
output chan<- crawler.CrawledDocument, seen crawler.SeenMap) error {
|
||||
|
||||
noETagClient := GhClient{
|
||||
RequestConfig: gc.client.RequestConfig,
|
||||
@@ -79,13 +90,17 @@ func (gc githubCrawler) Crawl(
|
||||
|
||||
// Query each range for files.
|
||||
errs := make(multiError, 0)
|
||||
queryResult := RangeQueryResult{}
|
||||
for _, query := range ranges {
|
||||
err := processQuery(ctx, gc.client, query, output)
|
||||
rangeResult, err := processQuery(ctx, gc.client, query, output, seen, gc.branchMap)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
queryResult.Add(rangeResult)
|
||||
}
|
||||
|
||||
logger.Printf("Summary of Crawl: %s", queryResult.String())
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
@@ -100,7 +115,7 @@ func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error
|
||||
// set the default branch if it is empty
|
||||
if d.DefaultBranch == "" {
|
||||
url := gc.client.ReposRequest(d.RepositoryFullName())
|
||||
defaultBranch, err := gc.client.GetDefaultBranch(url)
|
||||
defaultBranch, err := gc.client.GetDefaultBranch(url, d.RepositoryURL, gc.branchMap)
|
||||
if err != nil {
|
||||
logger.Printf(
|
||||
"(error: %v) setting default_branch to master\n", err)
|
||||
@@ -108,6 +123,8 @@ func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error
|
||||
}
|
||||
d.DefaultBranch = defaultBranch
|
||||
}
|
||||
gc.SetDefaultBranch(d.RepositoryURL, d.DefaultBranch)
|
||||
|
||||
repoURL := d.RepositoryURL + "/" + d.FilePath + "?ref=" + d.DefaultBranch
|
||||
repoSpec, err := git.NewRepoSpecFromUrl(repoURL)
|
||||
if err != nil {
|
||||
@@ -176,10 +193,32 @@ func (gc githubCrawler) Match(d *doc.Document) bool {
|
||||
return strings.Contains(repoSpec.Host, "github.com")
|
||||
}
|
||||
|
||||
type RangeQueryResult struct {
|
||||
totalDocCnt uint64
|
||||
seenDocCnt uint64
|
||||
newDocCnt uint64
|
||||
errorCnt uint64
|
||||
}
|
||||
|
||||
func (r *RangeQueryResult) Add(other RangeQueryResult) {
|
||||
r.totalDocCnt += other.totalDocCnt
|
||||
r.newDocCnt += other.newDocCnt
|
||||
r.seenDocCnt += other.seenDocCnt
|
||||
r.errorCnt += other.errorCnt
|
||||
}
|
||||
|
||||
func (r *RangeQueryResult) String() string {
|
||||
return fmt.Sprintf("got %d files from API. "+
|
||||
"%d have been seen before. %d are new and sent to the output channel." +
|
||||
" %d have kustomizationResultAdapter errors.",
|
||||
r.totalDocCnt, r.seenDocCnt, r.newDocCnt, r.errorCnt)
|
||||
}
|
||||
|
||||
// processQuery follows all of the pages in a query, and updates/adds the
|
||||
// documents from the crawl to the datastore/index.
|
||||
func processQuery(ctx context.Context, gcl GhClient, query string,
|
||||
output chan<- crawler.CrawledDocument) error {
|
||||
output chan<- crawler.CrawledDocument, seen crawler.SeenMap,
|
||||
branchMap map[string]string) (RangeQueryResult, error) {
|
||||
|
||||
queryPages := make(chan GhResponseInfo)
|
||||
|
||||
@@ -196,50 +235,67 @@ func processQuery(ctx context.Context, gcl GhClient, query string,
|
||||
}()
|
||||
|
||||
errs := make(multiError, 0)
|
||||
errorCnt := 0
|
||||
totalCnt := 0
|
||||
result := RangeQueryResult{}
|
||||
pageID := 1
|
||||
for page := range queryPages {
|
||||
if page.Error != nil {
|
||||
errs = append(errs, page.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
pageResult := RangeQueryResult{}
|
||||
for _, file := range page.Parsed.Items {
|
||||
k, err := kustomizationResultAdapter(gcl, file)
|
||||
k, err := kustomizationResultAdapter(gcl, file, seen, branchMap)
|
||||
if err != nil {
|
||||
logger.Printf("kustomizationResultAdapter failed: %v", err)
|
||||
errs = append(errs, err)
|
||||
errorCnt++
|
||||
pageResult.errorCnt++
|
||||
}
|
||||
if k != nil {
|
||||
pageResult.newDocCnt++
|
||||
output <- k
|
||||
} else {
|
||||
pageResult.seenDocCnt++
|
||||
}
|
||||
totalCnt++
|
||||
pageResult.totalDocCnt++
|
||||
}
|
||||
|
||||
logger.Printf("got %d files out of %d from API. %d of %d had errors\n",
|
||||
totalCnt, page.Parsed.TotalCount, errorCnt, totalCnt)
|
||||
logger.Printf("processQuery [TotalCount %d - page %d]: %s",
|
||||
page.Parsed.TotalCount, pageID, pageResult.String())
|
||||
result.Add(pageResult)
|
||||
|
||||
pageID++
|
||||
}
|
||||
|
||||
return errs
|
||||
logger.Printf("Summary of processQuery: %s", result.String())
|
||||
|
||||
return result, errs
|
||||
}
|
||||
|
||||
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec) (
|
||||
crawler.CrawledDocument, error) {
|
||||
|
||||
data, err := gcl.GetFileData(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen crawler.SeenMap,
|
||||
branchMap map[string]string) (crawler.CrawledDocument, error) {
|
||||
url := gcl.ReposRequest(k.Repository.FullName)
|
||||
defaultBranch, err := gcl.GetDefaultBranch(url)
|
||||
defaultBranch, err := gcl.GetDefaultBranch(url, k.Repository.URL, branchMap)
|
||||
if err != nil {
|
||||
logger.Printf(
|
||||
"(error: %v) setting default_branch to master\n", err)
|
||||
defaultBranch = "master"
|
||||
}
|
||||
|
||||
document := doc.Document{
|
||||
FilePath: k.Path,
|
||||
DefaultBranch: defaultBranch,
|
||||
RepositoryURL: k.Repository.URL,
|
||||
}
|
||||
|
||||
if seen.Seen(document.ID()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, err := gcl.GetFileData(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := doc.KustomizationDocument{
|
||||
Document: doc.Document{
|
||||
DocumentData: string(data),
|
||||
@@ -344,7 +400,15 @@ func CloseResponseBody(resp *http.Response) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gcl GhClient) GetDefaultBranch(url string) (string, error) {
|
||||
// GetDefaultBranch gets the default branch of a github repository.
|
||||
// m is a map which maps a github repository to its default branch.
|
||||
// If repo is already in m, the default branch for url will be obtained from m;
|
||||
// otherwise, a query will be made to github to obtain the default branch.
|
||||
func (gcl GhClient) GetDefaultBranch(url, repo string, m map[string]string) (string, error) {
|
||||
if v, ok := m[repo]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
resp, err := gcl.GetReposData(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
@@ -589,7 +653,7 @@ func (gcl GhClient) Do(query string) (*http.Response, error) {
|
||||
|
||||
// gcl.client.Do: a non-2xx status code doesn't cause an error.
|
||||
// See https://golang.org/pkg/net/http/#Client.Do for more info.
|
||||
resp, err := gcl.client.Do(req)
|
||||
resp, err := gcl.client.Do(req)
|
||||
if resp != nil && resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("GhClient.Do(%s) failed with response code: %d",
|
||||
query, resp.StatusCode)
|
||||
|
||||
@@ -2,6 +2,7 @@ package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ type testCachedSearch struct {
|
||||
}
|
||||
|
||||
func (c testCachedSearch) CountResults(upperBound uint64) (uint64, error) {
|
||||
fmt.Printf("CountResults(%05x)\n", upperBound)
|
||||
log.Printf("CountResults(%05x)\n", upperBound)
|
||||
count, ok := c.cache[upperBound]
|
||||
if !ok {
|
||||
return count, fmt.Errorf("cache not set at %x", upperBound)
|
||||
|
||||
@@ -2,6 +2,7 @@ package doc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -83,7 +84,7 @@ func (doc *KustomizationDocument) GetResources() ([]*Document, error) {
|
||||
}
|
||||
next, err := doc.Document.FromRelativePath(r)
|
||||
if err != nil {
|
||||
fmt.Printf("GetResources error: %v\n", err)
|
||||
log.Printf("GetResources error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
res = append(res, &next)
|
||||
|
||||
@@ -87,7 +87,7 @@ func (idx *index) responseErrorOrNil(info string, res *esapi.Response,
|
||||
|
||||
defer res.Body.Close()
|
||||
if res.IsError() {
|
||||
return fmt.Errorf("%s: %s", messageStart, res.String())
|
||||
return fmt.Errorf("%s: %s [%d]", messageStart, res.String(), res.StatusCode)
|
||||
}
|
||||
|
||||
if reader != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -97,14 +98,14 @@ type KustomizeIndex struct {
|
||||
}
|
||||
|
||||
// Create index reference to the index containing the kustomize documents.
|
||||
func NewKustomizeIndex(ctx context.Context) (*KustomizeIndex, error) {
|
||||
idx, err := newIndex(ctx, "kustomize")
|
||||
func NewKustomizeIndex(ctx context.Context, indexName string) (*KustomizeIndex, error) {
|
||||
idx, err := newIndex(ctx, indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indicesExistsOp := idx.client.Indices.Exists
|
||||
resp, err := indicesExistsOp([]string{"kustomize"},
|
||||
resp, err := indicesExistsOp([]string{indexName},
|
||||
indicesExistsOp.WithContext(idx.ctx),
|
||||
indicesExistsOp.WithPretty())
|
||||
if err != nil {
|
||||
@@ -112,9 +113,9 @@ func NewKustomizeIndex(ctx context.Context) (*KustomizeIndex, error) {
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
fmt.Printf("The kustomize index already exists\n")
|
||||
log.Printf("The %s index already exists", indexName)
|
||||
} else {
|
||||
fmt.Printf("Creating the kustomize index\n")
|
||||
log.Printf("Creating the %s index\n", indexName)
|
||||
if err := idx.CreateIndex([]byte(IndexConfig)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -252,7 +253,7 @@ func (it *KustomizeIterator) Next() bool {
|
||||
}
|
||||
|
||||
if it.err == nil {
|
||||
fmt.Printf("updating scroll: %s\n", *it.scrollImpl.ScrollID)
|
||||
log.Printf("updating scroll: %s\n", *it.scrollImpl.ScrollID)
|
||||
it.err = it.update(*it.scrollImpl.ScrollID, reader)
|
||||
}
|
||||
|
||||
@@ -341,7 +342,7 @@ func (ki *KustomizeIndex) Search(query string,
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to format query %s", query)
|
||||
}
|
||||
fmt.Printf("formated query: %s\n", data)
|
||||
log.Printf("formated query: %s\n", data)
|
||||
|
||||
var kr ElasticKustomizeResult
|
||||
err = ki.index.Search(data, opts.SearchOptions, func(results io.Reader) error {
|
||||
|
||||
@@ -63,4 +63,20 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
Search all the documents whose filePath does not end with any of these following
|
||||
three filenames: `kustomization.yaml`, `kustomization.yml`, `kustomization`:
|
||||
```
|
||||
curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d'
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{ "regexp": { "filePath": ".*/kustomization((.yaml)?|(.yml)?)/*" }}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
@@ -27,90 +27,28 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repoSpec.Ref == "" {
|
||||
repoSpec.Ref = "master"
|
||||
}
|
||||
cmd := exec.Command(
|
||||
gitProgram,
|
||||
"init",
|
||||
"clone",
|
||||
"--depth=1",
|
||||
repoSpec.CloneSpec(),
|
||||
"-b",
|
||||
repoSpec.Ref,
|
||||
repoSpec.Dir.String())
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("Error initializing empty git repo: %s", out.String())
|
||||
log.Printf("Error cloning git repo: %s", out.String())
|
||||
return errors.Wrapf(
|
||||
err,
|
||||
"trouble initializing empty git repo in %s",
|
||||
repoSpec.Dir.String())
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
gitProgram,
|
||||
"remote",
|
||||
"add",
|
||||
"origin",
|
||||
repoSpec.CloneSpec())
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("Error setting git remote: %s", out.String())
|
||||
return errors.Wrapf(
|
||||
err,
|
||||
"trouble adding remote %s",
|
||||
repoSpec.CloneSpec())
|
||||
}
|
||||
if repoSpec.Ref == "" {
|
||||
repoSpec.Ref = "master"
|
||||
}
|
||||
cmd = exec.Command(
|
||||
gitProgram,
|
||||
"fetch",
|
||||
"--depth=1",
|
||||
"origin",
|
||||
repoSpec.Ref)
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
cmd = exec.Command(
|
||||
gitProgram,
|
||||
"pull",
|
||||
"origin",
|
||||
"master")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "trouble pulling %s", repoSpec.OrgRepo)
|
||||
}
|
||||
if repoSpec.Ref == "" {
|
||||
repoSpec.Ref = "master"
|
||||
}
|
||||
cmd = exec.Command(gitProgram, "checkout", repoSpec.Ref)
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "trouble checking out href %s", repoSpec.Ref)
|
||||
}
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
gitProgram,
|
||||
"reset",
|
||||
"--hard",
|
||||
"FETCH_HEAD")
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("Error performing git reset: %s", out.String())
|
||||
return errors.Wrapf(
|
||||
err, "trouble hard resetting empty repository to %s", repoSpec.Ref)
|
||||
"trouble cloning git repo %v in %s",
|
||||
repoSpec.CloneSpec(), repoSpec.Dir.String())
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
@@ -120,10 +58,11 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||
"--init",
|
||||
"--recursive")
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
cmd.Dir = repoSpec.Dir.String()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.Ref)
|
||||
return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.CloneSpec())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -23,7 +23,7 @@ linters:
|
||||
- gofmt
|
||||
- goimports
|
||||
# - golint
|
||||
- gosec
|
||||
# - gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
|
||||
@@ -43,6 +43,26 @@ Advanced Documentation Topics:
|
||||
`,
|
||||
}
|
||||
|
||||
// Export commands publicly for composition
|
||||
var (
|
||||
Cat = commands.CatCommand
|
||||
Count = commands.CountCommand
|
||||
CreateSetter = commands.CreateSetterCommand
|
||||
Fmt = commands.FmtCommand
|
||||
Grep = commands.GrepCommand
|
||||
ListSetters = commands.ListSettersCommand
|
||||
Merge = commands.MergeCommand
|
||||
Merge3 = commands.Merge3Command
|
||||
RunFn = commands.RunFnCommand
|
||||
Set = commands.SetCommand
|
||||
Sink = commands.SinkCommand
|
||||
Source = commands.SourceCommand
|
||||
Tree = commands.TreeCommand
|
||||
|
||||
StackOnError = &commands.StackOnError
|
||||
ExitOnError = &commands.ExitOnError
|
||||
)
|
||||
|
||||
// NewConfigCommand returns a new *cobra.Command for the config command group. This may
|
||||
// be embedded into other go binaries as a way of packaging the "config" command as part
|
||||
// of another binary.
|
||||
@@ -77,6 +97,9 @@ func NewConfigCommand(name string) *cobra.Command {
|
||||
root.AddCommand(commands.Merge3Command(name))
|
||||
root.AddCommand(commands.CountCommand(name))
|
||||
root.AddCommand(commands.RunFnCommand(name))
|
||||
root.AddCommand(commands.XArgsCommand())
|
||||
root.AddCommand(commands.WrapCommand())
|
||||
|
||||
root.AddCommand(commands.SetCommand(name))
|
||||
root.AddCommand(commands.ListSettersCommand(name))
|
||||
root.AddCommand(commands.CreateSetterCommand(name))
|
||||
|
||||
@@ -10,7 +10,5 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.8
|
||||
)
|
||||
|
||||
replace sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
|
||||
|
||||
@@ -164,5 +164,7 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.8 h1:RJeaGsiSo1X9qa2pH9NeYZEeK+5/7nIilwfDCIoW3YU=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.8/go.mod h1:tDOfJjL6slQVBLHJ76XfXAFgAOEdfm04AW2HehYOp8k=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
@@ -4,22 +4,27 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/runfn"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// GetCatRunner returns a RunFnRunner.
|
||||
func GetRunFnRunner(name string) *RunFnRunner {
|
||||
r := &RunFnRunner{}
|
||||
c := &cobra.Command{
|
||||
Use: "run DIR",
|
||||
Aliases: []string{"run-fns"},
|
||||
Use: "run [DIR]",
|
||||
Short: commands.RunFnsShort,
|
||||
Long: commands.RunFnsLong,
|
||||
Example: commands.RunFnsExamples,
|
||||
RunE: r.runE,
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRunE: r.preRunE,
|
||||
}
|
||||
fixDocs(name, c)
|
||||
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
|
||||
@@ -27,11 +32,14 @@ func GetRunFnRunner(name string) *RunFnRunner {
|
||||
r.Command = c
|
||||
r.Command.Flags().BoolVar(
|
||||
&r.DryRun, "dry-run", false, "print results to stdout")
|
||||
r.Command.Flags().BoolVar(
|
||||
&r.GlobalScope, "global-scope", false, "set global scope for functions.")
|
||||
r.Command.Flags().StringSliceVar(
|
||||
&r.FnPaths, "fn-path", []string{},
|
||||
"directories containing functions without configuration")
|
||||
r.Command.AddCommand(XArgsCommand())
|
||||
r.Command.AddCommand(WrapCommand())
|
||||
"read functions from these directories instead of the configuration directory.")
|
||||
r.Command.Flags().StringVar(
|
||||
&r.Image, "image", "",
|
||||
"run this image as a function instead of discovering them.")
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -44,13 +52,144 @@ type RunFnRunner struct {
|
||||
IncludeSubpackages bool
|
||||
Command *cobra.Command
|
||||
DryRun bool
|
||||
GlobalScope bool
|
||||
FnPaths []string
|
||||
Image string
|
||||
RunFns runfn.RunFns
|
||||
}
|
||||
|
||||
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
|
||||
rec := runfn.RunFns{Path: args[0], FunctionPaths: r.FnPaths}
|
||||
if r.DryRun {
|
||||
rec.Output = c.OutOrStdout()
|
||||
}
|
||||
return handleError(c, rec.Execute())
|
||||
return handleError(c, r.RunFns.Execute())
|
||||
}
|
||||
|
||||
// getFunctions parses the commandline flags and arguments into explicit
|
||||
// Functions to run.
|
||||
func (r *RunFnRunner) getFunctions(c *cobra.Command, args, dataItems []string) (
|
||||
[]*yaml.RNode, error) {
|
||||
// if image isn't specified, then Functions is empty
|
||||
if r.Image == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// create the function spec to set as an annotation
|
||||
fn, err := yaml.Parse(`container: {}`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: add support network, volumes, etc based on flag values
|
||||
err = fn.PipeE(
|
||||
yaml.Lookup("container"),
|
||||
yaml.SetField("image", yaml.NewScalarRNode(r.Image)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create the function config
|
||||
rc, err := yaml.Parse(`
|
||||
metadata:
|
||||
name: function-input
|
||||
data: {}
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set the function annotation on the function config so it
|
||||
// is parsed by RunFns
|
||||
value, err := fn.String()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rc.PipeE(
|
||||
yaml.LookupCreate(yaml.MappingNode, "metadata", "annotations"),
|
||||
yaml.SetField("config.kubernetes.io/function", yaml.NewScalarRNode(value)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// default the function config kind to ConfigMap, this may be overridden
|
||||
var kind = "ConfigMap"
|
||||
var version = "v1"
|
||||
|
||||
// populate the function config with data. this is a convention for functions
|
||||
// to be more commandline friendly
|
||||
if len(dataItems) > 0 {
|
||||
dataField, err := rc.Pipe(yaml.Lookup("data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, s := range dataItems {
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if i == 0 && len(kv) == 1 {
|
||||
// first argument may be the kind
|
||||
kind = s
|
||||
continue
|
||||
}
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("args must have keys and values separated by =")
|
||||
}
|
||||
err := dataField.PipeE(yaml.SetField(kv[0], yaml.NewScalarRNode(kv[1])))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = rc.PipeE(yaml.SetField("kind", yaml.NewScalarRNode(kind)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rc.PipeE(yaml.SetField("apiVersion", yaml.NewScalarRNode(version)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*yaml.RNode{rc}, nil
|
||||
}
|
||||
|
||||
func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
|
||||
if c.ArgsLenAtDash() >= 0 && r.Image == "" {
|
||||
return errors.Errorf("must specify --image")
|
||||
}
|
||||
|
||||
var dataItems []string
|
||||
if c.ArgsLenAtDash() >= 0 {
|
||||
dataItems = args[c.ArgsLenAtDash():]
|
||||
args = args[:c.ArgsLenAtDash()]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return errors.Errorf("0 or 1 arguments supported, function arguments go after '--'")
|
||||
}
|
||||
|
||||
fns, err := r.getFunctions(c, args, dataItems)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the output to stdout if in dry-run mode or no arguments are specified
|
||||
var output io.Writer
|
||||
var input io.Reader
|
||||
if len(args) == 0 {
|
||||
output = c.OutOrStdout()
|
||||
input = c.InOrStdin()
|
||||
} else if r.DryRun {
|
||||
output = c.OutOrStdout()
|
||||
}
|
||||
|
||||
// set the path if specified as an argument
|
||||
var path string
|
||||
if len(args) == 1 {
|
||||
// argument is the directory
|
||||
path = args[0]
|
||||
}
|
||||
|
||||
r.RunFns = runfn.RunFns{
|
||||
FunctionPaths: r.FnPaths,
|
||||
GlobalScope: r.GlobalScope,
|
||||
Functions: fns,
|
||||
Output: output,
|
||||
Input: input,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
// don't consider args for the function
|
||||
return nil
|
||||
}
|
||||
|
||||
232
cmd/config/internal/commands/run_test.go
Normal file
232
cmd/config/internal/commands/run_test.go
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
|
||||
// flags and arguments into the RunFns structure to be executed.
|
||||
func TestRunFnCommand_preRunE(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
err string
|
||||
path string
|
||||
input io.Reader
|
||||
output io.Writer
|
||||
functionPaths []string
|
||||
}{
|
||||
{
|
||||
name: "config map",
|
||||
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||
path: "dir",
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {a: b, c: d, e: f}
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "config map stdin / stdout",
|
||||
args: []string{"run", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||
input: os.Stdin,
|
||||
output: os.Stdout,
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {a: b, c: d, e: f}
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "config map dry-run",
|
||||
args: []string{"run", "dir", "--image", "foo:bar", "--dry-run", "--", "a=b", "c=d", "e=f"},
|
||||
output: os.Stdout,
|
||||
path: "dir",
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {a: b, c: d, e: f}
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "config map no args",
|
||||
args: []string{"run", "dir", "--image", "foo:bar"},
|
||||
path: "dir",
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {}
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "custom kind",
|
||||
args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h"},
|
||||
path: "dir",
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {g: h}
|
||||
kind: Foo
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "custom kind '=' in data",
|
||||
args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
|
||||
path: "dir",
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {g: h, i: j=k}
|
||||
kind: Foo
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "function paths",
|
||||
args: []string{"run", "dir", "--fn-path", "path1", "--fn-path", "path2"},
|
||||
path: "dir",
|
||||
functionPaths: []string{"path1", "path2"},
|
||||
},
|
||||
{
|
||||
name: "custom kind with function paths",
|
||||
args: []string{
|
||||
"run", "dir", "--fn-path", "path", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
|
||||
path: "dir",
|
||||
functionPaths: []string{"path"},
|
||||
expected: `
|
||||
metadata:
|
||||
name: function-input
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container: {image: 'foo:bar'}
|
||||
data: {g: h, i: j=k}
|
||||
kind: Foo
|
||||
apiVersion: v1
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "config map multi args",
|
||||
args: []string{"run", "dir", "dir2", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
|
||||
err: "0 or 1 arguments supported",
|
||||
},
|
||||
{
|
||||
name: "config map not image",
|
||||
args: []string{"run", "dir", "--", "a=b", "c=d", "e=f"},
|
||||
err: "must specify --image",
|
||||
},
|
||||
{
|
||||
name: "config map bad data",
|
||||
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c", "e=f"},
|
||||
err: "must have keys and values separated by",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := GetRunFnRunner("kustomize")
|
||||
// Don't run the actual command
|
||||
r.Command.Run = nil
|
||||
r.Command.RunE = func(cmd *cobra.Command, args []string) error { return nil }
|
||||
r.Command.SilenceErrors = true
|
||||
r.Command.SilenceUsage = true
|
||||
|
||||
// hack due to https://github.com/spf13/cobra/issues/42
|
||||
root := &cobra.Command{Use: "root"}
|
||||
root.AddCommand(r.Command)
|
||||
root.SetArgs(tt.args)
|
||||
|
||||
// error case
|
||||
err := r.Command.Execute()
|
||||
if tt.err != "" {
|
||||
if !assert.Error(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Contains(t, err.Error(), tt.err) {
|
||||
t.FailNow()
|
||||
}
|
||||
// don't check anything else in error case
|
||||
return
|
||||
}
|
||||
|
||||
// non-error case
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check if Input was set
|
||||
if !assert.Equal(t, tt.input, r.RunFns.Input) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check if Output was set
|
||||
if !assert.Equal(t, tt.output, r.RunFns.Output) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check if Path was set
|
||||
if !assert.Equal(t, tt.path, r.RunFns.Path) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check if FunctionPaths were set
|
||||
if tt.functionPaths == nil {
|
||||
// make Equal work against flag default
|
||||
tt.functionPaths = []string{}
|
||||
}
|
||||
if !assert.Equal(t, tt.functionPaths, r.RunFns.FunctionPaths) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check if Functions were set
|
||||
if tt.expected != "" {
|
||||
if !assert.Len(t, r.RunFns.Functions, 1) {
|
||||
t.FailNow()
|
||||
}
|
||||
actual := strings.TrimSpace(r.RunFns.Functions[0].MustString())
|
||||
if !assert.Equal(t, strings.TrimSpace(tt.expected), actual) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,12 +4,18 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/spf13/cobra v0.0.5
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/cli-runtime v0.17.0
|
||||
k8s.io/client-go v0.17.0
|
||||
k8s.io/component-base v0.17.0 // indirect
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2
|
||||
)
|
||||
|
||||
replace sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
|
||||
replace (
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939 => ../../kstatus
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp
|
||||
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/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
@@ -31,15 +32,23 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
||||
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/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/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/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
|
||||
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.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -51,8 +60,10 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
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/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
@@ -60,6 +71,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
|
||||
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=
|
||||
@@ -68,40 +81,75 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
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-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
|
||||
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
|
||||
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
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.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
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 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/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.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -117,20 +165,30 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/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.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@@ -139,12 +197,15 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/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 v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
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=
|
||||
@@ -165,6 +226,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9
|
||||
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-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
|
||||
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=
|
||||
@@ -186,13 +249,18 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
|
||||
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/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.4.0/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.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
@@ -203,20 +271,24 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
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-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
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/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
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.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
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=
|
||||
@@ -237,19 +309,35 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -257,19 +345,26 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||
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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -281,7 +376,9 @@ 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-20180117170059-2c42eef0765b/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=
|
||||
@@ -290,14 +387,18 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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 h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/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.20171227012246-e19ae1496984/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=
|
||||
@@ -305,17 +406,24 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuA
|
||||
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/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
|
||||
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
|
||||
@@ -327,7 +435,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
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/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
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=
|
||||
@@ -336,10 +446,14 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0/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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
@@ -349,21 +463,30 @@ 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=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
|
||||
k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
|
||||
k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
|
||||
k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY=
|
||||
k8s.io/cli-runtime v0.17.0 h1:XEuStbJBHCQlEKFyTQmceDKEWOSYHZkcYWKp3SsQ9Hk=
|
||||
k8s.io/cli-runtime v0.17.0/go.mod h1:1E5iQpMODZq2lMWLUJELwRu2MLWIzwvMgDBpn3Y81Qo=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
|
||||
k8s.io/client-go v0.0.0-20191214190045-a32a6f7a3052/go.mod h1:tAaoc/sYuIL0+njJefSAmE28CIcxyaFV4kbIujBlY2s=
|
||||
k8s.io/client-go v0.0.0-20191219150334-0b8da7416048/go.mod h1:ZEe8ZASDUAuqVGJ+UN0ka0PfaR+b6a6E1PGsSNZRui8=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao=
|
||||
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
|
||||
k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c=
|
||||
k8s.io/component-base v0.17.0 h1:BnDFcmBDq+RPpxXjmuYnZXb59XNN9CaFrX8ba9+3xrA=
|
||||
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
|
||||
@@ -371,13 +494,17 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
|
||||
k8s.io/gengo v0.0.0-20190822140433-26a664648505/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 v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
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-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd h1:nZX5+wEqTu/EBIYjrZlFOA63z4+Zcy96lDkCZPU9a9c=
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo=
|
||||
k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
@@ -385,9 +512,16 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
|
||||
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2 h1:Rl/wMrnpZzZjsVeFIIOAb92Kz/UfLrTUEXjiHW6oS0o=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2/go.mod h1:rywm/rcR5LmCBghz9956tE45OdUPChFoXVVs+WmhMTI=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
|
||||
|
||||
@@ -6,6 +6,7 @@ package kubectlcobra
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -79,6 +80,8 @@ func updateHelp(names []string, c *cobra.Command) {
|
||||
// NewCmdApply creates the `apply` command
|
||||
func NewCmdApply(baseName string, f util.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := apply.NewApplyOptions(ioStreams)
|
||||
so := newStatusOptions(f, ioStreams)
|
||||
o.PreProcessorFn = PrependGroupingObject(o)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply (-f FILENAME | -k DIRECTORY)",
|
||||
@@ -95,6 +98,10 @@ func NewCmdApply(baseName string, f util.Factory, ioStreams genericclioptions.IO
|
||||
|
||||
cmdutil.CheckErr(o.Complete(f, cmd))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
infos, _ := o.GetObjects()
|
||||
if so.wait {
|
||||
cmdutil.CheckErr(so.waitForStatus(infos))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -102,6 +109,7 @@ func NewCmdApply(baseName string, f util.Factory, ioStreams genericclioptions.IO
|
||||
o.DeleteFlags.AddFlags(cmd)
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
so.AddFlags(cmd)
|
||||
|
||||
o.Overwrite = true
|
||||
|
||||
@@ -112,3 +120,27 @@ func NewCmdApply(baseName string, f util.Factory, ioStreams genericclioptions.IO
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// PrependGroupingObject orders the objects to apply so the "grouping"
|
||||
// object stores the inventory, and it is first to be applied.
|
||||
func PrependGroupingObject(o *apply.ApplyOptions) func() error {
|
||||
return func() error {
|
||||
if o == nil {
|
||||
return fmt.Errorf("ApplyOptions are nil")
|
||||
}
|
||||
infos, err := o.GetObjects()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists := findGroupingObject(infos)
|
||||
if exists {
|
||||
if err := addInventoryToGroupingObj(infos); err != nil {
|
||||
return err
|
||||
}
|
||||
if !sortGroupingObject(infos) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
58
cmd/kubectl/kubectlcobra/commands_test.go
Normal file
58
cmd/kubectl/kubectlcobra/commands_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// package kubectlcobra contains cobra commands from kubectl
|
||||
package kubectlcobra
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/kubectl/pkg/cmd/apply"
|
||||
)
|
||||
|
||||
func TestPrependGroupingObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
infos []*resource.Info
|
||||
}{
|
||||
{
|
||||
infos: []*resource.Info{copyGroupingInfo()},
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod3Info, copyGroupingInfo()},
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, copyGroupingInfo(), pod3Info},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
applyOptions := createApplyOptions(test.infos)
|
||||
f := PrependGroupingObject(applyOptions)
|
||||
err := f()
|
||||
if err != nil {
|
||||
t.Errorf("Error running pre-processor callback: %s", err)
|
||||
}
|
||||
infos, _ := applyOptions.GetObjects()
|
||||
if len(test.infos) != len(infos) {
|
||||
t.Fatalf("Wrong number of objects after prepending grouping object")
|
||||
}
|
||||
groupingInfo := infos[0]
|
||||
if !isGroupingObject(groupingInfo.Object) {
|
||||
t.Fatalf("First object is not the grouping object")
|
||||
}
|
||||
inventory, _ := retrieveInventoryFromGroupingObj(infos)
|
||||
if len(inventory) != (len(infos) - 1) {
|
||||
t.Errorf("Wrong number of inventory items stored in grouping object")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// createApplyOptions is a helper function to assemble the ApplyOptions
|
||||
// with the passed objects (infos).
|
||||
func createApplyOptions(infos []*resource.Info) *apply.ApplyOptions {
|
||||
applyOptions := &apply.ApplyOptions{}
|
||||
applyOptions.SetObjects(infos)
|
||||
return applyOptions
|
||||
}
|
||||
@@ -6,6 +6,10 @@ package kubectlcobra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -13,7 +17,10 @@ import (
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
)
|
||||
|
||||
const GroupingLabel = "kustomize.k8s.io/group-id"
|
||||
const (
|
||||
GroupingLabel = "kustomize.config.k8s.io/inventory-id"
|
||||
GroupingHash = "kustomize.config.k8s.io/inventory-hash"
|
||||
)
|
||||
|
||||
// isGroupingObject returns true if the passed object has the
|
||||
// grouping label.
|
||||
@@ -69,7 +76,8 @@ func sortGroupingObject(infos []*resource.Info) bool {
|
||||
func addInventoryToGroupingObj(infos []*resource.Info) error {
|
||||
|
||||
// Iterate through the objects (infos), creating an Inventory struct
|
||||
// as metadata for the object, or if it's the grouping object, store it.
|
||||
// as metadata for each object, or if it's the grouping object, store it.
|
||||
var groupingInfo *resource.Info
|
||||
var groupingObj *unstructured.Unstructured
|
||||
inventoryMap := map[string]string{}
|
||||
for _, info := range infos {
|
||||
@@ -84,6 +92,7 @@ func addInventoryToGroupingObj(infos []*resource.Info) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("Grouping object is not an Unstructured: %#v", groupingObj)
|
||||
}
|
||||
groupingInfo = info
|
||||
} else {
|
||||
if obj == nil {
|
||||
return fmt.Errorf("Creating inventory; object is nil")
|
||||
@@ -102,11 +111,35 @@ func addInventoryToGroupingObj(infos []*resource.Info) error {
|
||||
if groupingObj == nil {
|
||||
return fmt.Errorf("Grouping object not found")
|
||||
}
|
||||
err := unstructured.SetNestedStringMap(groupingObj.UnstructuredContent(), inventoryMap, "data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(inventoryMap) > 0 {
|
||||
// Adds the inventory map to the ConfigMap "data" section.
|
||||
err := unstructured.SetNestedStringMap(groupingObj.UnstructuredContent(),
|
||||
inventoryMap, "data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Adds the hash of the inventory strings as an annotation to the
|
||||
// grouping object. Inventory strings must be sorted to make hash
|
||||
// deterministic.
|
||||
inventoryList := mapKeysToSlice(inventoryMap)
|
||||
sort.Strings(inventoryList)
|
||||
invHash, err := calcInventoryHash(inventoryList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add the hash as a suffix to the grouping object's name.
|
||||
invHashStr := strconv.FormatUint(uint64(invHash), 16)
|
||||
if err := addSuffixToName(groupingInfo, invHashStr); err != nil {
|
||||
return err
|
||||
}
|
||||
annotations := groupingObj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[GroupingHash] = invHashStr
|
||||
groupingObj.SetAnnotations(annotations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,3 +176,77 @@ func retrieveInventoryFromGroupingObj(infos []*resource.Info) ([]*Inventory, err
|
||||
}
|
||||
return inventory, nil
|
||||
}
|
||||
|
||||
// calcInventoryHash returns an unsigned int32 representing the hash
|
||||
// of the inventory strings. If there is an error writing bytes to
|
||||
// the hash, then the error is returned; nil is returned otherwise.
|
||||
// Used to quickly identify the set of resources in the grouping object.
|
||||
func calcInventoryHash(inv []string) (uint32, error) {
|
||||
h := fnv.New32a()
|
||||
for _, is := range inv {
|
||||
_, err := h.Write([]byte(is))
|
||||
if err != nil {
|
||||
return uint32(0), err
|
||||
}
|
||||
}
|
||||
return h.Sum32(), nil
|
||||
}
|
||||
|
||||
// retrieveInventoryHash takes a grouping object (encapsulated by
|
||||
// a resource.Info), and returns the string representing the hash
|
||||
// of the grouping inventory; returns empty string if the grouping
|
||||
// object is not in Unstructured format, or if the hash annotation
|
||||
// does not exist.
|
||||
func retrieveInventoryHash(groupingInfo *resource.Info) string {
|
||||
var invHash = ""
|
||||
groupingObj, ok := groupingInfo.Object.(*unstructured.Unstructured)
|
||||
if ok {
|
||||
annotations := groupingObj.GetAnnotations()
|
||||
if annotations != nil {
|
||||
invHash = annotations[GroupingHash]
|
||||
}
|
||||
}
|
||||
return invHash
|
||||
}
|
||||
|
||||
// mapKeysToSlice returns the map keys as a slice of strings.
|
||||
func mapKeysToSlice(m map[string]string) []string {
|
||||
s := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
s[i] = k
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// addSuffixToName adds the passed suffix (usually a hash) as a suffix
|
||||
// to the name of the passed object stored in the Info struct. Returns
|
||||
// an error if the object is not "*unstructured.Unstructured" or if the
|
||||
// name stored in the object differs from the name in the Info struct.
|
||||
func addSuffixToName(info *resource.Info, suffix string) error {
|
||||
|
||||
if info == nil {
|
||||
return fmt.Errorf("Nil resource.Info")
|
||||
}
|
||||
suffix = strings.TrimSpace(suffix)
|
||||
if len(suffix) == 0 {
|
||||
return fmt.Errorf("Passed empty suffix")
|
||||
}
|
||||
|
||||
accessor, _ := meta.Accessor(info.Object)
|
||||
name := accessor.GetName()
|
||||
if name != info.Name {
|
||||
return fmt.Errorf("Grouping object (%s) and resource.Info (%s) have different names\n", name, info.Name)
|
||||
}
|
||||
// Error if name alread has suffix.
|
||||
suffix = "-" + suffix
|
||||
if strings.HasSuffix(name, suffix) {
|
||||
return fmt.Errorf("Name already has suffix: %s\n", name)
|
||||
}
|
||||
name += suffix
|
||||
accessor.SetName(name)
|
||||
info.Name = name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
package kubectlcobra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -33,12 +36,6 @@ var groupingObj = unstructured.Unstructured{
|
||||
},
|
||||
}
|
||||
|
||||
var groupingInfo = &resource.Info{
|
||||
Namespace: testNamespace,
|
||||
Name: groupingObjName,
|
||||
Object: &groupingObj,
|
||||
}
|
||||
|
||||
var pod1 = unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -90,6 +87,28 @@ var pod3Info = &resource.Info{
|
||||
Object: &pod3,
|
||||
}
|
||||
|
||||
var nonUnstructuredGroupingObj = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: testNamespace,
|
||||
Name: groupingObjName,
|
||||
Labels: map[string]string{
|
||||
GroupingLabel: "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var nonUnstructuredGroupingInfo = &resource.Info{
|
||||
Namespace: testNamespace,
|
||||
Name: groupingObjName,
|
||||
Object: nonUnstructuredGroupingObj,
|
||||
}
|
||||
|
||||
var nilInfo = &resource.Info{
|
||||
Namespace: testNamespace,
|
||||
Name: groupingObjName,
|
||||
Object: nil,
|
||||
}
|
||||
|
||||
func TestIsGroupingObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
obj runtime.Object
|
||||
@@ -137,7 +156,7 @@ func TestFindGroupingObject(t *testing.T) {
|
||||
name: "",
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo},
|
||||
infos: []*resource.Info{copyGroupingInfo()},
|
||||
exists: true,
|
||||
name: groupingObjName,
|
||||
},
|
||||
@@ -152,7 +171,7 @@ func TestFindGroupingObject(t *testing.T) {
|
||||
name: "",
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info},
|
||||
infos: []*resource.Info{pod1Info, pod2Info, copyGroupingInfo(), pod3Info},
|
||||
exists: true,
|
||||
name: groupingObjName,
|
||||
},
|
||||
@@ -182,7 +201,7 @@ func TestSortGroupingObject(t *testing.T) {
|
||||
sorted: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo},
|
||||
infos: []*resource.Info{copyGroupingInfo()},
|
||||
sorted: true,
|
||||
},
|
||||
{
|
||||
@@ -194,23 +213,23 @@ func TestSortGroupingObject(t *testing.T) {
|
||||
sorted: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, pod1Info},
|
||||
infos: []*resource.Info{copyGroupingInfo(), pod1Info},
|
||||
sorted: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, groupingInfo},
|
||||
infos: []*resource.Info{pod1Info, copyGroupingInfo()},
|
||||
sorted: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info},
|
||||
infos: []*resource.Info{pod1Info, pod2Info, copyGroupingInfo(), pod3Info},
|
||||
sorted: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, pod3Info, groupingInfo},
|
||||
infos: []*resource.Info{pod1Info, pod2Info, pod3Info, copyGroupingInfo()},
|
||||
sorted: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, pod1Info, pod2Info, pod3Info},
|
||||
infos: []*resource.Info{copyGroupingInfo(), pod1Info, pod2Info, pod3Info},
|
||||
sorted: true,
|
||||
},
|
||||
}
|
||||
@@ -250,24 +269,33 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
},
|
||||
// Grouping object without other objects is OK.
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo},
|
||||
infos: []*resource.Info{copyGroupingInfo(), nilInfo},
|
||||
isError: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{nonUnstructuredGroupingInfo},
|
||||
isError: true,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{copyGroupingInfo()},
|
||||
expected: []*Inventory{},
|
||||
isError: false,
|
||||
},
|
||||
// More than one grouping object is an error.
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, groupingInfo},
|
||||
infos: []*resource.Info{copyGroupingInfo(), copyGroupingInfo()},
|
||||
expected: []*Inventory{},
|
||||
isError: true,
|
||||
},
|
||||
// More than one grouping object is an error.
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, pod1Info, groupingInfo},
|
||||
infos: []*resource.Info{copyGroupingInfo(), pod1Info, copyGroupingInfo()},
|
||||
expected: []*Inventory{},
|
||||
isError: true,
|
||||
},
|
||||
// Basic test case: one grouping object, one pod.
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, pod1Info},
|
||||
infos: []*resource.Info{copyGroupingInfo(), pod1Info},
|
||||
expected: []*Inventory{
|
||||
&Inventory{
|
||||
Namespace: testNamespace,
|
||||
@@ -281,7 +309,7 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, groupingInfo},
|
||||
infos: []*resource.Info{pod1Info, copyGroupingInfo()},
|
||||
expected: []*Inventory{
|
||||
&Inventory{
|
||||
Namespace: testNamespace,
|
||||
@@ -295,7 +323,7 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info},
|
||||
infos: []*resource.Info{pod1Info, pod2Info, copyGroupingInfo(), pod3Info},
|
||||
expected: []*Inventory{
|
||||
&Inventory{
|
||||
Namespace: testNamespace,
|
||||
@@ -325,7 +353,7 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{pod1Info, pod2Info, pod3Info, groupingInfo},
|
||||
infos: []*resource.Info{pod1Info, pod2Info, pod3Info, copyGroupingInfo()},
|
||||
expected: []*Inventory{
|
||||
&Inventory{
|
||||
Namespace: testNamespace,
|
||||
@@ -355,7 +383,7 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
infos: []*resource.Info{groupingInfo, pod1Info, pod2Info, pod3Info},
|
||||
infos: []*resource.Info{copyGroupingInfo(), pod1Info, pod2Info, pod3Info},
|
||||
expected: []*Inventory{
|
||||
&Inventory{
|
||||
Namespace: testNamespace,
|
||||
@@ -393,28 +421,117 @@ func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
|
||||
}
|
||||
if !test.isError {
|
||||
if err != nil {
|
||||
t.Errorf("Received error when expecting none (%s)\n", err)
|
||||
} else {
|
||||
retrieved, err := retrieveInventoryFromGroupingObj(test.infos)
|
||||
if err != nil {
|
||||
t.Errorf("Error retrieving inventory: %s\n", err)
|
||||
}
|
||||
if len(test.expected) != len(retrieved) {
|
||||
t.Errorf("Expected inventory for %d resources, actual %d",
|
||||
len(test.expected), len(retrieved))
|
||||
}
|
||||
for _, expected := range test.expected {
|
||||
found := false
|
||||
for _, actual := range retrieved {
|
||||
if expected.Equals(actual) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected inventory (%s) not found", expected)
|
||||
t.Fatalf("Received error when expecting none (%s)\n", err)
|
||||
}
|
||||
retrieved, err := retrieveInventoryFromGroupingObj(test.infos)
|
||||
if err != nil {
|
||||
t.Fatalf("Error retrieving inventory: %s\n", err)
|
||||
}
|
||||
if len(test.expected) != len(retrieved) {
|
||||
t.Errorf("Expected inventory for %d resources, actual %d",
|
||||
len(test.expected), len(retrieved))
|
||||
}
|
||||
for _, expected := range test.expected {
|
||||
found := false
|
||||
for _, actual := range retrieved {
|
||||
if expected.Equals(actual) {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected inventory (%s) not found", expected)
|
||||
}
|
||||
}
|
||||
// If the grouping object has an inventory, check the
|
||||
// grouping object has an inventory hash.
|
||||
groupingInfo, exists := findGroupingObject(test.infos)
|
||||
if exists && len(test.expected) > 0 {
|
||||
invHash := retrieveInventoryHash(groupingInfo)
|
||||
if len(invHash) == 0 {
|
||||
t.Errorf("Grouping object missing inventory hash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSuffixToName(t *testing.T) {
|
||||
tests := []struct {
|
||||
info *resource.Info
|
||||
suffix string
|
||||
expected string
|
||||
isError bool
|
||||
}{
|
||||
// Nil info should return error.
|
||||
{
|
||||
info: nil,
|
||||
suffix: "",
|
||||
expected: "",
|
||||
isError: true,
|
||||
},
|
||||
// Empty suffix should return error.
|
||||
{
|
||||
info: copyGroupingInfo(),
|
||||
suffix: "",
|
||||
expected: "",
|
||||
isError: true,
|
||||
},
|
||||
// Empty suffix should return error.
|
||||
{
|
||||
info: copyGroupingInfo(),
|
||||
suffix: " \t",
|
||||
expected: "",
|
||||
isError: true,
|
||||
},
|
||||
{
|
||||
info: copyGroupingInfo(),
|
||||
suffix: "hashsuffix",
|
||||
expected: groupingObjName + "-hashsuffix",
|
||||
isError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
//t.Errorf("%#v [%s]", test.info, test.suffix)
|
||||
err := addSuffixToName(test.info, test.suffix)
|
||||
if test.isError {
|
||||
if err == nil {
|
||||
t.Errorf("Should have produced an error, but returned none.")
|
||||
}
|
||||
}
|
||||
if !test.isError {
|
||||
if err != nil {
|
||||
t.Fatalf("Received error when expecting none (%s)\n", err)
|
||||
}
|
||||
actualName, err := getObjectName(test.info.Object)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting object name: %s", err)
|
||||
}
|
||||
if actualName != test.info.Name {
|
||||
t.Errorf("Object name (%s) does not match info name (%s)\n", actualName, test.info.Name)
|
||||
}
|
||||
if test.expected != actualName {
|
||||
t.Errorf("Expected name (%s), got (%s)\n", test.expected, actualName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getObjectName(obj runtime.Object) (string, error) {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Grouping object is not Unstructured format")
|
||||
}
|
||||
return u.GetName(), nil
|
||||
}
|
||||
|
||||
func copyGroupingInfo() *resource.Info {
|
||||
groupingObjCopy := groupingObj.DeepCopy()
|
||||
var groupingInfo = &resource.Info{
|
||||
Namespace: testNamespace,
|
||||
Name: groupingObjName,
|
||||
Object: groupingObjCopy,
|
||||
}
|
||||
return groupingInfo
|
||||
}
|
||||
|
||||
105
cmd/kubectl/kubectlcobra/status.go
Normal file
105
cmd/kubectl/kubectlcobra/status.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubectlcobra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/kustomize/kstatus/wait"
|
||||
)
|
||||
|
||||
type StatusOptions struct {
|
||||
factory util.Factory
|
||||
ioStreams genericclioptions.IOStreams
|
||||
|
||||
wait bool
|
||||
period time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newStatusOptions(factory util.Factory, ioStreams genericclioptions.IOStreams) *StatusOptions {
|
||||
return &StatusOptions{
|
||||
factory: factory,
|
||||
ioStreams: ioStreams,
|
||||
|
||||
wait: false,
|
||||
period: 2 * time.Second,
|
||||
timeout: 1 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StatusOptions) AddFlags(c *cobra.Command) {
|
||||
c.Flags().BoolVar(&s.wait, "status", s.wait, "Wait for all applied resources to reach the Current status.")
|
||||
c.Flags().DurationVar(&s.period, "status-period", s.period, "Polling period for resource statuses.")
|
||||
c.Flags().DurationVar(&s.timeout, "status-timeout", s.timeout, "Timeout threshold for waiting for all resources to reach the Current status.")
|
||||
}
|
||||
|
||||
func (s *StatusOptions) waitForStatus(infos []*resource.Info) error {
|
||||
mapper, err := getRESTMapper(s.factory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := getClient(s.factory, mapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
|
||||
resolver := wait.NewResolver(c, mapper, s.period)
|
||||
ch := resolver.WaitForStatus(ctx, infosToResourceIdentifiers(infos))
|
||||
|
||||
for msg := range ch {
|
||||
switch msg.Type {
|
||||
case wait.ResourceUpdate:
|
||||
id := msg.EventResource.ResourceIdentifier
|
||||
gk := id.GroupKind
|
||||
fmt.Fprintf(s.ioStreams.Out, "%s/%s is %s: %s\n", strings.ToLower(gk.String()), id.Name, msg.EventResource.Status.String(), msg.EventResource.Message)
|
||||
case wait.Completed:
|
||||
fmt.Fprint(s.ioStreams.Out, "all resources has reached the Current status\n")
|
||||
case wait.Aborted:
|
||||
fmt.Fprintf(s.ioStreams.Out, "resources failed to the reached Current status after %s\n", s.timeout.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func infosToResourceIdentifiers(infos []*resource.Info) []wait.ResourceIdentifier {
|
||||
var resources []wait.ResourceIdentifier
|
||||
for _, info := range infos {
|
||||
u := info.Object.(*unstructured.Unstructured)
|
||||
resources = append(resources, wait.ResourceIdentifier{
|
||||
GroupKind: u.GroupVersionKind().GroupKind(),
|
||||
Namespace: u.GetNamespace(),
|
||||
Name: u.GetName(),
|
||||
})
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func getRESTMapper(f util.Factory) (meta.RESTMapper, error) {
|
||||
return f.ToRESTMapper()
|
||||
}
|
||||
|
||||
func getClient(f util.Factory, mapper meta.RESTMapper) (client.Reader, error) {
|
||||
config, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.New(config, client.Options{Scheme: scheme.Scheme, Mapper: mapper})
|
||||
}
|
||||
@@ -7,10 +7,9 @@ require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/client-go v0.17.0
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/kustomize/kstatus v0.0.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0
|
||||
|
||||
@@ -131,6 +131,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-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/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
@@ -151,6 +152,7 @@ github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/
|
||||
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-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/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.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
@@ -277,6 +279,7 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -303,8 +306,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -329,6 +330,7 @@ golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -338,6 +340,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -398,13 +401,19 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
@@ -418,8 +427,8 @@ k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKf
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d h1:1P0iBJsBzxRmR+dIFnM+Iu4aLxnoa7lBqozW/0uHbT8=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/kustomize/cmd/resource/status/generateddocs/commands"
|
||||
"sigs.k8s.io/kustomize/kstatus/wait"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
// GetEventsRunner returns a command EventsRunner.
|
||||
func GetEventsRunner() *EventsRunner {
|
||||
r := &EventsRunner{
|
||||
createClientFunc: createClient,
|
||||
newResolverFunc: newResolver,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "events DIR...",
|
||||
@@ -49,18 +48,16 @@ type EventsRunner struct {
|
||||
Timeout time.Duration
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
newResolverFunc newResolverFunc
|
||||
}
|
||||
|
||||
func (r *EventsRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a client and use it to set up a new resolver.
|
||||
client, err := r.createClientFunc()
|
||||
resolver, err := r.newResolverFunc(r.Interval)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating client")
|
||||
return errors.Wrap(err, "error creating resolver")
|
||||
}
|
||||
resolver := wait.NewResolver(client, r.Interval)
|
||||
|
||||
// Set up a CaptureIdentifierFilter and run all inputs through the
|
||||
// filter with the pipeline to capture the inventory of resources
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
"sigs.k8s.io/kustomize/kstatus/wait"
|
||||
)
|
||||
@@ -20,7 +22,7 @@ func TestEventsNoResources(t *testing.T) {
|
||||
fakeClient := &FakeClient{}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -64,7 +66,7 @@ metadata:
|
||||
}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -141,7 +143,8 @@ items:
|
||||
}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, corev1.SchemeGroupVersion.WithKind("Pod"),
|
||||
corev1.SchemeGroupVersion.WithKind("Service"))
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// GetFetchRunner returns a command FetchRunner.
|
||||
func GetFetchRunner() *FetchRunner {
|
||||
r := &FetchRunner{
|
||||
createClientFunc: createClient,
|
||||
newResolverFunc: newResolver,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "fetch DIR...",
|
||||
@@ -44,20 +44,17 @@ type FetchRunner struct {
|
||||
IncludeSubpackages bool
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
newResolverFunc newResolverFunc
|
||||
}
|
||||
|
||||
func (r *FetchRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a new client and use it to set up a resolver.
|
||||
k8sClient, err := r.createClientFunc()
|
||||
resolver, err := r.newResolverFunc(time.Minute)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating k8sClient")
|
||||
return errors.Wrap(err, "error creating resolver")
|
||||
}
|
||||
|
||||
resolver := wait.NewResolver(k8sClient, time.Minute)
|
||||
|
||||
// Set up a CaptureIdentifierFilter and run all inputs through the
|
||||
// filter with the pipeline to capture the inventory of resources
|
||||
// which we are interested in.
|
||||
@@ -108,7 +105,7 @@ func (f FetchStatusInfo) CurrentStatus() StatusData {
|
||||
var resourceData []ResourceStatusData
|
||||
for _, res := range f.Results {
|
||||
rsd := ResourceStatusData{
|
||||
Identifier: res.Resource,
|
||||
Identifier: res.ResourceIdentifier,
|
||||
}
|
||||
if res.Error != nil {
|
||||
rsd.Status = status.UnknownStatus
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestEmptyManifest(t *testing.T) {
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme)
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -65,7 +65,7 @@ metadata:
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme, deployment)
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -78,7 +78,7 @@ metadata:
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
expectedResource := ResourceIdentifier{
|
||||
apiVersion: "apps/v1",
|
||||
apiVersion: "apps",
|
||||
kind: "Deployment",
|
||||
namespace: "default",
|
||||
name: "bar",
|
||||
@@ -139,7 +139,8 @@ metadata:
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"),
|
||||
v1.SchemeGroupVersion.WithKind("Service"))
|
||||
r.Command.SetArgs([]string{d})
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
@@ -152,7 +153,7 @@ metadata:
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
expectedDeploymentResource := ResourceIdentifier{
|
||||
apiVersion: "apps/v1",
|
||||
apiVersion: "apps",
|
||||
kind: "Deployment",
|
||||
namespace: "default",
|
||||
name: "foo",
|
||||
@@ -162,7 +163,7 @@ metadata:
|
||||
verifyOutputContains(t, tableOutput, expectedDeploymentResource, expectedDeploymentStatus, expectedDeploymentMessage)
|
||||
|
||||
expectedServiceResource := ResourceIdentifier{
|
||||
apiVersion: "v1",
|
||||
apiVersion: "",
|
||||
kind: "Service",
|
||||
namespace: "default",
|
||||
name: "foo",
|
||||
|
||||
@@ -6,11 +6,15 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
"sigs.k8s.io/kustomize/kstatus/wait"
|
||||
)
|
||||
|
||||
type TableOutput struct {
|
||||
@@ -232,10 +236,25 @@ func (f *FakeClient) Get(_ context.Context, _ client.ObjectKey, obj runtime.Obje
|
||||
return callbackFunc(u)
|
||||
}
|
||||
|
||||
func (f *FakeClient) List(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
|
||||
func (f *FakeClient) List(context.Context, runtime.Object, ...client.ListOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKind) newResolverFunc {
|
||||
return func(pollInterval time.Duration) (*wait.Resolver, error) {
|
||||
var groupVersions []schema.GroupVersion
|
||||
for _, gvk := range mapperTypes {
|
||||
groupVersions = append(groupVersions, gvk.GroupVersion())
|
||||
}
|
||||
mapper := meta.NewDefaultRESTMapper(groupVersions)
|
||||
for _, gvk := range mapperTypes {
|
||||
mapper.Add(gvk, meta.RESTScopeNamespace)
|
||||
}
|
||||
|
||||
return wait.NewResolver(fakeClient, mapper, pollInterval), nil
|
||||
}
|
||||
}
|
||||
|
||||
func joinStatuses(statuses []status.Status) string {
|
||||
var stringStatuses []string
|
||||
for _, s := range statuses {
|
||||
|
||||
@@ -61,8 +61,7 @@ var (
|
||||
width: 25,
|
||||
colorFunc: defaultColorFunc,
|
||||
contentFunc: func(data ResourceStatusData) string {
|
||||
return fmt.Sprintf("%s/%s", data.Identifier.GetAPIVersion(),
|
||||
data.Identifier.GetKind())
|
||||
return fmt.Sprintf("%s/%s", data.Identifier.GroupKind.Group, data.Identifier.GroupKind.Kind)
|
||||
},
|
||||
},
|
||||
namespaceColumn: {
|
||||
@@ -70,7 +69,7 @@ var (
|
||||
width: 15,
|
||||
colorFunc: defaultColorFunc,
|
||||
contentFunc: func(data ResourceStatusData) string {
|
||||
return data.Identifier.GetNamespace()
|
||||
return data.Identifier.Namespace
|
||||
},
|
||||
},
|
||||
nameColumn: {
|
||||
@@ -78,7 +77,7 @@ var (
|
||||
width: 20,
|
||||
colorFunc: defaultColorFunc,
|
||||
contentFunc: func(data ResourceStatusData) string {
|
||||
return data.Identifier.GetName()
|
||||
return data.Identifier.Name
|
||||
},
|
||||
},
|
||||
statusColumn: {
|
||||
@@ -255,8 +254,8 @@ var (
|
||||
width: 20,
|
||||
requireResourceUpdateEvent: true,
|
||||
contentFunc: func(event wait.Event) string {
|
||||
return fmt.Sprintf("%s/%s", event.EventResource.Identifier.GetAPIVersion(),
|
||||
event.EventResource.Identifier.GetKind())
|
||||
return fmt.Sprintf("%s/%s", event.EventResource.ResourceIdentifier.GroupKind.Group,
|
||||
event.EventResource.ResourceIdentifier.GroupKind.Kind)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -264,7 +263,7 @@ var (
|
||||
width: 15,
|
||||
requireResourceUpdateEvent: true,
|
||||
contentFunc: func(event wait.Event) string {
|
||||
return event.EventResource.Identifier.GetNamespace()
|
||||
return event.EventResource.ResourceIdentifier.Namespace
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -272,7 +271,7 @@ var (
|
||||
width: 20,
|
||||
requireResourceUpdateEvent: true,
|
||||
contentFunc: func(event wait.Event) string {
|
||||
return event.EventResource.Identifier.GetName()
|
||||
return event.EventResource.ResourceIdentifier.Name
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -22,23 +25,23 @@ func init() {
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
type createClientFunc func() (client.Reader, error)
|
||||
type newResolverFunc func(pollInterval time.Duration) (*wait.Resolver, error)
|
||||
|
||||
// createClient returns a client for talking to a Kubernetes cluster. The client
|
||||
// is from controller-runtime.
|
||||
func createClient() (client.Reader, error) {
|
||||
// newResolver returns a new resolver that can resolve status for resources based
|
||||
// on polling the cluster.
|
||||
func newResolver(pollInterval time.Duration) (*wait.Resolver, error) {
|
||||
config := ctrl.GetConfigOrDie()
|
||||
mapper, err := apiutil.NewDiscoveryRESTMapper(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.New(config, client.Options{Scheme: scheme, Mapper: mapper})
|
||||
}
|
||||
|
||||
func newClientFunc(c client.Reader) func() (client.Reader, error) {
|
||||
return func() (client.Reader, error) {
|
||||
return c, nil
|
||||
c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wait.NewResolver(c, mapper, pollInterval), nil
|
||||
}
|
||||
|
||||
// CaptureIdentifiersFilter implements the Filter interface in the kio package. It
|
||||
@@ -55,8 +58,20 @@ func (f *CaptureIdentifiersFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(mortent): Update kyaml library
|
||||
id := meta.GetIdentifier()
|
||||
f.Identifiers = append(f.Identifiers, &id)
|
||||
gv, err := schema.ParseGroupVersion(id.APIVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Identifiers = append(f.Identifiers, wait.ResourceIdentifier{
|
||||
Name: id.Name,
|
||||
Namespace: id.Namespace,
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: gv.Group,
|
||||
Kind: id.Kind,
|
||||
},
|
||||
})
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
// GetWaitRunner return a command WaitRunner.
|
||||
func GetWaitRunner() *WaitRunner {
|
||||
r := &WaitRunner{
|
||||
createClientFunc: createClient,
|
||||
newResolverFunc: newResolver,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "wait DIR...",
|
||||
@@ -51,7 +51,7 @@ type WaitRunner struct {
|
||||
Timeout time.Duration
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
newResolverFunc newResolverFunc
|
||||
}
|
||||
|
||||
// runE implements the logic of the command and will call the Wait command in the wait
|
||||
@@ -59,12 +59,11 @@ type WaitRunner struct {
|
||||
// TablePrinter to display the information.
|
||||
func (r *WaitRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
client, err := r.createClientFunc()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating client")
|
||||
}
|
||||
|
||||
resolver := wait.NewResolver(client, r.Interval)
|
||||
resolver, err := r.newResolverFunc(r.Interval)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "errors creating resolver")
|
||||
}
|
||||
|
||||
captureFilter := &CaptureIdentifiersFilter{}
|
||||
filters := []kio.Filter{captureFilter}
|
||||
@@ -131,10 +130,9 @@ func (r *ResourceStatusCollector) updateResourceStatus(msg wait.Event) {
|
||||
r.AggregateStatus = msg.AggregateStatus
|
||||
eventResource := msg.EventResource
|
||||
for _, resourceState := range r.ResourceStatuses {
|
||||
if resourceState.Identifier.GetAPIVersion() == eventResource.Identifier.GetAPIVersion() &&
|
||||
resourceState.Identifier.GetKind() == eventResource.Identifier.GetKind() &&
|
||||
resourceState.Identifier.GetNamespace() == eventResource.Identifier.GetNamespace() &&
|
||||
resourceState.Identifier.GetName() == eventResource.Identifier.GetName() {
|
||||
if resourceState.Identifier.GroupKind == eventResource.ResourceIdentifier.GroupKind &&
|
||||
resourceState.Identifier.Namespace == eventResource.ResourceIdentifier.Namespace &&
|
||||
resourceState.Identifier.Name == eventResource.ResourceIdentifier.Name {
|
||||
resourceState.Status = eventResource.Status
|
||||
resourceState.Message = eventResource.Message
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ func TestWaitNoResources(t *testing.T) {
|
||||
fakeClient := &FakeClient{}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -72,7 +74,7 @@ metadata:
|
||||
}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, appsv1.SchemeGroupVersion.WithKind("Deployment"))
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
@@ -144,7 +146,8 @@ items:
|
||||
}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.newResolverFunc = fakeResolver(fakeClient, corev1.SchemeGroupVersion.WithKind("Pod"),
|
||||
corev1.SchemeGroupVersion.WithKind("Service"))
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
@@ -44,7 +44,7 @@ What transformations (customizations) should be applied?
|
||||
| Field | Type | Explanation |
|
||||
|---|---|---|
|
||||
| [commonLabels](#commonlabels) | string | Adds labels and some corresponding label selectors to all resources. |
|
||||
| [commonAnnotations](#commonannotations) | string | Adds annotions (non-identifying metadata) to add all resources. |
|
||||
| [commonAnnotations](#commonannotations) | string | Adds annotations (non-identifying metadata) to add all resources. |
|
||||
| [images](#images) | list | Images modify the name, tags and/or digest for images without creating patches. |
|
||||
| [inventory](#inventory) | struct | Specify an object who's annotations will contain a build result summary. |
|
||||
| [namespace](#namespace) | string | Adds namespace to all resources |
|
||||
|
||||
@@ -22,7 +22,7 @@ Steps:
|
||||
|
||||
First define a place to work:
|
||||
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease -->
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease @testE2EAgainstLatestRelease-->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
@@ -44,7 +44,7 @@ To keep this document shorter, the base resources are
|
||||
off in a supplemental data directory rather than
|
||||
declared here as HERE documents. Download them:
|
||||
|
||||
<!-- @downloadBase @testAgainstLatestRelease -->
|
||||
<!-- @downloadBase @testAgainstLatestRelease @testE2EAgainstLatestRelease-->
|
||||
```
|
||||
BASE=$DEMO_HOME/base
|
||||
mkdir -p $BASE
|
||||
@@ -309,3 +309,31 @@ To deploy, pipe the above commands to kubectl apply:
|
||||
> kustomize build $OVERLAYS/production |\
|
||||
> kubectl apply -f -
|
||||
> ```
|
||||
|
||||
[Alpha] To do end to end tests using kustomize, use the following commands on any folder. You should have GOPATH set up and "kind" installed(https://github.com/kubernetes-sigs/kind).
|
||||
|
||||
<!-- @e2eTest @testE2EAgainstLatestRelease -->
|
||||
```
|
||||
MYGOBIN=$GOPATH/bin
|
||||
kind delete cluster;
|
||||
kind create cluster;
|
||||
$MYGOBIN/kustomize build $BASE | kubectl apply -f -;
|
||||
status=$(mktemp);
|
||||
$MYGOBIN/resource status events $BASE #Waits for all transient events to finish
|
||||
$MYGOBIN/resource status fetch $BASE > $status
|
||||
|
||||
test 1 == \
|
||||
$(grep "apps/v1/Deployment" $status | grep "Deployment is available. Replicas: 3" | wc -l); \
|
||||
echo $?
|
||||
|
||||
test 1 == \
|
||||
$(grep "v1/ConfigMap" $status | grep "Resource is always ready" | wc -l); \
|
||||
echo $?
|
||||
|
||||
test 1 == \
|
||||
$(grep "v1/Service" $status | grep "Service is ready" | wc -l); \
|
||||
echo $?
|
||||
|
||||
$MYGOBIN/kustomize build $BASE | kubectl delete -f -;
|
||||
kind delete cluster;
|
||||
```
|
||||
@@ -48,14 +48,14 @@ go get sigs.k8s.io/kustomize/v3/cmd/kustomize
|
||||
|
||||
* [hello world](helloWorld.md) - 部署多个不同配置的 Hello World 服务。
|
||||
|
||||
* [LDAP](../ldap/README.md) - 部署多个配置不同的 LDAP 服务。
|
||||
* [LDAP](ldap.md) - 部署多个配置不同的 LDAP 服务。
|
||||
|
||||
* [springboot](../springboot/README.md) - 从头开始创建一个 Spring Boot 项目的生产配置。
|
||||
* [springboot](springboot.md) - 从头开始创建一个 Spring Boot 项目的生产配置。
|
||||
|
||||
* [mySql](../mySql/README.md) - 从头开始创建一个 MySQL 的生产配置。
|
||||
* [mySql](mysql.md) - 从头开始创建一个 MySQL 的生产配置。
|
||||
|
||||
* [breakfast](../breakfast.md) - 给 Alice 和 Bob 定制一顿早餐 :)
|
||||
* [breakfast](breakfast.md) - 给 Alice 和 Bob 定制一顿早餐 :)
|
||||
|
||||
* [multibases](../multibases/README.md) - 使用相同的 base 生成三个 variants(dev,staging,production)。
|
||||
* [multibases](multibases.md) - 使用相同的 base 生成三个 variants(dev,staging,production)。
|
||||
|
||||
>声明:部分文档可能稍微滞后于英文版本,同步工作持续进行中
|
||||
118
examples/zh/breakfast.md
Normal file
118
examples/zh/breakfast.md
Normal file
@@ -0,0 +1,118 @@
|
||||
[kubernetes API 对象样式]: https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields
|
||||
[variant]: ../../docs/glossary.md#variant
|
||||
|
||||
# 示例:早餐配置
|
||||
|
||||
定义一个工作空间:
|
||||
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
创建目录用于存放早餐的 base 配置:
|
||||
|
||||
<!-- @baseDir @testAgainstLatestRelease -->
|
||||
```
|
||||
mkdir -p $DEMO_HOME/breakfast/base
|
||||
```
|
||||
|
||||
创建一个 `kustomization` 来定义早餐所需的食物。包含咖啡和薄煎饼:
|
||||
|
||||
<!-- @baseKustomization @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/breakfast/base/kustomization.yaml
|
||||
resources:
|
||||
- coffee.yaml
|
||||
- pancakes.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
这里有一个 _coffee_ 类型。定义`kind`和 `metdata/name` 字段以符合 [kubernetes API 对象样式],不需要其他文件或定义:
|
||||
|
||||
<!-- @coffee @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/breakfast/base/coffee.yaml
|
||||
kind: Coffee
|
||||
metadata:
|
||||
name: morningCup
|
||||
temperature: lukewarm
|
||||
data:
|
||||
greeting: "Good Morning!"
|
||||
EOF
|
||||
```
|
||||
|
||||
`name` 字段仅将这种咖啡实例与其他实例(如果有的话)区分开
|
||||
|
||||
同样,定义 _pancakes_:
|
||||
<!-- @pancakes @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/breakfast/base/pancakes.yaml
|
||||
kind: Pancakes
|
||||
metadata:
|
||||
name: comfort
|
||||
stacksize: 3
|
||||
topping: none
|
||||
EOF
|
||||
```
|
||||
|
||||
为喜欢热咖啡的 Alice 定制她的早餐:
|
||||
|
||||
<!-- @aliceOverlay @testAgainstLatestRelease -->
|
||||
```
|
||||
mkdir -p $DEMO_HOME/breakfast/overlays/alice
|
||||
|
||||
cat <<EOF >$DEMO_HOME/breakfast/overlays/alice/kustomization.yaml
|
||||
commonLabels:
|
||||
who: alice
|
||||
resources:
|
||||
- ../../base
|
||||
patchesStrategicMerge:
|
||||
- temperature.yaml
|
||||
EOF
|
||||
|
||||
cat <<EOF >$DEMO_HOME/breakfast/overlays/alice/temperature.yaml
|
||||
kind: Coffee
|
||||
metadata:
|
||||
name: morningCup
|
||||
temperature: hot!
|
||||
EOF
|
||||
```
|
||||
|
||||
同样的,Bob 想要 _5_ 块薄煎饼和草莓:
|
||||
|
||||
<!-- @bobOverlay @testAgainstLatestRelease -->
|
||||
```
|
||||
mkdir -p $DEMO_HOME/breakfast/overlays/bob
|
||||
|
||||
cat <<EOF >$DEMO_HOME/breakfast/overlays/bob/kustomization.yaml
|
||||
commonLabels:
|
||||
who: bob
|
||||
resources:
|
||||
- ../../base
|
||||
patchesStrategicMerge:
|
||||
- topping.yaml
|
||||
EOF
|
||||
|
||||
cat <<EOF >$DEMO_HOME/breakfast/overlays/bob/topping.yaml
|
||||
kind: Pancakes
|
||||
metadata:
|
||||
name: comfort
|
||||
stacksize: 5
|
||||
topping: strawberries
|
||||
EOF
|
||||
```
|
||||
|
||||
现在,可以为 Alice 的早餐生成配置了:
|
||||
|
||||
<!-- @generateAlice @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME/breakfast/overlays/alice
|
||||
```
|
||||
|
||||
同样的,也为 Bob 的早餐生成配置:
|
||||
|
||||
<!-- @generateBob @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME/breakfast/overlays/bob
|
||||
```
|
||||
269
examples/zh/ldap.md
Normal file
269
examples/zh/ldap.md
Normal file
@@ -0,0 +1,269 @@
|
||||
[base]: ../../docs/glossary.md#base
|
||||
[gitops]: ../../docs/glossary.md#gitops
|
||||
[kustomization]: ../../docs/glossary.md#kustomization
|
||||
[overlay]: ../../docs/glossary.md#overlay
|
||||
[overlays]: ../../docs/glossary.md#overlay
|
||||
[variant]: ../../docs/glossary.md#variant
|
||||
[variants]: ../../docs/glossary.md#variant
|
||||
|
||||
# 示例:LDAP 服务
|
||||
|
||||
步骤:
|
||||
|
||||
1. 拉取已经存在的 [base] 配置
|
||||
2. 进行配置
|
||||
3. 基于 [base] 创建2个不同的 [overlays] (_staging_ 和 _production_)
|
||||
4. 运行 kustomize 或 kubectl 部署 staging 和 production
|
||||
|
||||
首先创建一个工作空间:
|
||||
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
> ```
|
||||
> DEMO_HOME=~/ldap
|
||||
> ```
|
||||
|
||||
## 创建 base
|
||||
|
||||
要使用 [overlays] 创建 [variant],首先需要创建一个 [base]。
|
||||
|
||||
为了保证文档的精简,基础资源都在补充目录中,如果需要请下载它们:
|
||||
|
||||
<!-- @downloadBase @testAgainstLatestRelease -->
|
||||
```
|
||||
BASE=$DEMO_HOME/base
|
||||
mkdir -p $BASE
|
||||
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/ldap"
|
||||
|
||||
curl -s -o "$BASE/#1" "$CONTENT/base\
|
||||
/{deployment.yaml,kustomization.yaml,service.yaml,env.startup.txt}"
|
||||
```
|
||||
|
||||
检查这个目录:
|
||||
|
||||
<!-- @runTree @testAgainstLatestRelease -->
|
||||
```
|
||||
tree $DEMO_HOME
|
||||
```
|
||||
|
||||
将会看到如下文件:
|
||||
|
||||
> ```
|
||||
> /tmp/tmp.IyYQQlHaJP
|
||||
> └── base
|
||||
> ├── deployment.yaml
|
||||
> ├── env.startup.txt
|
||||
> ├── kustomization.yaml
|
||||
> └── service.yaml
|
||||
> ```
|
||||
|
||||
这些资源可以由 kubectl 立刻部署到集群上来实例化 _ldap_ 服务:
|
||||
|
||||
> ```
|
||||
> kubectl apply -f $DEMO_HOME/base
|
||||
> ```
|
||||
|
||||
注意 `kubectl -f` 只能识别 k8s 资源文件。
|
||||
|
||||
### The Base Kustomization
|
||||
|
||||
`base` 目录包含一个 [kustomization] 文件:
|
||||
|
||||
<!-- @showKustomization @testAgainstLatestRelease -->
|
||||
```
|
||||
more $BASE/kustomization.yaml
|
||||
```
|
||||
|
||||
(可选)在 base 上运行 `kustomize`,并将结果打印到标准输出:
|
||||
|
||||
<!-- @buildBase @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $BASE
|
||||
```
|
||||
|
||||
### Customize the base
|
||||
|
||||
为所有资源设置名称前缀:
|
||||
|
||||
<!-- @namePrefix @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $BASE
|
||||
kustomize edit set nameprefix "my-"
|
||||
```
|
||||
|
||||
查看变化:
|
||||
<!-- @checkNameprefix @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $BASE | grep -C 3 "my-"
|
||||
```
|
||||
|
||||
## 创建 Overlays
|
||||
|
||||
创建 _staging_ 和 _production_ 的 [overlay]:
|
||||
|
||||
* 为 _Staging_ 新增一个 ConfigMap
|
||||
* 为 _Production_ 添加持久化存储盘和更多的副本数
|
||||
* 现实两个 [variants] 的不同之处
|
||||
|
||||
<!-- @overlayDirectories @testAgainstLatestRelease -->
|
||||
```
|
||||
OVERLAYS=$DEMO_HOME/overlays
|
||||
mkdir -p $OVERLAYS/staging
|
||||
mkdir -p $OVERLAYS/production
|
||||
```
|
||||
|
||||
#### Staging Kustomization
|
||||
|
||||
下载 staging 配置
|
||||
|
||||
<!-- @downloadStagingKustomization @testAgainstLatestRelease -->
|
||||
```
|
||||
curl -s -o "$OVERLAYS/staging/#1" "$CONTENT/overlays/staging\
|
||||
/{config.env,deployment.yaml,kustomization.yaml}"
|
||||
```
|
||||
|
||||
在 staging 配置中增加一个 ConfigMap
|
||||
> ```cat $OVERLAYS/staging/kustomization.yaml
|
||||
> (...truncated)
|
||||
> configMapGenerator:
|
||||
> - name: env-config
|
||||
> files:
|
||||
> - config.env
|
||||
> ```
|
||||
和2个副本
|
||||
> ```cat $OVERLAYS/staging/deployment.yaml
|
||||
> apiVersion: apps/v1
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> name: ldap
|
||||
> spec:
|
||||
> replicas: 2
|
||||
> ```
|
||||
|
||||
#### Production Kustomization
|
||||
|
||||
下载 production 配置
|
||||
<!-- @downloadProductionKustomization @testAgainstLatestRelease -->
|
||||
```
|
||||
curl -s -o "$OVERLAYS/production/#1" "$CONTENT/overlays/production\
|
||||
/{deployment.yaml,kustomization.yaml}"
|
||||
```
|
||||
|
||||
在 production 的配置中增加为6副本和存储盘
|
||||
> ```cat $OVERLAYS/production/deployment.yaml
|
||||
> apiVersion: apps/v1
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> name: ldap
|
||||
> spec:
|
||||
> replicas: 6
|
||||
> template:
|
||||
> spec:
|
||||
> volumes:
|
||||
> - name: ldap-data
|
||||
> emptyDir: null
|
||||
> gcePersistentDisk:
|
||||
> pdName: ldap-persistent-storage
|
||||
> ```
|
||||
|
||||
## 比较 overlays
|
||||
|
||||
|
||||
`DEMO_HOME` 现在包括:
|
||||
|
||||
* 一个 _base_ 目录:对拉取原始配置进行少量的定制
|
||||
|
||||
* 一个 _overlays_ 目录:其中包含在集群中创建不同的 _staging_ 和 _production_ [variants] 所需的 kustomizations 文件和 patche 文件
|
||||
|
||||
查看目录结构和差异:
|
||||
|
||||
<!-- @listFiles @testAgainstLatestRelease -->
|
||||
```
|
||||
tree $DEMO_HOME
|
||||
```
|
||||
|
||||
将会得到类似的内容:
|
||||
|
||||
> ```
|
||||
> /tmp/tmp.IyYQQlHaJP1
|
||||
> ├── base
|
||||
> │ ├── deployment.yaml
|
||||
> │ ├── env.startup.txt
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── service.yaml
|
||||
> └── overlays
|
||||
> ├── production
|
||||
> │ ├── deployment.yaml
|
||||
> │ └── kustomization.yaml
|
||||
> └── staging
|
||||
> ├── config.env
|
||||
> ├── deployment.yaml
|
||||
> └── kustomization.yaml
|
||||
> ```
|
||||
|
||||
直接对输出内容进行比较,以查看 _staging_ 和 _production_ 的不同之处:
|
||||
|
||||
<!-- @compareOutput -->
|
||||
```
|
||||
diff \
|
||||
<(kustomize build $OVERLAYS/staging) \
|
||||
<(kustomize build $OVERLAYS/production) |\
|
||||
more
|
||||
```
|
||||
|
||||
输出的差异内容
|
||||
|
||||
> ```diff
|
||||
> (...truncated)
|
||||
> < name: staging-my-ldap-configmap-kftftt474h
|
||||
> ---
|
||||
> > name: production-my-ldap-configmap-k27f7hkg4f
|
||||
> 85c75
|
||||
> < name: staging-my-ldap-service
|
||||
> ---
|
||||
> > name: production-my-ldap-service
|
||||
> 97c87
|
||||
> < name: staging-my-ldap
|
||||
> ---
|
||||
> > name: production-my-ldap
|
||||
> 99c89
|
||||
> < replicas: 2
|
||||
> ---
|
||||
> > replicas: 6
|
||||
> (...truncated)
|
||||
> ```
|
||||
|
||||
|
||||
## 部署
|
||||
|
||||
查看各个资源集:
|
||||
|
||||
<!-- @buildStaging @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $OVERLAYS/staging
|
||||
```
|
||||
|
||||
<!-- @buildProduction @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $OVERLAYS/production
|
||||
```
|
||||
|
||||
将上述命令通过管道传递给 kubectl 以进行部署:
|
||||
|
||||
> ```
|
||||
> kustomize build $OVERLAYS/staging |\
|
||||
> kubectl apply -f -
|
||||
> ```
|
||||
|
||||
> ```
|
||||
> kustomize build $OVERLAYS/production |\
|
||||
> kubectl apply -f -
|
||||
> ```
|
||||
113
examples/zh/multi-namespace.md
Normal file
113
examples/zh/multi-namespace.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 示例:使用通用的 base 应用多 namespace
|
||||
|
||||
`kustomize` 支持基于同一base具有不同 namespace 的多个 variants。
|
||||
|
||||
只需将 overlay 作为新的 kustomization 的 base,就可以创建一个额外的 overlay 将这些 variants 组合在一起。下面使用一个 pod 作为 base 来进行演示。
|
||||
|
||||
创建一个工作空间:
|
||||
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
定义一个通用的 base:
|
||||
<!-- @makeBase @testAgainstLatestRelease -->
|
||||
```
|
||||
BASE=$DEMO_HOME/base
|
||||
mkdir $BASE
|
||||
|
||||
cat <<EOF >$BASE/kustomization.yaml
|
||||
resources:
|
||||
- pod.yaml
|
||||
EOF
|
||||
|
||||
cat <<EOF >$BASE/pod.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
EOF
|
||||
```
|
||||
|
||||
定义 namespace-a 的 variant:
|
||||
<!-- @makeNamespaceA @testAgainstLatestRelease -->
|
||||
```
|
||||
NSA=$DEMO_HOME/namespace-a
|
||||
mkdir $NSA
|
||||
|
||||
cat <<EOF >$NSA/kustomization.yaml
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- ../base
|
||||
namespace: namespace-a
|
||||
EOF
|
||||
|
||||
cat <<EOF >$NSA/namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: namespace-a
|
||||
EOF
|
||||
```
|
||||
|
||||
定义 namespace-b 的 variant:
|
||||
<!-- @makeNamespaceB @testAgainstLatestRelease -->
|
||||
```
|
||||
NSB=$DEMO_HOME/namespace-b
|
||||
mkdir $NSB
|
||||
|
||||
cat <<EOF >$NSB/kustomization.yaml
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- ../base
|
||||
namespace: namespace-b
|
||||
EOF
|
||||
|
||||
cat <<EOF >$NSB/namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: namespace-b
|
||||
EOF
|
||||
```
|
||||
|
||||
然后定义一个 _Kustomization_,将两个 variants 组合在一起:
|
||||
<!-- @makeTopLayer @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||
resources:
|
||||
- namespace-a
|
||||
- namespace-b
|
||||
EOF
|
||||
```
|
||||
|
||||
现在工作空间有如下目录:
|
||||
> ```
|
||||
> .
|
||||
> ├── base
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── pod.yaml
|
||||
> ├── kustomization.yaml
|
||||
> ├── namespace-a
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── namespace.yaml
|
||||
> └── namespace-b
|
||||
> ├── kustomization.yaml
|
||||
> └── namespace.yaml
|
||||
> ```
|
||||
|
||||
输出两个 namespace 的 pod 对象,分别在 namespace-a 和 namespace-b。
|
||||
|
||||
<!-- @confirmVariants @testAgainstLatestRelease -->
|
||||
```
|
||||
test 2 == \
|
||||
$(kustomize build $DEMO_HOME| grep -B 4 "namespace: namespace-[ab]" | grep "name: myapp-pod" | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
127
examples/zh/multibases.md
Normal file
127
examples/zh/multibases.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 示例:多 Overlay 使用相同 base
|
||||
|
||||
`kustomize` 鼓励定义多个 variants:例如在通用的 base 上使用 dev、staging 和 prod overlay。
|
||||
|
||||
可以创建其他 overlay 来将这些 variants 组合在一起:只需将 overlay 声明为新 kustomization 的 base 即可。
|
||||
|
||||
如果 base 由于某种原因无法控制,将多个 variants 组合在一起也可以为他们添加通用的 label 或 annotation。
|
||||
|
||||
下面使用一个 pod 作为 base 来进行演示。
|
||||
|
||||
首先创建一个工作空间:
|
||||
|
||||
<!-- @makeWorkplace @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
定义一个通用的 base:
|
||||
<!-- @makeBase @testAgainstLatestRelease -->
|
||||
```
|
||||
BASE=$DEMO_HOME/base
|
||||
mkdir $BASE
|
||||
|
||||
cat <<EOF >$BASE/kustomization.yaml
|
||||
resources:
|
||||
- pod.yaml
|
||||
EOF
|
||||
|
||||
cat <<EOF >$BASE/pod.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: myapp-pod
|
||||
labels:
|
||||
app: myapp
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.7.9
|
||||
EOF
|
||||
```
|
||||
|
||||
定义 dev variant:
|
||||
<!-- @makeDev @testAgainstLatestRelease -->
|
||||
```
|
||||
DEV=$DEMO_HOME/dev
|
||||
mkdir $DEV
|
||||
|
||||
cat <<EOF >$DEV/kustomization.yaml
|
||||
resources:
|
||||
- ./../base
|
||||
namePrefix: dev-
|
||||
EOF
|
||||
```
|
||||
|
||||
定义 staging variant:
|
||||
<!-- @makeStaging @testAgainstLatestRelease -->
|
||||
```
|
||||
STAG=$DEMO_HOME/staging
|
||||
mkdir $STAG
|
||||
|
||||
cat <<EOF >$STAG/kustomization.yaml
|
||||
resources:
|
||||
- ./../base
|
||||
namePrefix: stag-
|
||||
EOF
|
||||
```
|
||||
|
||||
定义 production variant:
|
||||
<!-- @makeProd @testAgainstLatestRelease -->
|
||||
```
|
||||
PROD=$DEMO_HOME/production
|
||||
mkdir $PROD
|
||||
|
||||
cat <<EOF >$PROD/kustomization.yaml
|
||||
resources:
|
||||
- ./../base
|
||||
namePrefix: prod-
|
||||
EOF
|
||||
```
|
||||
|
||||
然后定义一个 _Kustomization_,将三个 variants 组合在一起:
|
||||
<!-- @makeTopLayer @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/kustomization.yaml
|
||||
resources:
|
||||
- ./dev
|
||||
- ./staging
|
||||
- ./production
|
||||
|
||||
namePrefix: cluster-a-
|
||||
EOF
|
||||
```
|
||||
|
||||
现在工作空间有如下目录:
|
||||
> ```
|
||||
> .
|
||||
> ├── base
|
||||
> │ ├── kustomization.yaml
|
||||
> │ └── pod.yaml
|
||||
> ├── dev
|
||||
> │ └── kustomization.yaml
|
||||
> ├── kustomization.yaml
|
||||
> ├── production
|
||||
> │ └── kustomization.yaml
|
||||
> └── staging
|
||||
> └── kustomization.yaml
|
||||
> ```
|
||||
|
||||
输出包含三个 pod 对象,分别来自 dev、staging 和 production variants。
|
||||
|
||||
<!-- @confirmVariants @testAgainstLatestRelease -->
|
||||
```
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep cluster-a-dev-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep cluster-a-stag-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
|
||||
test 1 == \
|
||||
$(kustomize build $DEMO_HOME | grep cluster-a-prod-myapp-pod | wc -l); \
|
||||
echo $?
|
||||
```
|
||||
|
||||
与在不同的 variants 中添加不同的 `namePrefix` 类似,也可以添加不同的 `namespace` 并在一个 _kustomization_ 中组成这些 variants。更多的详细信息,请查看[multi-namespaces](multi-namespace.md)。
|
||||
171
examples/zh/mysql.md
Normal file
171
examples/zh/mysql.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 示例:MySql
|
||||
|
||||
本示例采用现成的专为 MySql 设计的 k8s 资源,并对其进行定制使其适合生产环境。
|
||||
|
||||
在生产环境中,我们希望:
|
||||
|
||||
- 以 'prod-' 为前缀的 MySQL 资源
|
||||
- MySQL 资源具有 'env: prod' label
|
||||
- 使用持久化磁盘来存储 MySQL 数据
|
||||
|
||||
首先创建一个工作空间:
|
||||
<!-- @makeDemoHome @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
### 下载资源
|
||||
|
||||
为了保证文档的精简,基础资源都在补充目录中,如果需要请下载它们:
|
||||
|
||||
<!-- @downloadResources @testAgainstLatestRelease -->
|
||||
```
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" "https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/mySql\
|
||||
/{deployment,secret,service}.yaml"
|
||||
```
|
||||
|
||||
### 初始化 kustomization.yaml
|
||||
|
||||
`kustomize` 会从 `kustomization.yaml` 文件中获取指令,创建这个文件:
|
||||
|
||||
<!-- @kustomizeYaml @testAgainstLatestRelease -->
|
||||
```
|
||||
touch $DEMO_HOME/kustomization.yaml
|
||||
```
|
||||
|
||||
### 添加资源
|
||||
|
||||
<!-- @addResources @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
|
||||
kustomize edit add resource secret.yaml
|
||||
kustomize edit add resource service.yaml
|
||||
kustomize edit add resource deployment.yaml
|
||||
|
||||
cat kustomization.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 resources 字段如下:
|
||||
|
||||
> ```
|
||||
> resources:
|
||||
> - secret.yaml
|
||||
> - service.yaml
|
||||
> - deployment.yaml
|
||||
> ```
|
||||
|
||||
### 定制名称
|
||||
|
||||
为 MySQL 资源添加 _prod-_ 前缀(这些资源将用于生产环境):
|
||||
|
||||
<!-- @customizeLabel @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
|
||||
kustomize edit set nameprefix 'prod-'
|
||||
|
||||
cat kustomization.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 namePrefix 字段将会被更新:
|
||||
|
||||
> ```
|
||||
> namePrefix: prod-
|
||||
> ```
|
||||
|
||||
`namePrefix` 将在所有资源的名称前添加 _prod-_ 的前缀,可以通过如下命令查看:
|
||||
|
||||
<!-- @genNamePrefixConfig @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME
|
||||
```
|
||||
|
||||
输出内容:
|
||||
|
||||
> ```
|
||||
> apiVersion: v1
|
||||
> data:
|
||||
> password: YWRtaW4=
|
||||
> kind: Secret
|
||||
> metadata:
|
||||
> ....
|
||||
> name: prod-mysql-pass-d2gtcm2t2k
|
||||
> ---
|
||||
> apiVersion: v1
|
||||
> kind: Service
|
||||
> metadata:
|
||||
> ....
|
||||
> name: prod-mysql
|
||||
> spec:
|
||||
> ....
|
||||
> ---
|
||||
> apiVersion: apps/v1
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> ....
|
||||
> name: prod-mysql
|
||||
> spec:
|
||||
> selector:
|
||||
> ....
|
||||
> ```
|
||||
|
||||
### 定制 Label
|
||||
|
||||
我们希望生产环境的资源包含某些 Label,这样我们就可以通过 label selector 来查询到这些资源。
|
||||
|
||||
`kustomize` 没有 `edit set label` 命令来添加 label,但是可以通过编辑 `kustomization.yaml` 文件来实现:
|
||||
|
||||
<!-- @customizeLabels @testAgainstLatestRelease -->
|
||||
```
|
||||
sed -i.bak 's/app: helloworld/app: prod/' \
|
||||
$DEMO_HOME/kustomization.yaml
|
||||
```
|
||||
|
||||
这时,执行 `kustomize build` 命令将会生成包含 `prod-` 前缀和 `env:prod` label 的 MySQL 配置。
|
||||
|
||||
### 存储定制
|
||||
|
||||
现成的 MySQL 使用 `emptyDir` 类型的 volume,如果 MySQL Pod 被重新部署,则该类型的 volume 将会消失,这是不能应用于生产环境的,因此在生产环境中我们需要使用持久化磁盘。在 kustomize 中可以使用`patchesStrategicMerge` 来应用资源。
|
||||
|
||||
<!-- @createPatchFile @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<'EOF' > $DEMO_HOME/persistent-disk.yaml
|
||||
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mysql
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: mysql-persistent-storage
|
||||
emptyDir: null
|
||||
gcePersistentDisk:
|
||||
pdName: mysql-persistent-storage
|
||||
EOF
|
||||
```
|
||||
|
||||
将 patch 文件添加到 `kustomization.yaml` 中:
|
||||
|
||||
<!-- @specifyPatch @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<'EOF' >> $DEMO_HOME/kustomization.yaml
|
||||
patchesStrategicMerge:
|
||||
- persistent-disk.yaml
|
||||
EOF
|
||||
```
|
||||
|
||||
`mysql-persistent-storage` 必须存在一个持久化磁盘才能使其成功运行,分为两步:
|
||||
|
||||
1. 创建一个名为 `persistent-disk.yaml` 的 YAML 文件,用于修改 deployment.yaml 的定义。
|
||||
2. 在 `kustomization.yaml` 中添加 `persistent-disk.yaml` 到 `patchesStrategicMerge` 列表中。运行 `kustomize build` 将 patch 应用于 Deployment 资源。
|
||||
|
||||
现在就可以将完整的配置输出并在集群中部署(将结果通过管道输出给 `kubectl apply`),在生产环境创建MySQL 应用。
|
||||
|
||||
<!-- @finalInflation @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME # | kubectl apply -f -
|
||||
```
|
||||
281
examples/zh/springboot.md
Normal file
281
examples/zh/springboot.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# 示例:SpringBoot
|
||||
|
||||
在本教程中,您将学会如何使用 `kustomize` 定制一个运行 Spring Boot 应用的 k8s 配置。
|
||||
|
||||
在生产环境中,我们需要定制如下内容:
|
||||
|
||||
- 为 Spring Boot 应用添加特定配置
|
||||
- 配置数据库连接
|
||||
- 以 'prod-' 前缀命名资源
|
||||
- 资源具有 'env: prod' label
|
||||
- 设置合适的 JVM 内存
|
||||
- 健康检查和就绪检查
|
||||
|
||||
首先创建一个工作空间:
|
||||
<!-- @makeDemoHome @testAgainstLatestRelease -->
|
||||
```
|
||||
DEMO_HOME=$(mktemp -d)
|
||||
```
|
||||
|
||||
### 下载资源
|
||||
|
||||
为了保证文档的精简,基础资源都在补充目录中,如果需要请下载它们:
|
||||
<!-- @downloadResources @testAgainstLatestRelease -->
|
||||
```
|
||||
CONTENT="https://raw.githubusercontent.com\
|
||||
/kubernetes-sigs/kustomize\
|
||||
/master/examples/springboot"
|
||||
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" \
|
||||
"$CONTENT/base/{deployment,service}.yaml"
|
||||
```
|
||||
|
||||
### 初始化 kustomization.yaml
|
||||
|
||||
`kustomize` 会从 `kustomization.yaml` 文件中获取指令,创建这个文件:
|
||||
|
||||
<!-- @kustomizeYaml @testAgainstLatestRelease -->
|
||||
```
|
||||
touch $DEMO_HOME/kustomization.yaml
|
||||
```
|
||||
|
||||
### 添加资源
|
||||
|
||||
<!-- @addResources @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
|
||||
kustomize edit add resource service.yaml
|
||||
kustomize edit add resource deployment.yaml
|
||||
|
||||
cat kustomization.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 resources 字段如下:
|
||||
|
||||
> ```
|
||||
> resources:
|
||||
> - service.yaml
|
||||
> - deployment.yaml
|
||||
> ```
|
||||
|
||||
### 添加 configMap 生成器
|
||||
|
||||
<!-- @addConfigMap @testAgainstLatestRelease -->
|
||||
```
|
||||
echo "app.name=Kustomize Demo" >$DEMO_HOME/application.properties
|
||||
|
||||
kustomize edit add configmap demo-configmap \
|
||||
--from-file application.properties
|
||||
|
||||
cat kustomization.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 configMapGenerator 字段如下:
|
||||
|
||||
> ```
|
||||
> configMapGenerator:
|
||||
> - files:
|
||||
> - application.properties
|
||||
> name: demo-configmap
|
||||
> ```
|
||||
|
||||
### 定制 configMap
|
||||
|
||||
我们将为生产环境添加数据库连接凭证。通常这些凭据被存放在 `application.properties` 中,然而在有些时候,我们希望将这些凭证保存在其他文件中,而将应用的其他配置保存在 `application.properties` 中。通过这种清晰的分离,这些凭证和应用配置可由不同的团队管理和维护。例如,应用开发人员可以在 `application.properties` 中调整应用程序的配置,而数据库的连接凭证则由运维或 SRE 团队管理和维护。
|
||||
|
||||
对于 Spring Boot 应用,我们可以通过环境变量动态的设置 `spring.profiles.active`,然后应用将获取一个额外的 `application-<profile>.properties` 文件,我们可以分为两步定制这个 ConfigMap:
|
||||
|
||||
1. 通过 patch 添加一个环境变量
|
||||
2. 将文件添加到 ConfigMap 中
|
||||
|
||||
<!-- @customizeConfigMap @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >$DEMO_HOME/patch.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: sbdemo
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: sbdemo
|
||||
env:
|
||||
- name: spring.profiles.active
|
||||
value: prod
|
||||
EOF
|
||||
|
||||
kustomize edit add patch patch.yaml
|
||||
|
||||
cat <<EOF >$DEMO_HOME/application-prod.properties
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.datasource.url=jdbc:mysql://<prod_database_host>:3306/db_example
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=admin
|
||||
EOF
|
||||
|
||||
kustomize edit add configmap \
|
||||
demo-configmap --from-file application-prod.properties
|
||||
|
||||
cat kustomization.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 configMapGenerator 字段如下:
|
||||
|
||||
> ```
|
||||
> configMapGenerator:
|
||||
> - files:
|
||||
> - application.properties
|
||||
> - application-prod.properties
|
||||
> name: demo-configmap
|
||||
> ```
|
||||
|
||||
### 定制名称
|
||||
|
||||
为资源添加 _prod-_ 前缀(这些资源将用于生产环境):
|
||||
|
||||
<!-- @customizeLabel @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
kustomize edit set nameprefix 'prod-'
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 namePrefix 字段将会被更新:
|
||||
|
||||
> ```
|
||||
> namePrefix: prod-
|
||||
> ```
|
||||
|
||||
`namePrefix` 将在所有资源的名称前添加 _prod-_ 的前缀,可以通过如下命令查看:
|
||||
|
||||
<!-- @build1 @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME | grep prod-
|
||||
```
|
||||
|
||||
### 定制 Label
|
||||
|
||||
我们希望生产环境的资源包含某些 Label,这样我们就可以通过 label selector 来查询到这些资源。
|
||||
|
||||
`kustomize` 没有 `edit set label` 命令来添加 label,但是可以通过编辑 `kustomization.yaml` 文件来实现:
|
||||
|
||||
<!-- @customizeLabels @testAgainstLatestRelease -->
|
||||
```
|
||||
cat <<EOF >>$DEMO_HOME/kustomization.yaml
|
||||
commonLabels:
|
||||
env: prod
|
||||
EOF
|
||||
```
|
||||
|
||||
现在所有资源都包含 `prod-` 前缀和 `env:prod` label,可以通过下面的命令来查看:
|
||||
|
||||
<!-- @build2 @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME | grep -C 3 env
|
||||
```
|
||||
|
||||
### 下载调整 JVM 内存的 Patch
|
||||
|
||||
当 Spring Boot 应用部署在 k8s 集群中时,JVM 会运行在容器中。我们要为容器设置内存限制,并确保 JVM 知道容器的内存限制。在 k8s 的 Deployment 中,我们可以设置资源容器的资源限制,并将限制注入到一些环境变量中,当容器启动时,其可以获取环境变量并设置相应的 JVM 选项。
|
||||
|
||||
下载 `memorylimit_patch.yaml` 其包含内存限制设置的 patch:
|
||||
|
||||
<!-- @downloadPatch @testAgainstLatestRelease -->
|
||||
```
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" \
|
||||
"$CONTENT/overlays/production/{memorylimit_patch}.yaml"
|
||||
|
||||
cat $DEMO_HOME/memorylimit_patch.yaml
|
||||
```
|
||||
|
||||
输出内容
|
||||
|
||||
> ```
|
||||
> apiVersion: apps/v1
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> name: sbdemo
|
||||
> spec:
|
||||
> template:
|
||||
> spec:
|
||||
> containers:
|
||||
> - name: sbdemo
|
||||
> resources:
|
||||
> limits:
|
||||
> memory: 1250Mi
|
||||
> requests:
|
||||
> memory: 1250Mi
|
||||
> env:
|
||||
> - name: MEM_TOTAL_MB
|
||||
> valueFrom:
|
||||
> resourceFieldRef:
|
||||
> resource: limits.memory
|
||||
> ```
|
||||
|
||||
### 下载健康检查的 Patch
|
||||
|
||||
我们还可以在生产环境中添加健康检查和就绪检查,Spring Boot 应用都具有类似 `/actuator/health` 的接口用于健康检查,我们可以定制 k8s 的 Deployment 资源来进行健康检查和就绪检查。
|
||||
|
||||
下载 `memorylimit_patch.yaml` 其包含存活和就绪探针的 patch:
|
||||
|
||||
<!-- @downloadPatch @testAgainstLatestRelease -->
|
||||
```
|
||||
curl -s -o "$DEMO_HOME/#1.yaml" \
|
||||
"$CONTENT/overlays/production/{healthcheck_patch}.yaml"
|
||||
|
||||
cat $DEMO_HOME/healthcheck_patch.yaml
|
||||
```
|
||||
|
||||
输出内容
|
||||
|
||||
> ```
|
||||
> apiVersion: apps/v1
|
||||
> kind: Deployment
|
||||
> metadata:
|
||||
> name: sbdemo
|
||||
> spec:
|
||||
> template:
|
||||
> spec:
|
||||
> containers:
|
||||
> - name: sbdemo
|
||||
> livenessProbe:
|
||||
> httpGet:
|
||||
> path: /actuator/health
|
||||
> port: 8080
|
||||
> initialDelaySeconds: 10
|
||||
> periodSeconds: 3
|
||||
> readinessProbe:
|
||||
> initialDelaySeconds: 20
|
||||
> periodSeconds: 10
|
||||
> httpGet:
|
||||
> path: /actuator/info
|
||||
> port: 8080
|
||||
> ```
|
||||
|
||||
### 添加 patches
|
||||
|
||||
将这些 patch 添加到 `kustomization.yaml` 中:
|
||||
|
||||
<!-- @addPatch @testAgainstLatestRelease -->
|
||||
```
|
||||
cd $DEMO_HOME
|
||||
kustomize edit add patch memorylimit_patch.yaml
|
||||
kustomize edit add patch healthcheck_patch.yaml
|
||||
```
|
||||
|
||||
执行上面的命令后,`kustomization.yaml` 的 patchesStrategicMerge 字段如下:
|
||||
|
||||
> ```
|
||||
> patchesStrategicMerge:
|
||||
> - patch.yaml
|
||||
> - memorylimit_patch.yaml
|
||||
> - healthcheck_patch.yaml
|
||||
> ```
|
||||
|
||||
现在就可以将完整的配置输出并在集群中部署(将结果通过管道输出给 `kubectl apply`),在生产环境创建Spring Boot 应用。
|
||||
|
||||
<!-- @finalBuild @testAgainstLatestRelease -->
|
||||
```
|
||||
kustomize build $DEMO_HOME # | kubectl apply -f -
|
||||
```
|
||||
13
hack/testExamplesE2EAgainstKustomize.sh
Executable file
13
hack/testExamplesE2EAgainstKustomize.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright 2019 The Kubernetes Authors.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -o nounset
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
mdrip --blockTimeOut 60m0s --mode test \
|
||||
--label testE2EAgainstLatestRelease examples
|
||||
|
||||
echo "Example e2e tests passed against ${version}."
|
||||
@@ -6,25 +6,16 @@ require (
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gogo/protobuf v1.3.1 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/json-iterator/go v1.1.8 // indirect
|
||||
github.com/onsi/ginkgo v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.7.0 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.uber.org/atomic v1.4.0 // indirect
|
||||
go.uber.org/zap v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 // indirect
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b // indirect
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
|
||||
k8s.io/klog v1.0.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d // indirect
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/client-go v0.17.0
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
|
||||
@@ -110,6 +110,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-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/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
@@ -129,6 +130,7 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a
|
||||
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-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/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.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
@@ -252,6 +254,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -277,8 +280,8 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc=
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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=
|
||||
@@ -302,6 +305,7 @@ golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
|
||||
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -312,6 +316,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -369,13 +374,19 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
@@ -389,8 +400,8 @@ k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKf
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d h1:1P0iBJsBzxRmR+dIFnM+Iu4aLxnoa7lBqozW/0uHbT8=
|
||||
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
|
||||
@@ -395,26 +395,21 @@ func podConditions(u *unstructured.Unstructured) (*Result, error) {
|
||||
return newInProgressStatus("PodNotReady", message), nil
|
||||
}
|
||||
|
||||
// pdbConditions return standardized Conditions for Deployment
|
||||
// pdbConditions computes the status for PodDisruptionBudgets. A PDB
|
||||
// is currently considered Current if the disruption controller has
|
||||
// observed the latest version of the PDB resource and has computed
|
||||
// the AllowedDisruptions. PDBs do have ObservedGeneration in the
|
||||
// Status object, so if this function gets called we know that
|
||||
// the controller has observed the latest changes.
|
||||
// The disruption controller does not set any conditions if
|
||||
// computing the AllowedDisruptions fails (and there are many ways
|
||||
// it can fail), but there is PR against OSS Kubernetes to address
|
||||
// this: https://github.com/kubernetes/kubernetes/pull/86929
|
||||
func pdbConditions(u *unstructured.Unstructured) (*Result, error) {
|
||||
obj := u.UnstructuredContent()
|
||||
|
||||
// replicas
|
||||
currentHealthy := GetIntField(obj, ".status.currentHealthy", 0)
|
||||
desiredHealthy := GetIntField(obj, ".status.desiredHealthy", 0)
|
||||
if desiredHealthy == 0 {
|
||||
message := "Missing or zero .status.desiredHealthy"
|
||||
return newInProgressStatus("ZeroDesiredHealthy", message), nil
|
||||
}
|
||||
if desiredHealthy > currentHealthy {
|
||||
message := fmt.Sprintf("Budget not met. healthy replicas: %d/%d", currentHealthy, desiredHealthy)
|
||||
return newInProgressStatus("BudgetNotMet", message), nil
|
||||
}
|
||||
|
||||
// All ok
|
||||
return &Result{
|
||||
Status: CurrentStatus,
|
||||
Message: fmt.Sprintf("Budget is met. Replicas: %d/%d", currentHealthy, desiredHealthy),
|
||||
Message: "AllowedDisruptions has been computed.",
|
||||
Conditions: []Condition{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Package kstatus contains functionality for computing the status
|
||||
// of Kubernetes resources.
|
||||
//
|
||||
// The statuses defined in this package is:
|
||||
// The statuses defined in this package are:
|
||||
// * InProgress
|
||||
// * Current
|
||||
// * Failed
|
||||
@@ -13,11 +13,12 @@
|
||||
//
|
||||
// Computing the status of a resources can be done by calling the
|
||||
// Compute function in the status package.
|
||||
// import (
|
||||
// "sigs.k8s.io/kustomize/kstatus/status"
|
||||
// )
|
||||
// res, err := status.Compute(resource)
|
||||
//
|
||||
// import (
|
||||
// "sigs.k8s.io/kustomize/kstatus/status"
|
||||
// )
|
||||
//
|
||||
// res, err := status.Compute(resource)
|
||||
//
|
||||
// The package also defines a set of new conditions:
|
||||
// * InProgress
|
||||
@@ -31,8 +32,10 @@
|
||||
// the standard conditions described above. The values of
|
||||
// these conditions are decided based on other status information
|
||||
// available in the resources.
|
||||
// import (
|
||||
//
|
||||
// import (
|
||||
// "sigs.k8s.io/kustomize/kstatus/status
|
||||
// )
|
||||
// err := status.Augment(resource)
|
||||
// )
|
||||
//
|
||||
// err := status.Augment(resource)
|
||||
package status
|
||||
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
Statuses = []Status{InProgressStatus, FailedStatus, CurrentStatus, TerminatingStatus, UnknownStatus}
|
||||
Statuses = []Status{InProgressStatus, FailedStatus, CurrentStatus, TerminatingStatus, UnknownStatus}
|
||||
ConditionTypes = []ConditionType{ConditionFailed, ConditionInProgress}
|
||||
)
|
||||
|
||||
|
||||
@@ -822,66 +822,44 @@ func TestReplicasetStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var pdbNoStatus = `
|
||||
apiVersion: policy/v1
|
||||
var pdbNotObserved = `
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
generation: 1
|
||||
generation: 2
|
||||
name: test
|
||||
namespace: qual
|
||||
status:
|
||||
observedGeneration: 1
|
||||
`
|
||||
|
||||
var pdbOK1 = `
|
||||
apiVersion: policy/v1
|
||||
var pdbObserved = `
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
generation: 1
|
||||
name: test
|
||||
namespace: qual
|
||||
status:
|
||||
currentHealthy: 2
|
||||
desiredHealthy: 2
|
||||
`
|
||||
|
||||
var pdbMoreHealthy = `
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
generation: 1
|
||||
name: test
|
||||
namespace: qual
|
||||
status:
|
||||
currentHealthy: 4
|
||||
desiredHealthy: 2
|
||||
`
|
||||
|
||||
var pdbLessHealthy = `
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
generation: 1
|
||||
name: test
|
||||
namespace: qual
|
||||
status:
|
||||
currentHealthy: 2
|
||||
desiredHealthy: 4
|
||||
observedGeneration: 1
|
||||
`
|
||||
|
||||
func TestPDBStatus(t *testing.T) {
|
||||
testCases := map[string]testSpec{
|
||||
"pdbNoStatus": {
|
||||
spec: pdbNoStatus,
|
||||
"pdbNotObserved": {
|
||||
spec: pdbNotObserved,
|
||||
expectedStatus: InProgressStatus,
|
||||
expectedConditions: []Condition{{
|
||||
Type: ConditionInProgress,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "ZeroDesiredHealthy",
|
||||
Reason: "LatestGenerationNotObserved",
|
||||
}},
|
||||
absentConditionTypes: []ConditionType{
|
||||
ConditionFailed,
|
||||
},
|
||||
},
|
||||
"pdbOK1": {
|
||||
spec: pdbOK1,
|
||||
"pdbObserved": {
|
||||
spec: pdbObserved,
|
||||
expectedStatus: CurrentStatus,
|
||||
expectedConditions: []Condition{},
|
||||
absentConditionTypes: []ConditionType{
|
||||
@@ -889,27 +867,6 @@ func TestPDBStatus(t *testing.T) {
|
||||
ConditionInProgress,
|
||||
},
|
||||
},
|
||||
"pdbMoreHealthy": {
|
||||
spec: pdbMoreHealthy,
|
||||
expectedStatus: CurrentStatus,
|
||||
expectedConditions: []Condition{},
|
||||
absentConditionTypes: []ConditionType{
|
||||
ConditionFailed,
|
||||
ConditionInProgress,
|
||||
},
|
||||
},
|
||||
"pdbLessHealthy": {
|
||||
spec: pdbLessHealthy,
|
||||
expectedStatus: InProgressStatus,
|
||||
expectedConditions: []Condition{{
|
||||
Type: ConditionInProgress,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "BudgetNotMet",
|
||||
}},
|
||||
absentConditionTypes: []ConditionType{
|
||||
ConditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
// only requires functions for getting the apiVersion, kind, name
|
||||
// and namespace of a resource.
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
// "k8s.io/apimachinery/pkg/types"
|
||||
// "sigs.k8s.io/kustomize/kstatus/wait"
|
||||
// )
|
||||
// import (
|
||||
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
// "k8s.io/apimachinery/pkg/types"
|
||||
// "sigs.k8s.io/kustomize/kstatus/wait"
|
||||
// )
|
||||
//
|
||||
// key := types.NamespacedName{Name: "name", Namespace: "namespace"}
|
||||
// deployment := &unstructured.Unstructured{
|
||||
// Object: map[string]interface{}{
|
||||
// "apiVersion": "apps/v1",
|
||||
// "kind": "Deployment",
|
||||
// },
|
||||
// }
|
||||
// client.Get(context.Background(), key, deployment)
|
||||
// resourceIdentifiers := []wait.ResourceIdentifier{deployment}
|
||||
// key := types.NamespacedName{Name: "name", Namespace: "namespace"}
|
||||
// deployment := &unstructured.Unstructured{
|
||||
// Object: map[string]interface{}{
|
||||
// "apiVersion": "apps/v1",
|
||||
// "kind": "Deployment",
|
||||
// },
|
||||
// }
|
||||
// client.Get(context.Background(), key, deployment)
|
||||
// resourceIdentifiers := []wait.ResourceIdentifier{deployment}
|
||||
//
|
||||
// resolver := wait.NewResolver(client)
|
||||
// results := resolver.FetchAndResolve(context.Background(), resourceIdentifiers)
|
||||
// resolver := wait.NewResolver(client)
|
||||
// results := resolver.FetchAndResolve(context.Background(), resourceIdentifiers)
|
||||
//
|
||||
// WaitForStatus also looks up status for a list of resources, but it will
|
||||
// block until all the provided resources has reached the Current status or
|
||||
@@ -40,18 +40,19 @@
|
||||
// a channel that will provide updates as the status of the different
|
||||
// resources change.
|
||||
//
|
||||
// import (
|
||||
// "sigs.k8s.io/kustomize/kstatus/wait"
|
||||
// )
|
||||
// resolver := wait.NewResolver(client)
|
||||
// eventsChan := resolver.WaitForStatus(context.Background(), resourceIdentifiers, 2 * time.Second)
|
||||
// for {
|
||||
// select {
|
||||
// case event, ok := <-eventsChan:
|
||||
// if !ok {
|
||||
// return
|
||||
// import (
|
||||
// "sigs.k8s.io/kustomize/kstatus/wait"
|
||||
// )
|
||||
//
|
||||
// resolver := wait.NewResolver(client)
|
||||
// eventsChan := resolver.WaitForStatus(context.Background(), resourceIdentifiers, 2 * time.Second)
|
||||
// for {
|
||||
// select {
|
||||
// case event, ok := <-eventsChan:
|
||||
// if !ok {
|
||||
// return
|
||||
// }
|
||||
// fmt.Printf(event) // do something useful here.
|
||||
// }
|
||||
// fmt.Printf(event) // do something useful here.
|
||||
// }
|
||||
// }
|
||||
package wait
|
||||
|
||||
@@ -5,24 +5,28 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// keyFromResourceIdentifier creates a resourceKey from a ResourceIdentifier.
|
||||
func keyFromResourceIdentifier(i ResourceIdentifier) resourceKey {
|
||||
return resourceKey{
|
||||
apiVersion: i.GetAPIVersion(),
|
||||
kind: i.GetKind(),
|
||||
name: i.GetName(),
|
||||
namespace: i.GetNamespace(),
|
||||
func resourceIdentifierFromObject(object KubernetesObject) ResourceIdentifier {
|
||||
return ResourceIdentifier{
|
||||
Name: object.GetName(),
|
||||
Namespace: object.GetNamespace(),
|
||||
GroupKind: object.GroupVersionKind().GroupKind(),
|
||||
}
|
||||
}
|
||||
|
||||
// keyFromObject creates a resourceKey from an Object.
|
||||
func keyFromObject(obj runtime.Object) resourceKey {
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
r := obj.(metav1.Object)
|
||||
return resourceKey{
|
||||
apiVersion: gvk.GroupVersion().String(),
|
||||
kind: gvk.Kind,
|
||||
name: r.GetName(),
|
||||
namespace: r.GetNamespace(),
|
||||
func resourceIdentifiersFromObjects(objects []KubernetesObject) []ResourceIdentifier {
|
||||
var resourceIdentifiers []ResourceIdentifier
|
||||
for _, object := range objects {
|
||||
resourceIdentifiers = append(resourceIdentifiers, resourceIdentifierFromObject(object))
|
||||
}
|
||||
return resourceIdentifiers
|
||||
}
|
||||
|
||||
func resourceIdentifierFromRuntimeObject(object runtime.Object) ResourceIdentifier {
|
||||
gvk := object.GetObjectKind().GroupVersionKind()
|
||||
r := object.(metav1.Object)
|
||||
return ResourceIdentifier{
|
||||
GroupKind: gvk.GroupKind(),
|
||||
Name: r.GetName(),
|
||||
Namespace: r.GetNamespace(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,28 +10,59 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNamespace = "default"
|
||||
)
|
||||
|
||||
// ResourceIdentifier defines the functions needed to identify
|
||||
// a resource in a cluster. This interface is implemented by
|
||||
// both unstructured.Unstructured and the standard Kubernetes types.
|
||||
type ResourceIdentifier interface {
|
||||
type KubernetesObject interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
GetAPIVersion() string
|
||||
GetKind() string
|
||||
GroupVersionKind() schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// ResourceIdentifier contains the information needed to uniquely
|
||||
// identify a resource in a cluster.
|
||||
type ResourceIdentifier struct {
|
||||
Name string
|
||||
Namespace string
|
||||
GroupKind schema.GroupKind
|
||||
}
|
||||
|
||||
// Equals compares two ResourceIdentifiers and returns true if they
|
||||
// refer to the same resource. Special handling is needed for namespace
|
||||
// since an empty namespace for a namespace-scoped resource is defaulted
|
||||
// to the "default" namespace.
|
||||
func (r ResourceIdentifier) Equals(other ResourceIdentifier) bool {
|
||||
isSameNamespace := r.Namespace == other.Namespace ||
|
||||
(r.Namespace == "" && other.Namespace == defaultNamespace) ||
|
||||
(r.Namespace == defaultNamespace && other.Namespace == "")
|
||||
return r.GroupKind == other.GroupKind &&
|
||||
r.Name == other.Name &&
|
||||
isSameNamespace
|
||||
}
|
||||
|
||||
// Resolver provides the functions for resolving status of a list of resources.
|
||||
type Resolver struct {
|
||||
// DynamicClient is the client used to talk
|
||||
// with the cluster
|
||||
// client is the client used to talk
|
||||
// with the cluster. It uses the Reader interface
|
||||
// from controller-runtime.
|
||||
client client.Reader
|
||||
|
||||
// mapper is the RESTMapper needed to look up mappings
|
||||
// for resource types.
|
||||
mapper meta.RESTMapper
|
||||
|
||||
// statusComputeFunc defines which function should be used for computing
|
||||
// the status of a resource. This is available for testing purposes.
|
||||
statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)
|
||||
@@ -44,9 +75,10 @@ type Resolver struct {
|
||||
|
||||
// NewResolver creates a new resolver with the provided client. Fetching
|
||||
// and polling of resources will be done using the provided client.
|
||||
func NewResolver(client client.Reader, pollInterval time.Duration) *Resolver {
|
||||
func NewResolver(client client.Reader, mapper meta.RESTMapper, pollInterval time.Duration) *Resolver {
|
||||
return &Resolver{
|
||||
client: client,
|
||||
mapper: mapper,
|
||||
statusComputeFunc: status.Compute,
|
||||
pollInterval: pollInterval,
|
||||
}
|
||||
@@ -58,24 +90,31 @@ func NewResolver(client client.Reader, pollInterval time.Duration) *Resolver {
|
||||
type ResourceResult struct {
|
||||
Result *status.Result
|
||||
|
||||
Resource ResourceIdentifier
|
||||
ResourceIdentifier ResourceIdentifier
|
||||
|
||||
Error error
|
||||
}
|
||||
|
||||
// FetchAndResolve returns the status for a list of resources. It will return
|
||||
// the status for each of them individually. The slice of ResourceIdentifiers will
|
||||
// only be used to get the information needed to fetch the updated state of
|
||||
// the resources from the cluster.
|
||||
func (r *Resolver) FetchAndResolve(ctx context.Context, resources []ResourceIdentifier) []ResourceResult {
|
||||
// FetchAndResolveObjects returns the status for a list of kubernetes objects. These can be provided
|
||||
// either as Unstructured resources or the specific resource types. It will return the status for each
|
||||
// of them individually. The provided resources will only be used to get the information needed to
|
||||
// fetch the updated state of the resources from the cluster.
|
||||
func (r *Resolver) FetchAndResolveObjects(ctx context.Context, objects []KubernetesObject) []ResourceResult {
|
||||
resourceIds := resourceIdentifiersFromObjects(objects)
|
||||
return r.FetchAndResolve(ctx, resourceIds)
|
||||
}
|
||||
|
||||
// FetchAndResolve returns the status for a list of ResourceIdentifiers. It will return
|
||||
// the status for each of them individually.
|
||||
func (r *Resolver) FetchAndResolve(ctx context.Context, resourceIDs []ResourceIdentifier) []ResourceResult {
|
||||
var results []ResourceResult
|
||||
|
||||
for _, resource := range resources {
|
||||
u, err := r.fetchResource(ctx, resource)
|
||||
for _, resourceID := range resourceIDs {
|
||||
u, err := r.fetchResource(ctx, resourceID)
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(errors.Cause(err)) {
|
||||
results = append(results, ResourceResult{
|
||||
Resource: resource,
|
||||
ResourceIdentifier: resourceID,
|
||||
Result: &status.Result{
|
||||
Status: status.CurrentStatus,
|
||||
Message: "Resource does not exist",
|
||||
@@ -87,17 +126,17 @@ func (r *Resolver) FetchAndResolve(ctx context.Context, resources []ResourceIden
|
||||
Status: status.UnknownStatus,
|
||||
Message: fmt.Sprintf("Error fetching resource from cluster: %v", err),
|
||||
},
|
||||
Resource: resource,
|
||||
Error: err,
|
||||
ResourceIdentifier: resourceID,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
res, err := r.statusComputeFunc(u)
|
||||
results = append(results, ResourceResult{
|
||||
Result: res,
|
||||
Resource: resource,
|
||||
Error: err,
|
||||
Result: res,
|
||||
ResourceIdentifier: resourceID,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,7 +178,7 @@ const (
|
||||
type EventResource struct {
|
||||
// Identifier contains information that identifies which resource
|
||||
// this information is about.
|
||||
Identifier ResourceIdentifier
|
||||
ResourceIdentifier ResourceIdentifier
|
||||
|
||||
// Status is the latest status for the given resource.
|
||||
Status status.Status
|
||||
@@ -153,9 +192,18 @@ type EventResource struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
// WaitForStatus polls all the provided resources until all of them has
|
||||
// reached the Current status. Updates the channel as resources change their status and
|
||||
// when the wait is either completed or aborted.
|
||||
// WaitForStatus polls all the provided resources until all of them have reached the Current
|
||||
// status or the timeout specified through the context is reached. Updates on the status
|
||||
// of individual resources and the aggregate status is provided through the Event channel.
|
||||
func (r *Resolver) WaitForStatusOfObjects(ctx context.Context, objects []KubernetesObject) <-chan Event {
|
||||
resourceIds := resourceIdentifiersFromObjects(objects)
|
||||
return r.WaitForStatus(ctx, resourceIds)
|
||||
}
|
||||
|
||||
// WaitForStatus polls all the resources references by the provided ResourceIdentifiers until
|
||||
// all of them have reached the Current status or the timeout specified through the context is
|
||||
// reached. Updates on the status of individual resources and the aggregate status is provided
|
||||
// through the Event channel.
|
||||
func (r *Resolver) WaitForStatus(ctx context.Context, resources []ResourceIdentifier) <-chan Event {
|
||||
eventChan := make(chan Event)
|
||||
|
||||
@@ -225,12 +273,11 @@ func (r *Resolver) WaitForStatus(ctx context.Context, resources []ResourceIdenti
|
||||
// Completed type event. If the aggregate status has become Current, this function
|
||||
// will return true to signal that it is done.
|
||||
func (r *Resolver) checkAllResources(ctx context.Context, waitState *waitState, eventChan chan Event) bool {
|
||||
for id := range waitState.ResourceWaitStates {
|
||||
for resourceID := range waitState.ResourceWaitStates {
|
||||
// Make sure we have a local copy since we are passing
|
||||
// pointers to this variable as parameters to functions
|
||||
identifier := id
|
||||
u, err := r.fetchResource(ctx, &identifier)
|
||||
eventResource, updateObserved := waitState.ResourceObserved(&identifier, u, err)
|
||||
u, err := r.fetchResource(ctx, resourceID)
|
||||
eventResource, updateObserved := waitState.ResourceObserved(resourceID, u, err)
|
||||
// Find the aggregate status based on the new state for this resource.
|
||||
aggStatus := waitState.AggregateStatus()
|
||||
// We want events for changes in status for each resource, so send
|
||||
@@ -259,15 +306,25 @@ func (r *Resolver) checkAllResources(ctx context.Context, waitState *waitState,
|
||||
// through the client available in the Resolver. It returns the resource
|
||||
// as an Unstructured.
|
||||
func (r *Resolver) fetchResource(ctx context.Context, identifier ResourceIdentifier) (*unstructured.Unstructured, error) {
|
||||
key := types.NamespacedName{Name: identifier.GetName(), Namespace: identifier.GetNamespace()}
|
||||
u := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": identifier.GetAPIVersion(),
|
||||
"kind": identifier.GetKind(),
|
||||
},
|
||||
// We need to look up the preferred version for the GroupKind and
|
||||
// whether the resource type is cluster scoped. We look this
|
||||
// up with the RESTMapper.
|
||||
mapping, err := r.mapper.RESTMapping(identifier.GroupKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := r.client.Get(ctx, key, u)
|
||||
//return u, err
|
||||
|
||||
// Resources might not have the namespace set, which means we need to set
|
||||
// it to `default` if the resource is namespace scoped.
|
||||
namespace := identifier.Namespace
|
||||
if namespace == "" && mapping.Scope.Name() == meta.RESTScopeNameNamespace {
|
||||
namespace = defaultNamespace
|
||||
}
|
||||
|
||||
key := types.NamespacedName{Name: identifier.Name, Namespace: namespace}
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(mapping.GroupVersionKind)
|
||||
err = r.client.Get(ctx, key, u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error fetching resource from cluster")
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
@@ -32,6 +34,7 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
resources []runtime.Object
|
||||
mapperGVKs []schema.GroupVersionKind
|
||||
expectedResults []result
|
||||
}{
|
||||
"no resources": {
|
||||
@@ -52,6 +55,9 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
mapperGVKs: []schema.GroupVersionKind{
|
||||
appsv1.SchemeGroupVersion.WithKind("Deployment"),
|
||||
},
|
||||
expectedResults: []result{
|
||||
{
|
||||
status: status.InProgressStatus,
|
||||
@@ -92,6 +98,10 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
mapperGVKs: []schema.GroupVersionKind{
|
||||
appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
|
||||
corev1.SchemeGroupVersion.WithKind("Secret"),
|
||||
},
|
||||
expectedResults: []result{
|
||||
{
|
||||
status: status.CurrentStatus,
|
||||
@@ -109,18 +119,18 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme, tc.resources...)
|
||||
resolver := NewResolver(fakeClient, testPollInterval)
|
||||
|
||||
resolver := NewResolver(fakeClient, newRESTMapper(tc.mapperGVKs...), testPollInterval)
|
||||
resolver.statusComputeFunc = status.Compute
|
||||
|
||||
var identifiers []ResourceIdentifier
|
||||
for _, resource := range tc.resources {
|
||||
gvk := resource.GetObjectKind().GroupVersionKind()
|
||||
r := resource.(metav1.Object)
|
||||
identifiers = append(identifiers, &resourceKey{
|
||||
name: r.GetName(),
|
||||
namespace: r.GetNamespace(),
|
||||
apiVersion: gvk.GroupVersion().String(),
|
||||
kind: gvk.Kind,
|
||||
identifiers = append(identifiers, ResourceIdentifier{
|
||||
Name: r.GetName(),
|
||||
Namespace: r.GetNamespace(),
|
||||
GroupKind: gvk.GroupKind(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,7 +138,7 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
for i, res := range results {
|
||||
id := identifiers[i]
|
||||
expectedRes := tc.expectedResults[i]
|
||||
rid := fmt.Sprintf("%s/%s", id.GetNamespace(), id.GetName())
|
||||
rid := fmt.Sprintf("%s/%s", id.Namespace, id.Name)
|
||||
if expectedRes.error {
|
||||
if res.Error == nil {
|
||||
t.Errorf("expected error for resource %s, but didn't get one", rid)
|
||||
@@ -150,13 +160,15 @@ func TestFetchAndResolve(t *testing.T) {
|
||||
|
||||
func TestFetchAndResolveUnknownResource(t *testing.T) {
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme)
|
||||
resolver := NewResolver(fakeClient, testPollInterval)
|
||||
resolver := NewResolver(fakeClient, newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")), testPollInterval)
|
||||
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
|
||||
&resourceKey{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deploymnet",
|
||||
name: "myDeployment",
|
||||
namespace: "default",
|
||||
{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Name: "myDeployment",
|
||||
Namespace: "default",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -181,14 +193,17 @@ func TestFetchAndResolveWithFetchError(t *testing.T) {
|
||||
&fakeReader{
|
||||
Err: expectedError,
|
||||
},
|
||||
newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")),
|
||||
testPollInterval,
|
||||
)
|
||||
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
|
||||
&resourceKey{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deploymnet",
|
||||
name: "myDeployment",
|
||||
namespace: "default",
|
||||
{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Name: "myDeployment",
|
||||
Namespace: "default",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -222,7 +237,7 @@ func TestFetchAndResolveComputeStatusError(t *testing.T) {
|
||||
}
|
||||
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme.Scheme, resource)
|
||||
resolver := NewResolver(fakeClient, testPollInterval)
|
||||
resolver := NewResolver(fakeClient, newRESTMapper(appsv1.SchemeGroupVersion.WithKind("Deployment")), testPollInterval)
|
||||
|
||||
resolver.statusComputeFunc = func(u *unstructured.Unstructured) (*status.Result, error) {
|
||||
return &status.Result{
|
||||
@@ -231,11 +246,13 @@ func TestFetchAndResolveComputeStatusError(t *testing.T) {
|
||||
}, expectedError
|
||||
}
|
||||
results := resolver.FetchAndResolve(context.TODO(), []ResourceIdentifier{
|
||||
&resourceKey{
|
||||
apiVersion: resource.APIVersion,
|
||||
kind: resource.Kind,
|
||||
name: resource.GetName(),
|
||||
namespace: resource.GetNamespace(),
|
||||
{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: resource.GroupVersionKind().Group,
|
||||
Kind: resource.Kind,
|
||||
},
|
||||
Name: resource.GetName(),
|
||||
Namespace: resource.GetNamespace(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -358,23 +375,28 @@ func TestWaitForStatus(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
var objs []runtime.Object
|
||||
statusResults := make(map[resourceKey][]*status.Result)
|
||||
statusResults := make(map[ResourceIdentifier][]*status.Result)
|
||||
var identifiers []ResourceIdentifier
|
||||
|
||||
for obj, statuses := range tc.resources {
|
||||
objs = append(objs, obj)
|
||||
identifier := keyFromObject(obj)
|
||||
identifiers = append(identifiers, &identifier)
|
||||
identifier := resourceIdentifierFromRuntimeObject(obj)
|
||||
identifiers = append(identifiers, identifier)
|
||||
statusResults[identifier] = statuses
|
||||
}
|
||||
|
||||
statusComputer := statusComputer{
|
||||
results: statusResults,
|
||||
resourceCallCount: make(map[resourceKey]int),
|
||||
resourceCallCount: make(map[ResourceIdentifier]int),
|
||||
}
|
||||
|
||||
resolver := &Resolver{
|
||||
client: fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
|
||||
client: fake.NewFakeClientWithScheme(scheme.Scheme, objs...),
|
||||
mapper: newRESTMapper(
|
||||
appsv1.SchemeGroupVersion.WithKind("Deployment"),
|
||||
appsv1.SchemeGroupVersion.WithKind("StatefulSet"),
|
||||
corev1.SchemeGroupVersion.WithKind("Service"),
|
||||
),
|
||||
statusComputeFunc: statusComputer.Compute,
|
||||
pollInterval: testPollInterval,
|
||||
}
|
||||
@@ -397,20 +419,20 @@ func TestWaitForStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
var aggregateStatuses []status.Status
|
||||
resourceStatuses := make(map[resourceKey][]status.Status)
|
||||
resourceStatuses := make(map[ResourceIdentifier][]status.Status)
|
||||
for _, e := range events {
|
||||
aggregateStatuses = append(aggregateStatuses, e.AggregateStatus)
|
||||
if e.EventResource != nil {
|
||||
identifier := keyFromResourceIdentifier(e.EventResource.Identifier)
|
||||
identifier := e.EventResource.ResourceIdentifier
|
||||
resourceStatuses[identifier] = append(resourceStatuses[identifier], e.EventResource.Status)
|
||||
}
|
||||
}
|
||||
|
||||
for resource, expectedStatuses := range tc.expectedResourceStatuses {
|
||||
identifier := keyFromObject(resource)
|
||||
identifier := resourceIdentifierFromRuntimeObject(resource)
|
||||
actualStatuses := resourceStatuses[identifier]
|
||||
if !reflect.DeepEqual(expectedStatuses, actualStatuses) {
|
||||
t.Errorf("expected statuses %v for resource %s/%s, but got %v", expectedStatuses, identifier.namespace, identifier.name, actualStatuses)
|
||||
t.Errorf("expected statuses %v for resource %s/%s, but got %v", expectedStatuses, identifier.Namespace, identifier.Name, actualStatuses)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,21 +445,25 @@ func TestWaitForStatus(t *testing.T) {
|
||||
|
||||
func TestWaitForStatusDeletedResources(t *testing.T) {
|
||||
statusComputer := statusComputer{
|
||||
results: make(map[resourceKey][]*status.Result),
|
||||
resourceCallCount: make(map[resourceKey]int),
|
||||
results: make(map[ResourceIdentifier][]*status.Result),
|
||||
resourceCallCount: make(map[ResourceIdentifier]int),
|
||||
}
|
||||
|
||||
resolver := &Resolver{
|
||||
client: fake.NewFakeClientWithScheme(scheme.Scheme),
|
||||
client: fake.NewFakeClientWithScheme(scheme.Scheme),
|
||||
mapper: newRESTMapper(
|
||||
appsv1.SchemeGroupVersion.WithKind("Deployment"),
|
||||
corev1.SchemeGroupVersion.WithKind("Service"),
|
||||
),
|
||||
statusComputeFunc: statusComputer.Compute,
|
||||
pollInterval: testPollInterval,
|
||||
}
|
||||
|
||||
depResourceIdentifier := keyFromObject(deploymentResource)
|
||||
serviceResourceIdentifier := keyFromObject(serviceResource)
|
||||
depResourceIdentifier := resourceIdentifierFromRuntimeObject(deploymentResource)
|
||||
serviceResourceIdentifier := resourceIdentifierFromRuntimeObject(serviceResource)
|
||||
identifiers := []ResourceIdentifier{
|
||||
&depResourceIdentifier,
|
||||
&serviceResourceIdentifier,
|
||||
depResourceIdentifier,
|
||||
serviceResourceIdentifier,
|
||||
}
|
||||
|
||||
eventChan := resolver.WaitForStatus(context.TODO(), identifiers)
|
||||
@@ -499,17 +525,12 @@ loop:
|
||||
type statusComputer struct {
|
||||
t *testing.T
|
||||
|
||||
results map[resourceKey][]*status.Result
|
||||
resourceCallCount map[resourceKey]int
|
||||
results map[ResourceIdentifier][]*status.Result
|
||||
resourceCallCount map[ResourceIdentifier]int
|
||||
}
|
||||
|
||||
func (s *statusComputer) Compute(u *unstructured.Unstructured) (*status.Result, error) {
|
||||
identifier := resourceKey{
|
||||
apiVersion: u.GetAPIVersion(),
|
||||
kind: u.GetKind(),
|
||||
name: u.GetName(),
|
||||
namespace: u.GetNamespace(),
|
||||
}
|
||||
identifier := resourceIdentifierFromRuntimeObject(u)
|
||||
|
||||
resourceResults, ok := s.results[identifier]
|
||||
if !ok {
|
||||
@@ -559,3 +580,15 @@ var serviceResource = &corev1.Service{
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
func newRESTMapper(gvks ...schema.GroupVersionKind) meta.RESTMapper {
|
||||
var groupVersions []schema.GroupVersion
|
||||
for _, gvk := range gvks {
|
||||
groupVersions = append(groupVersions, gvk.GroupVersion())
|
||||
}
|
||||
mapper := meta.NewDefaultRESTMapper(groupVersions)
|
||||
for _, gvk := range gvks {
|
||||
mapper.Add(gvk, meta.RESTScopeNamespace)
|
||||
}
|
||||
return mapper
|
||||
}
|
||||
|
||||
@@ -10,41 +10,12 @@ import (
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
// resourceKey is a minimal implementation of
|
||||
// the ResourceIdentifier interface.
|
||||
type resourceKey struct {
|
||||
name string
|
||||
namespace string
|
||||
apiVersion string
|
||||
kind string
|
||||
}
|
||||
|
||||
// GetName returns the name of the resource.
|
||||
func (r *resourceKey) GetName() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
// GetNamespace returns the namespace of the resource.
|
||||
func (r *resourceKey) GetNamespace() string {
|
||||
return r.namespace
|
||||
}
|
||||
|
||||
// GetAPIVersion returns the API version of the resource.
|
||||
func (r *resourceKey) GetAPIVersion() string {
|
||||
return r.apiVersion
|
||||
}
|
||||
|
||||
// GetKind returns the Kind of the resource.
|
||||
func (r *resourceKey) GetKind() string {
|
||||
return r.kind
|
||||
}
|
||||
|
||||
// waitState keeps the state about the resources and their last
|
||||
// observed state. This is used to determine any changes in state
|
||||
// so events can be sent when needed.
|
||||
type waitState struct {
|
||||
// ResourceWaitStates contains wait state for each of the resources.
|
||||
ResourceWaitStates map[resourceKey]*resourceWaitState
|
||||
ResourceWaitStates map[ResourceIdentifier]*resourceWaitState
|
||||
|
||||
// statusComputeFunc defines the function used to compute the state of
|
||||
// a single resource. This is available for testing purposes.
|
||||
@@ -62,17 +33,11 @@ type resourceWaitState struct {
|
||||
|
||||
// newWaitState creates a new waitState object and initializes it with the
|
||||
// provided slice of resources and the provided statusComputeFunc.
|
||||
func newWaitState(resources []ResourceIdentifier, statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)) *waitState {
|
||||
resourceWaitStates := make(map[resourceKey]*resourceWaitState)
|
||||
func newWaitState(resourceIDs []ResourceIdentifier, statusComputeFunc func(u *unstructured.Unstructured) (*status.Result, error)) *waitState {
|
||||
resourceWaitStates := make(map[ResourceIdentifier]*resourceWaitState)
|
||||
|
||||
for _, r := range resources {
|
||||
identifier := resourceKey{
|
||||
apiVersion: r.GetAPIVersion(),
|
||||
kind: r.GetKind(),
|
||||
name: r.GetName(),
|
||||
namespace: r.GetNamespace(),
|
||||
}
|
||||
resourceWaitStates[identifier] = &resourceWaitState{}
|
||||
for _, resourceID := range resourceIDs {
|
||||
resourceWaitStates[resourceID] = &resourceWaitState{}
|
||||
}
|
||||
|
||||
return &waitState{
|
||||
@@ -107,19 +72,12 @@ func (w *waitState) AggregateStatus() status.Status {
|
||||
// that will be true if the status of the observed resource has changed
|
||||
// since the previous observation and false it not. This is used to determine
|
||||
// whether a new event should be sent based on this observation.
|
||||
func (w *waitState) ResourceObserved(id ResourceIdentifier, resource *unstructured.Unstructured, err error) (EventResource, bool) {
|
||||
identifier := resourceKey{
|
||||
name: id.GetName(),
|
||||
namespace: id.GetNamespace(),
|
||||
apiVersion: id.GetAPIVersion(),
|
||||
kind: id.GetKind(),
|
||||
}
|
||||
|
||||
func (w *waitState) ResourceObserved(resourceID ResourceIdentifier, resource *unstructured.Unstructured, err error) (EventResource, bool) {
|
||||
// Check for nil is not needed here as the id passed in comes
|
||||
// from iterating over the keys of the map.
|
||||
rws := w.ResourceWaitStates[identifier]
|
||||
rws := w.ResourceWaitStates[resourceID]
|
||||
|
||||
eventResource := w.getEventResource(identifier, resource, err)
|
||||
eventResource := w.getEventResource(resourceID, resource, err)
|
||||
// If the new eventResource is identical to the previous one, we return
|
||||
// with the last return value indicating this is not a new event.
|
||||
if rws.LastEvent != nil && reflect.DeepEqual(eventResource, *rws.LastEvent) {
|
||||
@@ -133,22 +91,22 @@ func (w *waitState) ResourceObserved(id ResourceIdentifier, resource *unstructur
|
||||
// the provided resourceKey. The EventResource contains information about the
|
||||
// latest status for the given resource, so it computes status for the resource
|
||||
// as well as check for deletion.
|
||||
func (w *waitState) getEventResource(identifier resourceKey, resource *unstructured.Unstructured, err error) EventResource {
|
||||
func (w *waitState) getEventResource(resourceID ResourceIdentifier, resource *unstructured.Unstructured, err error) EventResource {
|
||||
// Get the resourceWaitState for this resource. It contains information
|
||||
// of the previous observed statuses. We don't need to check for nil here
|
||||
// as the identifier comes from iterating over the keys of the
|
||||
// ResourceWaitState map.
|
||||
r := w.ResourceWaitStates[identifier]
|
||||
r := w.ResourceWaitStates[resourceID]
|
||||
|
||||
// If fetching the resource from the cluster failed, we don't really
|
||||
// know anything about the status of the resource, so simply
|
||||
// report the status as Unknown.
|
||||
if err != nil && !k8serrors.IsNotFound(errors.Cause(err)) {
|
||||
return EventResource{
|
||||
Identifier: &identifier,
|
||||
Status: status.UnknownStatus,
|
||||
Message: fmt.Sprintf("Error: %s", err),
|
||||
Error: err,
|
||||
ResourceIdentifier: resourceID,
|
||||
Status: status.UnknownStatus,
|
||||
Message: fmt.Sprintf("Error: %s", err),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,9 +119,9 @@ func (w *waitState) getEventResource(identifier resourceKey, resource *unstructu
|
||||
if k8serrors.IsNotFound(errors.Cause(err)) {
|
||||
r.HasBeenCurrent = true
|
||||
return EventResource{
|
||||
Identifier: &identifier,
|
||||
Status: status.CurrentStatus,
|
||||
Message: fmt.Sprintf("Resource has been deleted"),
|
||||
ResourceIdentifier: resourceID,
|
||||
Status: status.CurrentStatus,
|
||||
Message: fmt.Sprintf("Resource has been deleted"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,9 +135,9 @@ func (w *waitState) getEventResource(identifier resourceKey, resource *unstructu
|
||||
|
||||
if resource.GetDeletionTimestamp() != nil {
|
||||
return EventResource{
|
||||
Identifier: &identifier,
|
||||
Status: status.TerminatingStatus,
|
||||
Message: fmt.Sprintf("Resource is terminating"),
|
||||
ResourceIdentifier: resourceID,
|
||||
Status: status.TerminatingStatus,
|
||||
Message: fmt.Sprintf("Resource is terminating"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +146,10 @@ func (w *waitState) getEventResource(identifier resourceKey, resource *unstructu
|
||||
// as Unknown.
|
||||
if err != nil {
|
||||
return EventResource{
|
||||
Identifier: &identifier,
|
||||
Status: status.UnknownStatus,
|
||||
Message: fmt.Sprintf("Error: %s", err),
|
||||
Error: err,
|
||||
ResourceIdentifier: resourceID,
|
||||
Status: status.UnknownStatus,
|
||||
Message: fmt.Sprintf("Error: %s", err),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +162,8 @@ func (w *waitState) getEventResource(identifier resourceKey, resource *unstructu
|
||||
}
|
||||
|
||||
return EventResource{
|
||||
Identifier: &identifier,
|
||||
Status: statusResult.Status,
|
||||
Message: statusResult.Message,
|
||||
ResourceIdentifier: resourceID,
|
||||
Status: statusResult.Status,
|
||||
Message: statusResult.Message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ require (
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.5
|
||||
k8s.io/client-go v0.17.0
|
||||
sigs.k8s.io/kustomize/api v0.3.1
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.2
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.2
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2
|
||||
sigs.k8s.io/kustomize/api v0.3.2
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.5
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.3
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.6
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
|
||||
@@ -18,5 +18,3 @@ exclude (
|
||||
github.com/russross/blackfriday v2.0.0+incompatible
|
||||
sigs.k8s.io/kustomize/api v0.2.0
|
||||
)
|
||||
|
||||
replace sigs.k8s.io/kustomize/api v0.3.1 => ../api
|
||||
|
||||
@@ -107,6 +107,8 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
|
||||
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
|
||||
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 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
@@ -344,6 +346,8 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d h1:BzRvVq1EHuIjxpijCEKpAxzKUUMurOQ4sknehIATRh8=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM=
|
||||
@@ -542,15 +546,22 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
|
||||
k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY=
|
||||
k8s.io/cli-runtime v0.17.0 h1:XEuStbJBHCQlEKFyTQmceDKEWOSYHZkcYWKp3SsQ9Hk=
|
||||
k8s.io/cli-runtime v0.17.0/go.mod h1:1E5iQpMODZq2lMWLUJELwRu2MLWIzwvMgDBpn3Y81Qo=
|
||||
k8s.io/client-go v0.0.0-20191214190045-a32a6f7a3052/go.mod h1:tAaoc/sYuIL0+njJefSAmE28CIcxyaFV4kbIujBlY2s=
|
||||
k8s.io/client-go v0.0.0-20191219150334-0b8da7416048/go.mod h1:ZEe8ZASDUAuqVGJ+UN0ka0PfaR+b6a6E1PGsSNZRui8=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao=
|
||||
k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c=
|
||||
k8s.io/component-base v0.17.0 h1:BnDFcmBDq+RPpxXjmuYnZXb59XNN9CaFrX8ba9+3xrA=
|
||||
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
@@ -561,9 +572,9 @@ 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-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kubectl v0.17.0 h1:xD4EWlL+epc/JTO1gvSjmV9yiYF0Z2wiHK2DIek6URY=
|
||||
k8s.io/kubectl v0.17.0/go.mod h1:jIPrUAW656Vzn9wZCCe0PC+oTcu56u2HgFD21Xbfk1s=
|
||||
k8s.io/metrics v0.17.0/go.mod h1:EH1D3YAwN6d7bMelrElnLhLg72l/ERStyv2SIQVt6Do=
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd h1:nZX5+wEqTu/EBIYjrZlFOA63z4+Zcy96lDkCZPU9a9c=
|
||||
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo=
|
||||
k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
@@ -579,12 +590,15 @@ mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8Eo
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.2 h1:FphfIoGJ0jGGJJXq9WoG5sqqEIuTeDGx58E5NWHV8Hc=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.2/go.mod h1:c6IBoPpAAm5a2aD+0iH8IfeyCF5GPsY5Ws57Dwpcvg0=
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.2 h1:MxUAU5ie0tqx2MuDrUlcAL+Mgt8LVFcXc2scinSD8/w=
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.2/go.mod h1:SbNCE1g937W1yvaQrZbvPNT3aDRdicdeW2qXLTa+YiM=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2 h1:Rl/wMrnpZzZjsVeFIIOAb92Kz/UfLrTUEXjiHW6oS0o=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.2/go.mod h1:rywm/rcR5LmCBghz9956tE45OdUPChFoXVVs+WmhMTI=
|
||||
sigs.k8s.io/kustomize/api v0.3.2 h1:64gvYVAvqe2fNfcTevtXh/GmLwVwHIcJ2Z5HBMfjncs=
|
||||
sigs.k8s.io/kustomize/api v0.3.2/go.mod h1:A+ATnlHqzictQfQC1q3KB/T6MSr0UWQsrrLxMWkge2E=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.5 h1:mFJowsk9IGvwm5dUpVB+ZM63on2JjgaCy+YcVsFaVxU=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.0.5/go.mod h1:L47nDnZDfGFQG3gnPJLG2UABn0nVb9v+ndceyMH0jjU=
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.3 h1:cXn6GqRnOQtp4EC1+NiJKdUHE/aQ+5HhtAB28R4sVXA=
|
||||
sigs.k8s.io/kustomize/cmd/kubectl v0.0.3/go.mod h1:JnS9HnTjUUMOE44WNboy/wi89J/K/XbAoU7O/iPXqqE=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.5/go.mod h1:waxTrzQRK9i6/5fR5HNo8xa4YwvWn8t85vMnOGFEZik=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.6 h1:KhQr7JwpCseFTSWCwqp4CJ4mY6Kx+i34tF4e0eNkcXw=
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.6/go.mod h1:tDOfJjL6slQVBLHJ76XfXAFgAOEdfm04AW2HehYOp8k=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
@@ -22,7 +22,7 @@ linters:
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- gosec
|
||||
# - gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
|
||||
@@ -152,6 +152,10 @@ type ContainerFilter struct {
|
||||
checkInput func(string)
|
||||
}
|
||||
|
||||
func (c ContainerFilter) String() string {
|
||||
return c.Image
|
||||
}
|
||||
|
||||
// StorageMount represents a container's mounted storage option(s)
|
||||
type StorageMount struct {
|
||||
// Type of mount e.g. bind mount, local volume, etc.
|
||||
@@ -225,6 +229,11 @@ func (c *ContainerFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode,
|
||||
}
|
||||
|
||||
resourceDir := path.Clean(path.Dir(p))
|
||||
if path.Base(resourceDir) == functionsDirectoryName {
|
||||
// Functions in the `functions` directory are scoped to
|
||||
// themselves, and should see themselves as input
|
||||
resourceDir = path.Dir(resourceDir)
|
||||
}
|
||||
if !strings.HasPrefix(resourceDir, dir) {
|
||||
// this Resource doesn't fall under the function scope if it
|
||||
// isn't in a subdirectory of where the function lives
|
||||
|
||||
@@ -587,6 +587,96 @@ metadata:
|
||||
`, b.String())
|
||||
}
|
||||
|
||||
func TestFilter_Filter_globalScope(t *testing.T) {
|
||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
config.kubernetes.io/path: 'foo/bar.yaml'
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
input, err := (&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-foo
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service-foo
|
||||
`)}).Read()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
GlobalScope: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deployment-foo
|
||||
annotations:
|
||||
config.kubernetes.io/index: '0'
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service-foo
|
||||
annotations:
|
||||
config.kubernetes.io/index: '1'
|
||||
functionConfig: {apiVersion: apps/v1, kind: Deployment, metadata: {name: foo, annotations: {
|
||||
config.kubernetes.io/path: 'foo/bar.yaml'}}}
|
||||
`, s) {
|
||||
t.FailNow()
|
||||
}
|
||||
},
|
||||
}).Filter(input)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.True(t, called) {
|
||||
return
|
||||
}
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = kio.ByteWriter{Writer: b, KeepReaderAnnotations: true}.Write(result)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Resources should be preserved -- paths shouldn't be set by container
|
||||
assert.Equal(t, `apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: deployment-foo
|
||||
annotations:
|
||||
config.kubernetes.io/index: '0'
|
||||
config.kubernetes.io/path: 'foo/statefulset_deployment-foo.yaml'
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service-foo
|
||||
annotations:
|
||||
config.kubernetes.io/index: '1'
|
||||
config.kubernetes.io/path: 'foo/service_service-foo.yaml'
|
||||
`, b.String())
|
||||
}
|
||||
|
||||
func TestFilter_Filter_scopeFunctionsDir(t *testing.T) {
|
||||
// functions under "functions/" dir should be scoped to parent dir
|
||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||
@@ -778,3 +868,26 @@ metadata:
|
||||
config.kubernetes.io/index: '1'
|
||||
`, b.String())
|
||||
}
|
||||
|
||||
func TestContainerFilter_scope(t *testing.T) {
|
||||
cf := &ContainerFilter{}
|
||||
|
||||
fnR, err := yaml.Parse(`apiVersion: config.kubernetes.io/v1beta1
|
||||
kind: ConfigFunction
|
||||
metadata:
|
||||
name: config-function
|
||||
annotations:
|
||||
config.kubernetes.io/path: 'functions/bar.yaml'
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
inRs := []*yaml.RNode{fnR}
|
||||
inScopeRs, notInScopeRs, err := cf.scope(".", inRs)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Len(t, inScopeRs, 1, "Number of in-scope Resources")
|
||||
assert.Len(t, notInScopeRs, 0, "Number of out-of-scope Resources")
|
||||
}
|
||||
|
||||
@@ -5,11 +5,16 @@ package runfn
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
@@ -22,13 +27,31 @@ type RunFns struct {
|
||||
Path string
|
||||
|
||||
// FunctionPaths Paths allows functions to be specified outside the configuration
|
||||
// directory
|
||||
// directory.
|
||||
// Functions provided on FunctionPaths are globally scoped.
|
||||
// If FunctionPaths length is > 0, then NoFunctionsFromInput defaults to true
|
||||
FunctionPaths []string
|
||||
|
||||
// Functions is an explicit list of functions to run against the input.
|
||||
// Functions provided on Functions are globally scoped.
|
||||
// If Functions length is > 0, then NoFunctionsFromInput defaults to true
|
||||
Functions []*yaml.RNode
|
||||
|
||||
// GlobalScope if true, functions read from input will be scoped globally rather
|
||||
// than only to Resources under their subdirs.
|
||||
GlobalScope bool
|
||||
|
||||
// Input can be set to read the Resources from Input rather than from a directory
|
||||
Input io.Reader
|
||||
|
||||
// Output can be set to write the result to Output rather than back to the directory
|
||||
Output io.Writer
|
||||
|
||||
// containerFilterProvider may be override by tests to fake invoking containers
|
||||
// NoFunctionsFromInput if set to true will not read any functions from the input,
|
||||
// and only use explicit sources
|
||||
NoFunctionsFromInput *bool
|
||||
|
||||
// for testing purposes only
|
||||
containerFilterProvider func(string, string, *yaml.RNode) kio.Filter
|
||||
}
|
||||
|
||||
@@ -43,55 +66,225 @@ func (r RunFns) Execute() error {
|
||||
|
||||
// default the containerFilterProvider if it hasn't been override. Split out for testing.
|
||||
(&r).init()
|
||||
nodes, fltrs, output, err := r.getNodesAndFilters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.runFunctions(nodes, output, fltrs)
|
||||
}
|
||||
|
||||
// identify the configuration functions in the directory
|
||||
func (r RunFns) getNodesAndFilters() (
|
||||
*kio.PackageBuffer, []kio.Filter, *kio.LocalPackageReadWriter, error) {
|
||||
// Read Resources from Directory or Input
|
||||
buff := &kio.PackageBuffer{}
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.Path}},
|
||||
p := kio.Pipeline{Outputs: []kio.Writer{buff}}
|
||||
// save the output dir because we will need it to write back
|
||||
// the same one for reading must be used for writing if deleting Resources
|
||||
var outputPkg *kio.LocalPackageReadWriter
|
||||
if r.Path != "" {
|
||||
outputPkg = &kio.LocalPackageReadWriter{PackagePath: r.Path}
|
||||
}
|
||||
|
||||
if r.Input == nil {
|
||||
p.Inputs = []kio.Reader{outputPkg}
|
||||
} else {
|
||||
p.Inputs = []kio.Reader{&kio.ByteReader{Reader: r.Input}}
|
||||
}
|
||||
if err := p.Execute(); err != nil {
|
||||
return nil, nil, outputPkg, err
|
||||
}
|
||||
|
||||
fltrs, err := r.getFilters(buff.Nodes)
|
||||
if err != nil {
|
||||
return nil, nil, outputPkg, err
|
||||
}
|
||||
return buff, fltrs, outputPkg, nil
|
||||
}
|
||||
|
||||
func (r RunFns) getFilters(nodes []*yaml.RNode) ([]kio.Filter, error) {
|
||||
var fltrs []kio.Filter
|
||||
|
||||
// implicit filters from the input Resources
|
||||
f, err := r.getFunctionsFromInput(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fltrs = append(fltrs, f...)
|
||||
|
||||
// explicit filters from a list of directories
|
||||
f, err = r.getFunctionsFromFunctionPaths()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fltrs = append(fltrs, f...)
|
||||
|
||||
// explicit filters from a list of directories
|
||||
f = r.getFunctionsFromFunctions()
|
||||
fltrs = append(fltrs, f...)
|
||||
|
||||
return fltrs, nil
|
||||
}
|
||||
|
||||
// runFunctions runs the fltrs against the input and writes to either r.Output or output
|
||||
func (r RunFns) runFunctions(
|
||||
input kio.Reader, output kio.Writer, fltrs []kio.Filter) error {
|
||||
// use the previously read Resources as input
|
||||
var outputs []kio.Writer
|
||||
if r.Output == nil {
|
||||
// write back to the package
|
||||
outputs = append(outputs, output)
|
||||
} else {
|
||||
// write to the output instead of the directory if r.Output is specified or
|
||||
// the output is nil (reading from Input)
|
||||
outputs = append(outputs, kio.ByteWriter{Writer: r.Output})
|
||||
}
|
||||
return kio.Pipeline{Inputs: []kio.Reader{input}, Filters: fltrs, Outputs: outputs}.Execute()
|
||||
}
|
||||
|
||||
// getFunctionsFromInput scans the input for functions and runs them
|
||||
func (r RunFns) getFunctionsFromInput(nodes []*yaml.RNode) ([]kio.Filter, error) {
|
||||
if *r.NoFunctionsFromInput {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var fltrs []kio.Filter
|
||||
buff := &kio.PackageBuffer{}
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.PackageBuffer{Nodes: nodes}},
|
||||
Filters: []kio.Filter{&filters.IsReconcilerFilter{}},
|
||||
Outputs: []kio.Writer{buff},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
sortFns(buff)
|
||||
for i := range buff.Nodes {
|
||||
api := buff.Nodes[i]
|
||||
img, path := filters.GetContainerName(api)
|
||||
fltrs = append(fltrs, r.containerFilterProvider(img, path, api))
|
||||
}
|
||||
return fltrs, nil
|
||||
}
|
||||
|
||||
// getFunctionsFromFunctionPaths returns the set of functions read from r.FunctionPaths
|
||||
// as a slice of Filters
|
||||
func (r RunFns) getFunctionsFromFunctionPaths() ([]kio.Filter, error) {
|
||||
var fltrs []kio.Filter
|
||||
buff := &kio.PackageBuffer{}
|
||||
for i := range r.FunctionPaths {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: r.FunctionPaths[i]}},
|
||||
Outputs: []kio.Writer{buff},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// reconcile each local API
|
||||
var fltrs []kio.Filter
|
||||
for i := range buff.Nodes {
|
||||
api := buff.Nodes[i]
|
||||
img, path := filters.GetContainerName(api)
|
||||
fltrs = append(fltrs, r.containerFilterProvider(img, path, api))
|
||||
c := r.containerFilterProvider(img, path, api)
|
||||
cf, ok := c.(*filters.ContainerFilter)
|
||||
if ok {
|
||||
// functions provided by FunctionPaths are globally scoped
|
||||
cf.GlobalScope = true
|
||||
}
|
||||
fltrs = append(fltrs, c)
|
||||
}
|
||||
return fltrs, nil
|
||||
}
|
||||
|
||||
pkgIO := &kio.LocalPackageReadWriter{PackagePath: r.Path}
|
||||
inputs := []kio.Reader{pkgIO}
|
||||
var outputs []kio.Writer
|
||||
if r.Output == nil {
|
||||
// write back to the package
|
||||
outputs = append(outputs, pkgIO)
|
||||
} else {
|
||||
// write to the output instead of the directory
|
||||
outputs = append(outputs, kio.ByteWriter{Writer: r.Output})
|
||||
// getFunctionsFromFunctions returns the set of explicitly provided functions as
|
||||
// Filters
|
||||
func (r RunFns) getFunctionsFromFunctions() []kio.Filter {
|
||||
var fltrs []kio.Filter
|
||||
for i := range r.Functions {
|
||||
api := r.Functions[i]
|
||||
img, path := filters.GetContainerName(api)
|
||||
c := r.containerFilterProvider(img, path, api)
|
||||
cf, ok := c.(*filters.ContainerFilter)
|
||||
if ok {
|
||||
// functions provided by Functions are globally scoped
|
||||
cf.GlobalScope = true
|
||||
}
|
||||
fltrs = append(fltrs, c)
|
||||
}
|
||||
return kio.Pipeline{Inputs: inputs, Filters: fltrs, Outputs: outputs}.Execute()
|
||||
return fltrs
|
||||
}
|
||||
|
||||
// sortFns sorts functions so that functions with the longest paths come first
|
||||
func sortFns(buff *kio.PackageBuffer) {
|
||||
// sort the nodes so that we traverse them depth first
|
||||
// functions deeper in the file system tree should be run first
|
||||
sort.Slice(buff.Nodes, func(i, j int) bool {
|
||||
mi, _ := buff.Nodes[i].GetMeta()
|
||||
pi := mi.Annotations[kioutil.PathAnnotation]
|
||||
if path.Base(path.Dir(pi)) == "functions" {
|
||||
// don't count the functions dir, the functions are scoped 1 level above
|
||||
pi = path.Dir(path.Dir(pi))
|
||||
} else {
|
||||
pi = path.Dir(pi)
|
||||
}
|
||||
|
||||
mj, _ := buff.Nodes[j].GetMeta()
|
||||
pj := mj.Annotations[kioutil.PathAnnotation]
|
||||
if path.Base(path.Dir(pj)) == "functions" {
|
||||
// don't count the functions dir, the functions are scoped 1 level above
|
||||
pj = path.Dir(path.Dir(pj))
|
||||
} else {
|
||||
pj = path.Dir(pj)
|
||||
}
|
||||
|
||||
// i is "less" than j (comes earlier) if its depth is greater -- e.g. run
|
||||
// i before j if it is deeper in the directory structure
|
||||
li := len(strings.Split(pi, "/"))
|
||||
if pi == "." {
|
||||
// local dir should have 0 path elements instead of 1
|
||||
li = 0
|
||||
}
|
||||
lj := len(strings.Split(pj, "/"))
|
||||
if pj == "." {
|
||||
// local dir should have 0 path elements instead of 1
|
||||
lj = 0
|
||||
}
|
||||
if li != lj {
|
||||
// use greater-than because we want to sort with the longest
|
||||
// paths FIRST rather than last
|
||||
return li > lj
|
||||
}
|
||||
|
||||
// sort by path names if depths are equal
|
||||
return pi < pj
|
||||
})
|
||||
}
|
||||
|
||||
// init initializes the RunFns with a containerFilterProvider.
|
||||
func (r *RunFns) init() {
|
||||
if r.NoFunctionsFromInput == nil {
|
||||
// default no functions from input if any function sources are explicitly provided
|
||||
nfn := len(r.FunctionPaths) > 0 || len(r.Functions) > 0
|
||||
r.NoFunctionsFromInput = &nfn
|
||||
}
|
||||
|
||||
// if no path is specified, default reading from stdin and writing to stdout
|
||||
if r.Path == "" {
|
||||
if r.Output == nil {
|
||||
r.Output = os.Stdout
|
||||
}
|
||||
if r.Input == nil {
|
||||
r.Input = os.Stdin
|
||||
}
|
||||
}
|
||||
|
||||
// if containerFilterProvider hasn't been set, use the default
|
||||
if r.containerFilterProvider == nil {
|
||||
r.containerFilterProvider = func(image, path string, api *yaml.RNode) kio.Filter {
|
||||
cf := &filters.ContainerFilter{Image: image, Config: api, StorageMounts: r.StorageMounts}
|
||||
cf := &filters.ContainerFilter{
|
||||
Image: image,
|
||||
Config: api,
|
||||
StorageMounts: r.StorageMounts,
|
||||
GlobalScope: r.GlobalScope,
|
||||
}
|
||||
return cf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ package runfn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -22,17 +24,26 @@ const (
|
||||
ValueReplacerYAMLData = `apiVersion: v1
|
||||
kind: ValueReplacer
|
||||
metadata:
|
||||
configFn:
|
||||
container:
|
||||
image: gcr.io/example.com/image:version
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: gcr.io/example.com/image:version
|
||||
config.kubernetes.io/local-config: "true"
|
||||
stringMatch: Deployment
|
||||
replace: StatefulSet
|
||||
`
|
||||
)
|
||||
|
||||
func TestRunFns_Execute(t *testing.T) {
|
||||
func TestRunFns_init(t *testing.T) {
|
||||
instance := RunFns{}
|
||||
instance.init()
|
||||
if !assert.Equal(t, instance.Input, os.Stdin) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t, instance.Output, os.Stdout) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
api, err := yaml.Parse(`apiVersion: apps/v1
|
||||
kind:
|
||||
`)
|
||||
@@ -43,55 +54,458 @@ kind:
|
||||
assert.Equal(t, &filters.ContainerFilter{Image: "example.com:version", Config: api}, filter)
|
||||
}
|
||||
|
||||
func TestCmd_Execute(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
|
||||
func TestRunFns_Execute__initGlobalScope(t *testing.T) {
|
||||
instance := RunFns{GlobalScope: true}
|
||||
instance.init()
|
||||
if !assert.Equal(t, instance.Input, os.Stdin) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t, instance.Output, os.Stdout) {
|
||||
t.FailNow()
|
||||
}
|
||||
api, err := yaml.Parse(`apiVersion: apps/v1
|
||||
kind:
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !assert.True(t, ok) {
|
||||
t.FailNow()
|
||||
}
|
||||
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
|
||||
return
|
||||
}
|
||||
filter := instance.containerFilterProvider("example.com:version", "", api)
|
||||
assert.Equal(t, &filters.ContainerFilter{
|
||||
Image: "example.com:version", Config: api, GlobalScope: true}, filter)
|
||||
}
|
||||
|
||||
// write a test filter
|
||||
func TestRunFns_Execute__initDefault(t *testing.T) {
|
||||
b := &bytes.Buffer{}
|
||||
var tests = []struct {
|
||||
instance RunFns
|
||||
expected RunFns
|
||||
name string
|
||||
}{
|
||||
{
|
||||
instance: RunFns{},
|
||||
name: "empty",
|
||||
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
|
||||
},
|
||||
{
|
||||
name: "explicit output",
|
||||
instance: RunFns{Output: b},
|
||||
expected: RunFns{Output: b, Input: os.Stdin, NoFunctionsFromInput: getFalse()},
|
||||
},
|
||||
{
|
||||
name: "explicit input",
|
||||
instance: RunFns{Input: b},
|
||||
expected: RunFns{Output: os.Stdout, Input: b, NoFunctionsFromInput: getFalse()},
|
||||
},
|
||||
{
|
||||
name: "explicit functions -- no functions from input",
|
||||
instance: RunFns{Functions: []*yaml.RNode{{}}},
|
||||
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getTrue(), Functions: []*yaml.RNode{{}}},
|
||||
},
|
||||
{
|
||||
name: "explicit functions -- yes functions from input",
|
||||
instance: RunFns{Functions: []*yaml.RNode{{}}, NoFunctionsFromInput: getFalse()},
|
||||
expected: RunFns{Output: os.Stdout, Input: os.Stdin, NoFunctionsFromInput: getFalse(), Functions: []*yaml.RNode{{}}},
|
||||
},
|
||||
{
|
||||
name: "explicit functions in paths -- no functions from input",
|
||||
instance: RunFns{FunctionPaths: []string{"foo"}},
|
||||
expected: RunFns{
|
||||
Output: os.Stdout,
|
||||
Input: os.Stdin,
|
||||
NoFunctionsFromInput: getTrue(),
|
||||
FunctionPaths: []string{"foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "functions in paths -- yes functions from input",
|
||||
instance: RunFns{FunctionPaths: []string{"foo"}, NoFunctionsFromInput: getFalse()},
|
||||
expected: RunFns{
|
||||
Output: os.Stdout,
|
||||
Input: os.Stdin,
|
||||
NoFunctionsFromInput: getFalse(),
|
||||
FunctionPaths: []string{"foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
(&tt.instance).init()
|
||||
(&tt.instance).containerFilterProvider = nil
|
||||
if !assert.Equal(t, tt.expected, tt.instance) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getTrue() *bool {
|
||||
t := true
|
||||
return &t
|
||||
}
|
||||
|
||||
func getFalse() *bool {
|
||||
f := false
|
||||
return &f
|
||||
}
|
||||
|
||||
// TestRunFns_getFilters tests how filters are found and sorted
|
||||
func TestRunFns_getFilters(t *testing.T) {
|
||||
type f struct {
|
||||
// path to function file and string value to write
|
||||
path, value string
|
||||
// if true, create the function in a separate directory from
|
||||
// the config, and provide it through FunctionPaths
|
||||
outOfPackage bool
|
||||
|
||||
// if true, create the function as an explicit Functions input
|
||||
explicitFunction bool
|
||||
|
||||
// if true and outOfPackage is true, create a new directory
|
||||
// for this function separate from the previous one. If
|
||||
// false and outOfPackage is true, create the function in
|
||||
// the directory created for the last outOfPackage function.
|
||||
newFnPath bool
|
||||
}
|
||||
var tests = []struct {
|
||||
// function files to write
|
||||
in []f
|
||||
// images to be run in a specific order
|
||||
out []string
|
||||
// name of the test
|
||||
name string
|
||||
// value to set for NoFunctionsFromInput
|
||||
noFunctionsFromInput *bool
|
||||
}{
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "single implicit function",
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("foo", "bar.yaml"),
|
||||
value: `
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: ExampleFunction
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: gcr.io/example.com/image:v1.0.0
|
||||
config.kubernetes.io/local-config: "true"
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"gcr.io/example.com/image:v1.0.0"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- deepest first",
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("a.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("foo", "b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"b", "a"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- skip implicit with output of package",
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("foo", "a.yaml"),
|
||||
outOfPackage: true, // out of package is run last
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"a"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- skip implicit",
|
||||
noFunctionsFromInput: getTrue(),
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("foo", "a.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: nil,
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- include implicit",
|
||||
noFunctionsFromInput: getFalse(),
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("foo", "a.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"a", "b"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- implicit first",
|
||||
noFunctionsFromInput: getFalse(),
|
||||
in: []f{
|
||||
{
|
||||
path: filepath.Join("foo", "a.yaml"),
|
||||
outOfPackage: true, // out of package is run last
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"b", "a"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "explicit functions",
|
||||
in: []f{
|
||||
{
|
||||
explicitFunction: true,
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: c
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"c"},
|
||||
},
|
||||
|
||||
// Test
|
||||
//
|
||||
//
|
||||
{name: "sort functions -- implicit first",
|
||||
noFunctionsFromInput: getFalse(),
|
||||
in: []f{
|
||||
{
|
||||
explicitFunction: true,
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: c
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("foo", "a.yaml"),
|
||||
outOfPackage: true, // out of package is run last
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: a
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b.yaml"),
|
||||
value: `
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/function: |
|
||||
container:
|
||||
image: b
|
||||
`,
|
||||
},
|
||||
},
|
||||
out: []string{"b", "a", "c"},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// setup the test directory
|
||||
d := setupTest(t)
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
// write the functions to files
|
||||
var fnPaths []string
|
||||
var parsedFns []*yaml.RNode
|
||||
var fnPath string
|
||||
var err error
|
||||
for _, f := range tt.in {
|
||||
// get the location for the file
|
||||
var dir string
|
||||
switch {
|
||||
case f.outOfPackage:
|
||||
// if out of package, write to a separate temp directory
|
||||
if f.newFnPath || fnPath == "" {
|
||||
// create a new fn directory
|
||||
fnPath, err = ioutil.TempDir("", "kustomize-test")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(fnPath)
|
||||
fnPaths = append(fnPaths, fnPath)
|
||||
}
|
||||
dir = fnPath
|
||||
case f.explicitFunction:
|
||||
parsedFns = append(parsedFns, yaml.MustParse(f.value))
|
||||
default:
|
||||
// if in package, write to the dir containing the configs
|
||||
dir = d
|
||||
}
|
||||
|
||||
if !f.explicitFunction {
|
||||
// create the parent dir and write the file
|
||||
err = os.MkdirAll(filepath.Join(dir, filepath.Dir(f.path)), 0700)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
err := ioutil.WriteFile(filepath.Join(dir, f.path), []byte(f.value), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init the instance
|
||||
r := &RunFns{
|
||||
FunctionPaths: fnPaths,
|
||||
Functions: parsedFns,
|
||||
Path: d,
|
||||
NoFunctionsFromInput: tt.noFunctionsFromInput,
|
||||
}
|
||||
r.init()
|
||||
|
||||
// get the filters which would be run
|
||||
var results []string
|
||||
_, fltrs, _, err := r.getNodesAndFilters()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
for _, f := range fltrs {
|
||||
results = append(results, strings.TrimSpace(fmt.Sprintf("%v", f)))
|
||||
}
|
||||
|
||||
// compare the actual ordering to the expected ordering
|
||||
if !assert.Equal(t, tt.out, results) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_Execute(t *testing.T) {
|
||||
dir := setupTest(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// write a test filter to the directory of configuration
|
||||
if !assert.NoError(t, ioutil.WriteFile(
|
||||
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
|
||||
return
|
||||
}
|
||||
|
||||
instance := RunFns{
|
||||
Path: dir,
|
||||
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
|
||||
// parse the filter from the input
|
||||
filter := yaml.YFilter{}
|
||||
b := &bytes.Buffer{}
|
||||
e := yaml.NewEncoder(b)
|
||||
if !assert.NoError(t, e.Encode(node.YNode())) {
|
||||
t.FailNow()
|
||||
}
|
||||
e.Close()
|
||||
d := yaml.NewDecoder(b)
|
||||
if !assert.NoError(t, d.Decode(&filter)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return filters.Modifier{
|
||||
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
|
||||
}
|
||||
},
|
||||
}
|
||||
instance := RunFns{Path: dir, containerFilterProvider: getFilterProvider(t)}
|
||||
if !assert.NoError(t, instance.Execute()) {
|
||||
t.FailNow()
|
||||
}
|
||||
@@ -103,29 +517,12 @@ func TestCmd_Execute(t *testing.T) {
|
||||
assert.Contains(t, string(b), "kind: StatefulSet")
|
||||
}
|
||||
|
||||
func TestCmd_Execute_APIs(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
// TestCmd_Execute_setOutput tests the execution of a filter reading and writing to a dir
|
||||
func TestCmd_Execute_setFunctionPaths(t *testing.T) {
|
||||
dir := setupTest(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !assert.True(t, ok) {
|
||||
t.FailNow()
|
||||
}
|
||||
ds, err := filepath.Abs(filepath.Join(filepath.Dir(filename), "test", "testdata"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, copyutil.CopyDir(ds, dir)) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
|
||||
return
|
||||
}
|
||||
|
||||
// write a test filter
|
||||
// write a test filter to a separate directory
|
||||
tmpF, err := ioutil.TempFile("", "filter*.yaml")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
@@ -135,28 +532,15 @@ func TestCmd_Execute_APIs(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// run the functions, providing the path to the directory of filters
|
||||
instance := RunFns{
|
||||
FunctionPaths: []string{tmpF.Name()},
|
||||
Path: dir,
|
||||
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
|
||||
// parse the filter from the input
|
||||
filter := yaml.YFilter{}
|
||||
b := &bytes.Buffer{}
|
||||
e := yaml.NewEncoder(b)
|
||||
if !assert.NoError(t, e.Encode(node.YNode())) {
|
||||
t.FailNow()
|
||||
}
|
||||
e.Close()
|
||||
d := yaml.NewDecoder(b)
|
||||
if !assert.NoError(t, d.Decode(&filter)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return filters.Modifier{
|
||||
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
|
||||
}
|
||||
},
|
||||
FunctionPaths: []string{tmpF.Name()},
|
||||
Path: dir,
|
||||
containerFilterProvider: getFilterProvider(t),
|
||||
}
|
||||
// initialize the defaults
|
||||
instance.init()
|
||||
|
||||
err = instance.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
@@ -169,12 +553,91 @@ func TestCmd_Execute_APIs(t *testing.T) {
|
||||
assert.Contains(t, string(b), "kind: StatefulSet")
|
||||
}
|
||||
|
||||
func TestCmd_Execute_Stdout(t *testing.T) {
|
||||
// TestCmd_Execute_setOutput tests the execution of a filter using an io.Writer as output
|
||||
func TestCmd_Execute_setOutput(t *testing.T) {
|
||||
dir := setupTest(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// write a test filter
|
||||
if !assert.NoError(t, ioutil.WriteFile(
|
||||
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
|
||||
return
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
instance := RunFns{
|
||||
Output: out, // write to out
|
||||
Path: dir,
|
||||
containerFilterProvider: getFilterProvider(t),
|
||||
}
|
||||
// initialize the defaults
|
||||
instance.init()
|
||||
|
||||
if !assert.NoError(t, instance.Execute()) {
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadFile(
|
||||
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.NotContains(t, string(b), "kind: StatefulSet")
|
||||
assert.Contains(t, out.String(), "kind: StatefulSet")
|
||||
}
|
||||
|
||||
// TestCmd_Execute_setInput tests the execution of a filter using an io.Reader as input
|
||||
func TestCmd_Execute_setInput(t *testing.T) {
|
||||
dir := setupTest(t)
|
||||
defer os.RemoveAll(dir)
|
||||
if !assert.NoError(t, ioutil.WriteFile(
|
||||
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
|
||||
return
|
||||
}
|
||||
|
||||
read, err := kio.LocalPackageReader{PackagePath: dir}.Read()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
input := &bytes.Buffer{}
|
||||
if !assert.NoError(t, kio.ByteWriter{Writer: input}.Write(read)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
outDir, err := ioutil.TempDir("", "kustomize-test")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !assert.NoError(t, ioutil.WriteFile(
|
||||
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
|
||||
return
|
||||
}
|
||||
|
||||
instance := RunFns{
|
||||
Input: input, // read from input
|
||||
Path: outDir,
|
||||
containerFilterProvider: getFilterProvider(t),
|
||||
}
|
||||
// initialize the defaults
|
||||
instance.init()
|
||||
|
||||
if !assert.NoError(t, instance.Execute()) {
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadFile(
|
||||
filepath.Join(outDir, "java", "java-deployment.resource.yaml"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Contains(t, string(b), "kind: StatefulSet")
|
||||
}
|
||||
|
||||
// setupTest initializes a temp test directory containing test data
|
||||
func setupTest(t *testing.T) string {
|
||||
dir, err := ioutil.TempDir("", "kustomize-kyaml-test")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !assert.True(t, ok) {
|
||||
@@ -188,46 +651,31 @@ func TestCmd_Execute_Stdout(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, os.Chdir(filepath.Dir(dir))) {
|
||||
return
|
||||
t.FailNow()
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with
|
||||
// a filter to s/kind: Deployment/kind: StatefulSet/g.
|
||||
// this can be used to simulate running a filter.
|
||||
func getFilterProvider(t *testing.T) func(string, string, *yaml.RNode) kio.Filter {
|
||||
return func(s, _ string, node *yaml.RNode) kio.Filter {
|
||||
// parse the filter from the input
|
||||
filter := yaml.YFilter{}
|
||||
b := &bytes.Buffer{}
|
||||
e := yaml.NewEncoder(b)
|
||||
if !assert.NoError(t, e.Encode(node.YNode())) {
|
||||
t.FailNow()
|
||||
}
|
||||
e.Close()
|
||||
d := yaml.NewDecoder(b)
|
||||
if !assert.NoError(t, d.Decode(&filter)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return filters.Modifier{
|
||||
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
|
||||
}
|
||||
}
|
||||
|
||||
// write a test filter
|
||||
if !assert.NoError(t, ioutil.WriteFile(
|
||||
filepath.Join(dir, "filter.yaml"), []byte(ValueReplacerYAMLData), 0600)) {
|
||||
return
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
instance := RunFns{
|
||||
Output: out,
|
||||
Path: dir,
|
||||
containerFilterProvider: func(s, _ string, node *yaml.RNode) kio.Filter {
|
||||
// parse the filter from the input
|
||||
filter := yaml.YFilter{}
|
||||
b := &bytes.Buffer{}
|
||||
e := yaml.NewEncoder(b)
|
||||
if !assert.NoError(t, e.Encode(node.YNode())) {
|
||||
t.FailNow()
|
||||
}
|
||||
e.Close()
|
||||
d := yaml.NewDecoder(b)
|
||||
if !assert.NoError(t, d.Decode(&filter)) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return filters.Modifier{
|
||||
Filters: []yaml.YFilter{{Filter: yaml.Lookup("kind")}, filter},
|
||||
}
|
||||
},
|
||||
}
|
||||
if !assert.NoError(t, instance.Execute()) {
|
||||
return
|
||||
}
|
||||
b, err := ioutil.ReadFile(
|
||||
filepath.Join(dir, "java", "java-deployment.resource.yaml"))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.NotContains(t, string(b), "kind: StatefulSet")
|
||||
assert.Contains(t, out.String(), "kind: StatefulSet")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# kyaml version
|
||||
export kyaml_major=0
|
||||
export kyaml_minor=0
|
||||
export kyaml_patch=6
|
||||
export kyaml_patch=7
|
||||
|
||||
# kstatus version
|
||||
export kstatus_major=0
|
||||
@@ -21,7 +21,7 @@ export api_patch=2
|
||||
# cmd/config version
|
||||
export cmd_config_major=0
|
||||
export cmd_config_minor=0
|
||||
export cmd_config_patch=6
|
||||
export cmd_config_patch=8
|
||||
|
||||
# cmd/kubectl version
|
||||
export cmd_kubectl_major=0
|
||||
@@ -31,12 +31,12 @@ export cmd_kubectl_patch=3
|
||||
# cmd/resource version
|
||||
export cmd_resource_major=0
|
||||
export cmd_resource_minor=0
|
||||
export cmd_resource_patch=1
|
||||
export cmd_resource_patch=2
|
||||
|
||||
# kustomize version
|
||||
export kustomize_major=3
|
||||
export kustomize_minor=5
|
||||
export kustomize_patch=3
|
||||
export kustomize_patch=4
|
||||
|
||||
export pluginator_major=2
|
||||
export pluginator_minor=1
|
||||
|
||||
Reference in New Issue
Block a user