From 3893e128970473285a51df23515ead8299eb9034 Mon Sep 17 00:00:00 2001 From: Arthur Gustin Date: Tue, 14 Jan 2020 14:18:01 +0100 Subject: [PATCH 01/15] Add --namespace option to kustomize edit add secret command Fix https://github.com/kubernetes-sigs/kustomize/issues/1625 --- .../internal/commands/edit/add/flagsandargs.go | 2 ++ kustomize/internal/commands/edit/add/secret.go | 16 +++++++++++----- .../internal/commands/edit/add/secret_test.go | 11 ++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/kustomize/internal/commands/edit/add/flagsandargs.go b/kustomize/internal/commands/edit/add/flagsandargs.go index 659dd0039..d17088ab5 100644 --- a/kustomize/internal/commands/edit/add/flagsandargs.go +++ b/kustomize/internal/commands/edit/add/flagsandargs.go @@ -24,6 +24,8 @@ type flagsAndArgs struct { EnvFileSource string // Type of secret to create Type string + // Namespace of secret + Namespace string } // Validate validates required fields are set to support structured generation. diff --git a/kustomize/internal/commands/edit/add/secret.go b/kustomize/internal/commands/edit/add/secret.go index 48566dae9..ceb9650bf 100644 --- a/kustomize/internal/commands/edit/add/secret.go +++ b/kustomize/internal/commands/edit/add/secret.go @@ -86,6 +86,11 @@ func newCmdAddSecret( "type", "Opaque", "Specify the secret type this can be 'Opaque' (default), or 'kubernetes.io/tls'") + cmd.Flags().StringVar( + &flags.Namespace, + "namespace", + "", + "Specify the namespace of the secret") return cmd } @@ -97,7 +102,7 @@ func addSecret( ldr ifc.KvLoader, k *types.Kustomization, flags flagsAndArgs, kf ifc.KunstructuredFactory) error { - args := findOrMakeSecretArgs(k, flags.Name, flags.Type) + args := findOrMakeSecretArgs(k, flags.Name, flags.Namespace, flags.Type) mergeFlagsIntoGeneratorArgs(&args.GeneratorArgs, flags) // Validate by trying to create corev1.secret. _, err := kf.MakeSecret(ldr, k.GeneratorOptions, args) @@ -107,16 +112,17 @@ func addSecret( return nil } -func findOrMakeSecretArgs(m *types.Kustomization, name, secretType string) *types.SecretArgs { +func findOrMakeSecretArgs(m *types.Kustomization, name, namespace, secretType string) *types.SecretArgs { for i, v := range m.SecretGenerator { - if name == v.Name { + if name == v.Name && namespace == v.Namespace { return &m.SecretGenerator[i] } } // secret not found, create new one and add it to the kustomization file. secret := &types.SecretArgs{ - GeneratorArgs: types.GeneratorArgs{Name: name}, - Type: secretType} + GeneratorArgs: types.GeneratorArgs{Name: name, Namespace: namespace}, + Type: secretType, + } m.SecretGenerator = append(m.SecretGenerator, *secret) return &m.SecretGenerator[len(m.SecretGenerator)-1] } diff --git a/kustomize/internal/commands/edit/add/secret_test.go b/kustomize/internal/commands/edit/add/secret_test.go index 2007bfa12..30fc1dd27 100644 --- a/kustomize/internal/commands/edit/add/secret_test.go +++ b/kustomize/internal/commands/edit/add/secret_test.go @@ -28,6 +28,7 @@ func TestNewCmdAddSecretIsNotNil(t *testing.T) { func TestMakeSecretArgs(t *testing.T) { secretName := "test-secret-name" + namespace := "test-secret-namespace" kustomization := &types.Kustomization{ NamePrefix: "test-name-prefix", @@ -38,7 +39,7 @@ func TestMakeSecretArgs(t *testing.T) { if len(kustomization.SecretGenerator) != 0 { t.Fatal("Initial kustomization should not have any secrets") } - args := findOrMakeSecretArgs(kustomization, secretName, secretType) + args := findOrMakeSecretArgs(kustomization, secretName, namespace, secretType) if args == nil { t.Fatalf("args should always be non-nil") @@ -52,7 +53,7 @@ func TestMakeSecretArgs(t *testing.T) { t.Fatalf("Pointer address for newly inserted secret generator should be same") } - args2 := findOrMakeSecretArgs(kustomization, secretName, secretType) + args2 := findOrMakeSecretArgs(kustomization, secretName, namespace, secretType) if args2 != args { t.Fatalf("should have returned an existing args with name: %v", secretName) @@ -65,7 +66,7 @@ func TestMakeSecretArgs(t *testing.T) { func TestMergeFlagsIntoSecretArgs_LiteralSources(t *testing.T) { k := &types.Kustomization{} - args := findOrMakeSecretArgs(k, "foo", "forbidden") + args := findOrMakeSecretArgs(k, "foo", "bar", "forbidden") mergeFlagsIntoGeneratorArgs( &args.GeneratorArgs, flagsAndArgs{LiteralSources: []string{"k1=v1"}}) @@ -82,7 +83,7 @@ func TestMergeFlagsIntoSecretArgs_LiteralSources(t *testing.T) { func TestMergeFlagsIntoSecretArgs_FileSources(t *testing.T) { k := &types.Kustomization{} - args := findOrMakeSecretArgs(k, "foo", "forbidden") + args := findOrMakeSecretArgs(k, "foo", "bar", "forbidden") mergeFlagsIntoGeneratorArgs( &args.GeneratorArgs, flagsAndArgs{FileSources: []string{"file1"}}) @@ -99,7 +100,7 @@ func TestMergeFlagsIntoSecretArgs_FileSources(t *testing.T) { func TestMergeFlagsIntoSecretArgs_EnvSource(t *testing.T) { k := &types.Kustomization{} - args := findOrMakeSecretArgs(k, "foo", "forbidden") + args := findOrMakeSecretArgs(k, "foo", "bar", "forbidden") mergeFlagsIntoGeneratorArgs( &args.GeneratorArgs, flagsAndArgs{EnvFileSource: "env1"}) From d050276662deaf955a8a2d4e36f93ff6925ddb7e Mon Sep 17 00:00:00 2001 From: Ashley Nelson Date: Wed, 15 Jan 2020 11:59:39 -0600 Subject: [PATCH 02/15] Support third-party Helm repos in ChartInflator --- .../v1/chartinflator/ChartInflator | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugin/someteam.example.com/v1/chartinflator/ChartInflator b/plugin/someteam.example.com/v1/chartinflator/ChartInflator index 16d26b3cb..7bf69be3c 100755 --- a/plugin/someteam.example.com/v1/chartinflator/ChartInflator +++ b/plugin/someteam.example.com/v1/chartinflator/ChartInflator @@ -40,10 +40,11 @@ function parseYaml { local file=$1 while read -r line do - local k=${line%:*} + local k=${line%%:*} local v=${line#*:} [ "$k" == "chartName" ] && chartName=$v + [ "$k" == "chartRepo" ] && chartRepo=$v [ "$k" == "chartHome" ] && chartHome=$v [ "$k" == "chartRelease" ] && chartRelease=$v [ "$k" == "chartVersion" ] && chartVersion=$v @@ -56,6 +57,7 @@ function parseYaml { # Trim leading space chartName="${chartName#"${chartName%%[![:space:]]*}"}" + chartRepo="${chartRepo#"${chartRepo%%[![:space:]]*}"}" chartHome="${chartHome#"${chartHome%%[![:space:]]*}"}" chartRelease="${chartRelease#"${chartRelease%%[![:space:]]*}"}" chartVersion="${chartVersion#"${chartVersion%%[![:space:]]*}"}" @@ -84,6 +86,14 @@ if [ -z "$chartRelease" ]; then chartRelease="stable" fi +# The repo to pull the chart from +if [ -n "$chartRepo" ]; then + chartRepoArg="--repo=$chartRepo" + chartNameArg="$chartName" +else + chartNameArg="$chartRelease/$chartName" +fi + # Set version only if specified if [ ! -z "$chartVersion" ]; then chartVersionArg="--version=$chartVersion" @@ -114,9 +124,10 @@ doHelm init --client-only >& /dev/null if [ ! -d "$chartHome/$chartName" ]; then doHelm fetch $chartVersionArg \ + $chartRepoArg \ --untar \ --untardir $chartHome \ - ${chartRelease}/$chartName + $chartNameArg fi doHelm template \ From 3519cc56a1f2bca017804f2aef3559f4fc30df40 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Thu, 9 Jan 2020 13:45:39 -0800 Subject: [PATCH 03/15] Add support to get files referred in the generators and tranformers fields --- .../crawl/cmd/kustomize_stats/main.go | 19 +---- api/internal/crawl/crawler/crawler.go | 9 +- api/internal/crawl/doc/doc.go | 55 +++++++++--- api/internal/crawl/doc/doc_test.go | 85 +++++++++++++++++-- 4 files changed, 133 insertions(+), 35 deletions(-) diff --git a/api/internal/crawl/cmd/kustomize_stats/main.go b/api/internal/crawl/cmd/kustomize_stats/main.go index 403989d86..4f8fc195f 100644 --- a/api/internal/crawl/cmd/kustomize_stats/main.go +++ b/api/internal/crawl/cmd/kustomize_stats/main.go @@ -5,12 +5,12 @@ import ( "flag" "fmt" "log" - "path/filepath" "sort" "time" + "sigs.k8s.io/kustomize/api/internal/crawl/doc" + "sigs.k8s.io/kustomize/api/internal/crawl/index" - "sigs.k8s.io/kustomize/api/konfig" ) // iterateArr adds each item in arr into countMap. @@ -25,17 +25,6 @@ func iterateArr(arr []string, countMap map[string]int) { } -// isKustomizationFile determines whether a file path is a kustomization file -func isKustomizationFile(path string) bool { - basename := filepath.Base(path) - for _, name := range konfig.RecognizedKustomizationFileNames() { - if basename == name { - return true - } - } - return false -} - // SortMapKeyByValue takes a map as its input, sorts its keys according to their values // in the map, and outputs the sorted keys as a slice. func SortMapKeyByValue(m map[string]int) []string { @@ -44,7 +33,7 @@ func SortMapKeyByValue(m map[string]int) []string { keys = append(keys, key) } // sort keys according to their values in the map m - sort.Slice(keys, func(i, j int) bool {return m[keys[i]] > m[keys[j]]}) + sort.Slice(keys, func(i, j int) bool { return m[keys[i]] > m[keys[j]] }) return keys } @@ -101,7 +90,7 @@ If you only want to list the 10 most popular features, set the flag to 10.`) iterateArr(hit.Document.Kinds, kindsMap) iterateArr(hit.Document.Identifiers, identifiersMap) - if isKustomizationFile(hit.Document.FilePath) { + if doc.IsKustomizationFile(hit.Document.FilePath) { kustomizationFilecount++ iterateArr(hit.Document.Identifiers, kustomizeIdentifiersMap) } diff --git a/api/internal/crawl/crawler/crawler.go b/api/internal/crawl/crawler/crawler.go index b8f9d3874..b2f3ccd5f 100644 --- a/api/internal/crawl/crawler/crawler.go +++ b/api/internal/crawl/crawler/crawler.go @@ -43,7 +43,12 @@ type CrawledDocument interface { ID() string GetDocument() *doc.Document // Get all the Documents directly referred in a Document. - GetResources() ([]*doc.Document, error) + // For a Document representing a non-kustomization file, an empty slice will be returned. + // For a Document representing a kustomization file: + // the `includeResources` parameter determines whether the documents referred in the `resources` field are returned or not; + // the `includeTransformers` parameter determines whether the documents referred in the `transformers` field are returned or not; + // the `includeGenerators` parameter determines whether the documents referred in the `generators` field are returned or not. + GetResources(includeResources, includeTransformers, includeGenerators bool) ([]*doc.Document, error) WasCached() bool } @@ -95,7 +100,7 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc, return } - deps, err := cdoc.GetResources() + deps, err := cdoc.GetResources(true, false, false) if err != nil { logger.Println(err) return diff --git a/api/internal/crawl/doc/doc.go b/api/internal/crawl/doc/doc.go index 4a709f693..87cd5a3e2 100644 --- a/api/internal/crawl/doc/doc.go +++ b/api/internal/crawl/doc/doc.go @@ -3,6 +3,7 @@ package doc import ( "fmt" "log" + "path/filepath" "sort" "strings" @@ -51,15 +52,21 @@ func (doc *KustomizationDocument) String() string { doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values)) } -// Implements the CrawlerDocument interface. -func (doc *KustomizationDocument) GetResources() ([]*Document, error) { - isResource := true - for _, suffix := range konfig.RecognizedKustomizationFileNames() { - if strings.HasSuffix(doc.FilePath, "/"+suffix) { - isResource = false +// IsKustomizationFile determines whether a file path is a kustomization file +func IsKustomizationFile(path string) bool { + basename := filepath.Base(path) + for _, name := range konfig.RecognizedKustomizationFileNames() { + if basename == name { + return true } } - if isResource { + return false +} + +// Implements the CrawlerDocument interface. +func (doc *KustomizationDocument) GetResources( + includeResources, includeTransformers, includeGenerators bool) ([]*Document, error) { + if !IsKustomizationFile(doc.FilePath) { return []*Document{}, nil } @@ -77,20 +84,42 @@ func (doc *KustomizationDocument) GetResources() ([]*Document, error) { } k.FixKustomizationPostUnmarshalling() - res := make([]*Document, 0, len(k.Resources)) - for _, r := range k.Resources { + res := make([]*Document, 0) + + if includeResources { + resourceDocs := doc.CollectDocuments(k.Resources) + res = append(res, resourceDocs...) + } + + if includeGenerators { + generatorDocs := doc.CollectDocuments(k.Generators) + res = append(res, generatorDocs...) + } + + if includeTransformers { + transformerDocs := doc.CollectDocuments(k.Transformers) + res = append(res, transformerDocs...) + } + + return res, nil +} + +// CollectDocuments construct a Document for each path in paths, and return +// a slice of Document pointers. +func (doc *KustomizationDocument) CollectDocuments(paths []string) []*Document { + docs := make([]*Document, 0, len(paths)) + for _, r := range paths { if strings.TrimSpace(r) == "" { continue } next, err := doc.Document.FromRelativePath(r) if err != nil { - log.Printf("GetResources error: %v\n", err) + log.Printf("CollectDocuments error: %v\n", err) continue } - res = append(res, &next) + docs = append(docs, &next) } - - return res, nil + return docs } func (doc *KustomizationDocument) readBytes() ([]map[string]interface{}, error) { diff --git a/api/internal/crawl/doc/doc_test.go b/api/internal/crawl/doc/doc_test.go index 6fef92d97..c193809a6 100644 --- a/api/internal/crawl/doc/doc_test.go +++ b/api/internal/crawl/doc/doc_test.go @@ -189,11 +189,13 @@ metadata: } } +type TestStructForGetResources struct { + doc KustomizationDocument + resources []*Document +} + func TestGetResources(t *testing.T) { - tests := []struct { - doc KustomizationDocument - resources []*Document - }{ + tests := []TestStructForGetResources{ { doc: KustomizationDocument{ Document: Document{ @@ -248,9 +250,12 @@ resources: resources: []*Document{}, }, } + runTest(t, tests, true, false, false) +} +func runTest(t *testing.T, tests []TestStructForGetResources, includeResources, includeTransformers, includeGenerators bool) { for _, test := range tests { - res, err := test.doc.GetResources() + res, err := test.doc.GetResources(includeResources, includeTransformers, includeGenerators) if err != nil { t.Errorf("Unexpected error: %v\n", err) continue @@ -284,3 +289,73 @@ resources: } } } + +func TestGetResourcesAndGenerators(t *testing.T) { + tests := []TestStructForGetResources{ + { + doc: KustomizationDocument{ + Document: Document{ + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/kustomization.yaml", + DocumentData: ` +resources: +- file.yaml + +generators: +- gen.yaml + +transformers: +- tr.yaml +`}, + }, + resources: []*Document{ + { + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/gen.yaml", + }, + { + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/file.yaml", + }, + }, + }, + } + runTest(t, tests, true, false, true) +} + +func TestGetResourcesAndGeneratorsAndTransformers(t *testing.T) { + tests := []TestStructForGetResources{ + { + doc: KustomizationDocument{ + Document: Document{ + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/kustomization.yaml", + DocumentData: ` +resources: +- file.yaml + +generators: +- gen.yaml + +transformers: +- tr.yaml +`}, + }, + resources: []*Document{ + { + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/tr.yaml", + }, + { + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/gen.yaml", + }, + { + RepositoryURL: "sigs.k8s.io/kustomize", + FilePath: "some/path/to/kdir/file.yaml", + }, + }, + }, + } + runTest(t, tests, true, true, true) +} From 29e50ab476eb36da11755bde4bf7c02e5532ef9b Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Fri, 10 Jan 2020 10:23:20 -0800 Subject: [PATCH 04/15] Collect stats on generators and transformers --- .../crawl/cmd/kustomize_stats/main.go | 186 +++++++++++++++++- .../config/crawler/kustomize_stats/job.yaml | 5 + api/internal/crawl/doc/docname.go | 5 + 3 files changed, 193 insertions(+), 3 deletions(-) diff --git a/api/internal/crawl/cmd/kustomize_stats/main.go b/api/internal/crawl/cmd/kustomize_stats/main.go index 4f8fc195f..234d1327c 100644 --- a/api/internal/crawl/cmd/kustomize_stats/main.go +++ b/api/internal/crawl/cmd/kustomize_stats/main.go @@ -5,14 +5,24 @@ import ( "flag" "fmt" "log" + "net/http" + "os" + "sigs.k8s.io/kustomize/api/internal/crawl/crawler" "sort" "time" + "sigs.k8s.io/kustomize/api/internal/crawl/crawler/github" + "sigs.k8s.io/kustomize/api/internal/crawl/doc" "sigs.k8s.io/kustomize/api/internal/crawl/index" ) +const ( + githubAccessTokenVar = "GITHUB_ACCESS_TOKEN" + retryCount = 3 +) + // iterateArr adds each item in arr into countMap. func iterateArr(arr []string, countMap map[string]int) { for _, item := range arr { @@ -37,6 +47,159 @@ func SortMapKeyByValue(m map[string]int) []string { return keys } +func GeneratorAndTransformerStats(ctx context.Context, + generatorDocs []*doc.Document, transformerDocs []*doc.Document, + idx *index.KustomizeIndex) { + // allGenerators includes all the documents referred in the generators field + allGenerators := crawler.NewUniqueDocuments() + + // allTransformers includes all the documents referred in the transformers field + allTransformers := crawler.NewUniqueDocuments() + + // docUsingGeneratorCount counts the number of the kustomization files using generators + docUsingGeneratorCount := 0 + + // docUsingTransformerCount counts the number of the kustomization files using transformers + docUsingTransformerCount := 0 + + // collect all the documents referred in the generators and transformers fields + for _, d := range generatorDocs { + kdoc := doc.KustomizationDocument{ + Document: *d, + } + generators, err := kdoc.GetResources(false, false, true) + if err != nil { + log.Printf("failed to parse the generators field of the Document (%s): %v", + d.Path(), err) + } + if len(generators) > 0 { + docUsingGeneratorCount++ + allGenerators.AddDocuments(generators) + } + } + + for _, d := range transformerDocs { + kdoc := doc.KustomizationDocument{ + Document: *d, + } + transformers, err := kdoc.GetResources(false, true, false) + if err != nil { + log.Printf("failed to parse the transformers field of the Document (%s): %v", + d.Path(), err) + } + if len(transformers) > 0 { + docUsingTransformerCount++ + allTransformers.AddDocuments(transformers) + } + } + + // fileGeneratorCount counts file-type generators + // dirGeneratorCount counts dir-type generators + fileGeneratorCount, dirGeneratorCount, generatorFiles, generatorDirs := DocumentTypeSummary(ctx, allGenerators.Documents()) + + // fileTransformerCount counts file-type transformers + // dirTransformerCount counts dir-type transformers + fileTransformerCount, dirTransformerCount, transformerFiles, transformerDirs := DocumentTypeSummary(ctx, allTransformers.Documents()) + + // check whether any of the generator files are not in the index + nonExistGeneratorFileCount := ExistInIndex(idx, generatorFiles, "generator file ") + // check whether any of the generator dirs are not in the index + nonExistGeneratorDirCount := ExistInIndex(idx, generatorDirs, "generator dir ") + + // check whether any of the transformer files are not in the index + nonExistTransformerFileCount := ExistInIndex(idx, transformerFiles, "transformer file ") + // check whether any of the transformer dirs are not in the index + nonExistTransformerDirCount := ExistInIndex(idx, transformerDirs, "transformer dir ") + + GitRepositorySummary(generatorFiles, "generator files") + GitRepositorySummary(generatorDirs, "generator dirs") + GitRepositorySummary(transformerFiles, "transformer files") + GitRepositorySummary(transformerDirs, "transformer dirs") + + fmt.Printf(`%d kustomization files use generators: %d generators are files and %d generators are dirs. +%d kustomization files use tranformers: %d transformers are files and %d transformers are dirs.`, + docUsingGeneratorCount, fileGeneratorCount, dirGeneratorCount, + docUsingTransformerCount, fileTransformerCount, dirTransformerCount) + fmt.Printf("\n") + fmt.Printf("%d generator files do not exist in the index\n", nonExistGeneratorFileCount) + fmt.Printf("%d generator dirs do not exist in the index\n", nonExistGeneratorDirCount) + fmt.Printf("%d transformer files do not exist in the index\n", nonExistTransformerFileCount) + fmt.Printf("%d transformer dirs do not exist in the index\n", nonExistTransformerDirCount) +} + +// GitRepositorySummary counts the distribution of docs: +// 1) how many git repositories are these docs from? +// 2) how many docs are from each git repository? +func GitRepositorySummary(docs []*doc.Document, msgPrefix string) { + m := make(map[string]int) + for _, d := range docs { + if _, ok := m[d.RepositoryURL]; ok { + m[d.RepositoryURL]++ + } else { + m[d.RepositoryURL] = 1 + } + } + sortedKeys := SortMapKeyByValue(m) + for _, k := range sortedKeys { + fmt.Printf("%d %s are from %s\n", m[k], msgPrefix, k) + } +} + +// ExistInIndex goes through each Document in docs, and check whether it is in the index or not. +// It returns the number of documents which does not exist in the index. +func ExistInIndex(idx *index.KustomizeIndex, docs []*doc.Document, msgPrefix string) int { + nonExistCount := 0 + for _, d := range docs { + exists, err := idx.Exists(d.ID()) + if err != nil { + log.Println(err) + } + if !exists { + log.Printf("%s (%s) does not exist in the index", msgPrefix, d.Path()) + nonExistCount++ + } + } + return nonExistCount +} + +// DocumentTypeSummary goes through each doc in docs, and determines whether it is a file or dir. +func DocumentTypeSummary(ctx context.Context, docs []*doc.Document) ( + fileCount, dirCount int, files, dirs []*doc.Document) { + githubToken := os.Getenv(githubAccessTokenVar) + if githubToken == "" { + log.Fatalf("Must set the variable '%s' to make github requests.\n", + githubAccessTokenVar) + } + ghCrawler := github.NewCrawler(githubToken, retryCount, &http.Client{}, github.QueryWith()) + + for _, d := range docs { + oldFilePath := d.FilePath + if err := ghCrawler.FetchDocument(ctx, d); err != nil { + log.Printf("FetchDocument failed on %s: %v", d.Path(), err) + continue + } + + if d.FilePath == oldFilePath { + fileCount++ + files = append(files, d) + } else { + dirCount++ + dirs = append(dirs, d) + } + } + return fileCount, dirCount, files, dirs +} + +// ExistInSlice checks where target exits in items. +func ExistInSlice(items []string, target string) bool { + for _, item := range items { + if item == target { + return true + } + } + return false +} + func main() { topKindsPtr := flag.Int( "kinds", -1, @@ -53,10 +216,12 @@ If you only want to list the 10 most popular identifiers, set the flag to 10.`) `the number of kustomize features to be listed according to their popularities. By default, all the features will be listed. If you only want to list the 10 most popular features, set the flag to 10.`) + indexNamePtr := flag.String( + "index", "kustomize", "The name of the ElasticSearch index.") flag.Parse() ctx := context.Background() - idx, err := index.NewKustomizeIndex(ctx) + idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr) if err != nil { log.Fatalf("Could not create an index: %v\n", err) } @@ -74,6 +239,12 @@ If you only want to list the 10 most popular features, set the flag to 10.`) // ids tracks the unique IDs of the documents in the index ids := make(map[string]struct{}) + // generatorDocs includes all the docs using generators + generatorDocs := make([]*doc.Document, 0) + + // transformersDocs includes all the docs using transformers + transformersDocs := make([]*doc.Document, 0) + // get all the documents in the index query := []byte(`{ "query":{ "match_all":{} } }`) it := idx.IterateQuery(query, 10000, 60*time.Second) @@ -83,7 +254,7 @@ If you only want to list the 10 most popular features, set the flag to 10.`) if _, ok := ids[hit.ID]; !ok { ids[hit.ID] = struct{}{} } else { - fmt.Printf("Found duplicate ID (%s)\n", hit.ID) + log.Printf("Found duplicate ID (%s)\n", hit.ID) } count++ @@ -93,11 +264,18 @@ If you only want to list the 10 most popular features, set the flag to 10.`) if doc.IsKustomizationFile(hit.Document.FilePath) { kustomizationFilecount++ iterateArr(hit.Document.Identifiers, kustomizeIdentifiersMap) + if ExistInSlice(hit.Document.Identifiers, "generators") { + generatorDocs = append(generatorDocs, hit.Document.Copy()) + } + if ExistInSlice(hit.Document.Identifiers, "transformers") { + transformersDocs = append(transformersDocs, hit.Document.Copy()) + } } } } + if err := it.Err(); err != nil { - fmt.Printf("Error iterating: %v\n", err) + log.Fatalf("Error iterating: %v\n", err) } sortedKindsMapKeys := SortMapKeyByValue(kindsMap) @@ -136,4 +314,6 @@ There are %d documents in the kustomize index. kustomizeFeatureCount++ } } + + GeneratorAndTransformerStats(ctx, generatorDocs, transformersDocs, idx) } diff --git a/api/internal/crawl/config/crawler/kustomize_stats/job.yaml b/api/internal/crawl/config/crawler/kustomize_stats/job.yaml index e7d22cd51..15ec4c4fd 100644 --- a/api/internal/crawl/config/crawler/kustomize_stats/job.yaml +++ b/api/internal/crawl/config/crawler/kustomize_stats/job.yaml @@ -13,6 +13,11 @@ spec: command: ["/kustomize_stats"] args: ["--kinds=50", "--identifiers=50", "--kustomize-features=50"] env: + - name: GITHUB_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: github-access-token + key: token - name: ELASTICSEARCH_URL valueFrom: configMapKeyRef: diff --git a/api/internal/crawl/doc/docname.go b/api/internal/crawl/doc/docname.go index 5afca1290..e295e4620 100644 --- a/api/internal/crawl/doc/docname.go +++ b/api/internal/crawl/doc/docname.go @@ -35,6 +35,11 @@ func (doc *Document) Copy() *Document { } } +func (doc *Document) Path() string { + return fmt.Sprintf("repoURL: %s filePath: %s branch: %s", + doc.RepositoryURL, doc.FilePath, doc.DefaultBranch) +} + // Implements the CrawlerDocument interface. func (doc *Document) WasCached() bool { return doc.IsSame From aaaba993892fb06d2b9c4b1353e7454ea5cb7bd7 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Wed, 15 Jan 2020 11:50:51 -0800 Subject: [PATCH 05/15] Use Document.Path instead of its fields --- api/internal/crawl/crawler/crawler.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/api/internal/crawl/crawler/crawler.go b/api/internal/crawl/crawler/crawler.go index b2f3ccd5f..2f7026e7a 100644 --- a/api/internal/crawl/crawler/crawler.go +++ b/api/internal/crawl/crawler/crawler.go @@ -95,8 +95,8 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc, // Insert into index if err := indx(cdoc, index.InsertOrUpdate); err != nil { - logger.Printf("Failed to insert or update %s %s: %v", - cdoc.GetDocument().RepositoryURL, cdoc.GetDocument().FilePath, err) + logger.Printf("Failed to insert or update doc(%s): %v", + cdoc.GetDocument().Path(), err) return } @@ -136,7 +136,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C *docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)] crawledDocCount++ - logger.Printf("Crawling doc %d: %s %s", crawledDocCount, tail.RepositoryURL, tail.FilePath) + logger.Printf("Crawling doc %d: %s", crawledDocCount, tail.Path()) if seen.Seen(tail.ID()) { logger.Printf("this doc has been seen before") @@ -145,7 +145,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C } if tail.WasCached() { - logger.Printf("%s %s is cached already", tail.RepositoryURL, tail.FilePath) + logger.Printf("doc(%s) is cached already", tail.Path()) cachedDocCount++ continue } @@ -168,8 +168,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C if err := match.FetchDocument(ctx, tail); err != nil { - logger.Printf("FetchDocument failed on %s %s: %v", - tail.RepositoryURL, tail.FilePath, err) + logger.Printf("FetchDocument failed on doc(%s): %v", tail.Path(), err) FetchDocumentErrCount++ // delete the document from the index cdoc := &doc.KustomizationDocument{ @@ -177,16 +176,14 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C } 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) + logger.Printf("Failed to delete doc(%s): %v", cdoc.Path(), err) } deleteDocCount++ continue } if err := match.SetCreated(ctx, tail); err != nil { - logger.Printf("SetCreated failed on %s %s: %v", - tail.RepositoryURL, tail.FilePath, err) + logger.Printf("SetCreated failed on doc(%s): %v", tail.Path(), err) SetCreatedErrCount++ } @@ -194,8 +191,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C // If conv returns an error, cdoc can still be added into the index so that // cdoc.Document can be searched. if err != nil { - logger.Printf("conv failed on %s %s: %v", - tail.RepositoryURL, tail.FilePath, err) + logger.Printf("conv failed on doc(%s): %v", tail.Path(), err) convErrCount++ } From c340c30f25db90011c03ee3fec57078b85a6b8b6 Mon Sep 17 00:00:00 2001 From: Phani Teja Marupaka Date: Wed, 15 Jan 2020 14:09:40 -0800 Subject: [PATCH 06/15] Alpha commands e2e tests --- Makefile | 14 ++- cmd/kubectl/go.mod | 4 +- .../alphaTestExamples/helloworld/README.md | 113 ++++++++++++++++++ .../helloworld/configMap.yaml | 7 ++ .../helloworld/deployment.yaml | 30 +++++ .../helloworld/grouping.yaml | 6 + .../helloworld/kustomization.yaml | 10 ++ .../alphaTestExamples/helloworld/service.yaml | 12 ++ examples/helloWorld/README.md | 32 +---- hack/testExamplesE2EAgainstKustomize.sh | 4 +- kustomize/go.mod | 5 + 11 files changed, 200 insertions(+), 37 deletions(-) create mode 100644 examples/alphaTestExamples/helloworld/README.md create mode 100644 examples/alphaTestExamples/helloworld/configMap.yaml create mode 100644 examples/alphaTestExamples/helloworld/deployment.yaml create mode 100644 examples/alphaTestExamples/helloworld/grouping.yaml create mode 100644 examples/alphaTestExamples/helloworld/kustomization.yaml create mode 100644 examples/alphaTestExamples/helloworld/service.yaml diff --git a/Makefile b/Makefile index 77b8fe954..04530a8c3 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ verify-kustomize: \ test-examples-kustomize-against-latest .PHONY: verify-kustomize-e2e -verify-kustomize-e2e: test-examples-e2e-kustomize-against-HEAD +verify-kustomize-e2e: test-examples-e2e-kustomize # Other builds in this repo might want a different linter version. # Without one Makefile to rule them all, the different makes @@ -202,8 +202,16 @@ 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 +test-examples-e2e-kustomize: $(MYGOBIN)/mdrip + ( \ + set -e; \ + /bin/rm -f $(MYGOBIN)/kustomize; \ + echo "Installing kustomize from ."; \ + cd kustomize; go install .; cd ..; \ + echo "Installing resource from ."; \ + cd cmd/resource; go install .; cd ../..; \ + ./hack/testExamplesE2EAgainstKustomize.sh .; \ + ) .PHONY: test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip diff --git a/cmd/kubectl/go.mod b/cmd/kubectl/go.mod index 5213335c1..3a42432e6 100644 --- a/cmd/kubectl/go.mod +++ b/cmd/kubectl/go.mod @@ -11,11 +11,11 @@ require ( k8s.io/component-base v0.17.0 // indirect k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd sigs.k8s.io/controller-runtime v0.4.0 - sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939 + sigs.k8s.io/kustomize/kstatus v0.0.1 sigs.k8s.io/kustomize/kyaml v0.0.2 ) replace ( - sigs.k8s.io/kustomize/kstatus v0.0.0-20200109211150-9555095de939 => ../../kstatus + sigs.k8s.io/kustomize/kstatus v0.0.1 => ../../kstatus sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml ) diff --git a/examples/alphaTestExamples/helloworld/README.md b/examples/alphaTestExamples/helloworld/README.md new file mode 100644 index 000000000..96817fa67 --- /dev/null +++ b/examples/alphaTestExamples/helloworld/README.md @@ -0,0 +1,113 @@ +[base]: ../../docs/glossary.md#base +[config]: https://github.com/kinflate/example-hello +[gitops]: ../../docs/glossary.md#gitops +[hello]: https://github.com/monopole/hello +[kustomization]: ../../docs/glossary.md#kustomization +[original]: https://github.com/kinflate/example-hello +[overlay]: ../../docs/glossary.md#overlay +[overlays]: ../../docs/glossary.md#overlay +[patch]: ../../docs/glossary.md#patch +[variant]: ../../docs/glossary.md#variant +[variants]: ../../docs/glossary.md#variant + +# Demo: hello world + +Steps: + + 1. Clone an existing configuration as a [base]. + 1. Customize it. + +First define a place to work: + + +``` +DEMO_HOME=$(mktemp -d) +``` + +Alternatively, use + +> ``` +> DEMO_HOME=~/hello +> ``` + +## Establish the base + +Let's run the [hello] service. + +To keep this document shorter, the base resources are +off in a supplemental data directory rather than +declared here as HERE documents. Download them: + + +``` +BASE=$DEMO_HOME/base +mkdir -p $BASE + +curl -s -o "$BASE/#1.yaml" "https://raw.githubusercontent.com\ +/kubernetes-sigs/kustomize\ +/master/examples/alphaTestExamples/helloWorld\ +/{configMap,deployment,grouping,kustomization,service}.yaml" +``` + +### The Base Kustomization + +The `base` directory has a [kustomization] file: + + +``` +more $BASE/kustomization.yaml +``` + +### Customize the base + +A first customization step could be to change the _app +label_ applied to all resources: + + +``` +sed -i.bak 's/app: hello/app: my-hello/' \ + $BASE/kustomization.yaml +``` + +To do end to end tests using kustomize, go through the following section. You should have GOPATH set up and "kind" installed(https://github.com/kubernetes-sigs/kind). + + +``` +MYGOBIN=$GOPATH/bin +``` + +Delete any existing kind cluster and create a new one. By default the name of the cluster is "kind" + +``` +kind delete cluster; +kind create cluster; +``` + +Use the kustomize binary in MYGOBIN to apply a deployment, fetch the status and verify the status. + +``` +export KUSTOMIZE_ENABLE_ALPHA_COMMANDS=true + +$MYGOBIN/kustomize resources apply $BASE --status; + +status=$(mktemp); +$MYGOBIN/resource status fetch $BASE > $status + +test 1 == \ + $(grep "the-deployment" $status | grep "Deployment is available. Replicas: 3" | wc -l); \ + echo $? + +test 1 == \ + $(grep "the-map" $status | grep "Resource is always ready" | wc -l); \ + echo $? + +test 1 == \ + $(grep "the-service" $status | grep "Service is ready" | wc -l); \ + echo $? +``` + +Clean-up the cluster + +``` +kind delete cluster; +``` \ No newline at end of file diff --git a/examples/alphaTestExamples/helloworld/configMap.yaml b/examples/alphaTestExamples/helloworld/configMap.yaml new file mode 100644 index 000000000..e335ab8cc --- /dev/null +++ b/examples/alphaTestExamples/helloworld/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/examples/alphaTestExamples/helloworld/deployment.yaml b/examples/alphaTestExamples/helloworld/deployment.yaml new file mode 100644 index 000000000..6e7940908 --- /dev/null +++ b/examples/alphaTestExamples/helloworld/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/examples/alphaTestExamples/helloworld/grouping.yaml b/examples/alphaTestExamples/helloworld/grouping.yaml new file mode 100644 index 000000000..ea723dda6 --- /dev/null +++ b/examples/alphaTestExamples/helloworld/grouping.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: inventory-map + labels: + "kustomize.config.k8s.io/inventory-id": "hello-world-app" \ No newline at end of file diff --git a/examples/alphaTestExamples/helloworld/kustomization.yaml b/examples/alphaTestExamples/helloworld/kustomization.yaml new file mode 100644 index 000000000..73e197c8b --- /dev/null +++ b/examples/alphaTestExamples/helloworld/kustomization.yaml @@ -0,0 +1,10 @@ +# Example configuration for the webserver +# at https://github.com/monopole/hello +commonLabels: + app: hello + +resources: +- deployment.yaml +- service.yaml +- configMap.yaml +- grouping.yaml diff --git a/examples/alphaTestExamples/helloworld/service.yaml b/examples/alphaTestExamples/helloworld/service.yaml new file mode 100644 index 000000000..e238f7002 --- /dev/null +++ b/examples/alphaTestExamples/helloworld/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 diff --git a/examples/helloWorld/README.md b/examples/helloWorld/README.md index 0ec226d5b..fcd36bb4c 100644 --- a/examples/helloWorld/README.md +++ b/examples/helloWorld/README.md @@ -22,7 +22,7 @@ Steps: First define a place to work: - + ``` 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: - + ``` BASE=$DEMO_HOME/base mkdir -p $BASE @@ -309,31 +309,3 @@ 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). - - -``` -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; -``` \ No newline at end of file diff --git a/hack/testExamplesE2EAgainstKustomize.sh b/hack/testExamplesE2EAgainstKustomize.sh index a6c44195b..4a0941d08 100755 --- a/hack/testExamplesE2EAgainstKustomize.sh +++ b/hack/testExamplesE2EAgainstKustomize.sh @@ -8,6 +8,6 @@ set -o errexit set -o pipefail mdrip --blockTimeOut 60m0s --mode test \ - --label testE2EAgainstLatestRelease examples + --label testE2EAgainstLatestRelease examples/alphaTestExamples -echo "Example e2e tests passed against ${version}." +echo "Example e2e tests passed against ." diff --git a/kustomize/go.mod b/kustomize/go.mod index 50467251a..d8abd9115 100644 --- a/kustomize/go.mod +++ b/kustomize/go.mod @@ -18,3 +18,8 @@ exclude ( github.com/russross/blackfriday v2.0.0+incompatible sigs.k8s.io/kustomize/api v0.2.0 ) + +replace ( + sigs.k8s.io/kustomize/cmd/kubectl v0.0.3 => ../cmd/kubectl + sigs.k8s.io/kustomize/kstatus v0.0.1 => ../kstatus +) From cf8d53a1958a31ac7b31ff1104a4a15276836c94 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Wed, 15 Jan 2020 12:04:22 -0800 Subject: [PATCH 07/15] Move SeenMap to the utils dir --- api/internal/crawl/cmd/crawler/crawler.go | 7 ++-- .../crawl/cmd/kustomize_stats/main.go | 5 ++- api/internal/crawl/crawler/crawler.go | 30 +++++----------- api/internal/crawl/crawler/crawler_test.go | 8 +++-- api/internal/crawl/crawler/github/crawler.go | 16 +++++---- api/internal/crawl/crawler/github/queries.go | 2 +- api/internal/crawl/doc/docname_test.go | 4 +-- api/internal/crawl/doc/unique_doc.go | 36 +++++++++++++++++++ api/internal/crawl/index/kustomize.go | 1 + api/internal/crawl/utils/utils.go | 16 +++++++++ 10 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 api/internal/crawl/doc/unique_doc.go create mode 100644 api/internal/crawl/utils/utils.go diff --git a/api/internal/crawl/cmd/crawler/crawler.go b/api/internal/crawl/cmd/crawler/crawler.go index 4a7883e54..2d301efe1 100644 --- a/api/internal/crawl/cmd/crawler/crawler.go +++ b/api/internal/crawl/cmd/crawler/crawler.go @@ -9,6 +9,8 @@ import ( "os" "time" + "sigs.k8s.io/kustomize/api/internal/crawl/utils" + "sigs.k8s.io/kustomize/api/internal/crawl/crawler" "sigs.k8s.io/kustomize/api/internal/crawl/crawler/github" "sigs.k8s.io/kustomize/api/internal/crawl/doc" @@ -26,6 +28,7 @@ const ( ) type CrawlMode int + const ( CrawlUnknown CrawlMode = iota // Crawl all the kustomization files in all the repositories of a Github user @@ -125,13 +128,13 @@ func main() { // seen tracks the IDs of all the documents in the index. // This helps avoid indexing a given document multiple times. - seen := crawler.NewSeenMap() + seen := utils.NewSeenMap() mode := NewCrawlMode(*modePtr) ghCrawlerConstructor := func(user, repo string) crawler.Crawler { if user != "" { - return github.NewCrawler(githubToken, retryCount, clientCache, + return github.NewCrawler(githubToken, retryCount, clientCache, github.QueryWith( github.Filename("kustomization.yaml"), github.Filename("kustomization.yml"), diff --git a/api/internal/crawl/cmd/kustomize_stats/main.go b/api/internal/crawl/cmd/kustomize_stats/main.go index 234d1327c..d3a436ebe 100644 --- a/api/internal/crawl/cmd/kustomize_stats/main.go +++ b/api/internal/crawl/cmd/kustomize_stats/main.go @@ -7,7 +7,6 @@ import ( "log" "net/http" "os" - "sigs.k8s.io/kustomize/api/internal/crawl/crawler" "sort" "time" @@ -51,10 +50,10 @@ func GeneratorAndTransformerStats(ctx context.Context, generatorDocs []*doc.Document, transformerDocs []*doc.Document, idx *index.KustomizeIndex) { // allGenerators includes all the documents referred in the generators field - allGenerators := crawler.NewUniqueDocuments() + allGenerators := doc.NewUniqueDocuments() // allTransformers includes all the documents referred in the transformers field - allTransformers := crawler.NewUniqueDocuments() + allTransformers := doc.NewUniqueDocuments() // docUsingGeneratorCount counts the number of the kustomization files using generators docUsingGeneratorCount := 0 diff --git a/api/internal/crawl/crawler/crawler.go b/api/internal/crawl/crawler/crawler.go index 2f7026e7a..11715616d 100644 --- a/api/internal/crawl/crawler/crawler.go +++ b/api/internal/crawl/crawler/crawler.go @@ -10,6 +10,8 @@ import ( "os" "sync" + "sigs.k8s.io/kustomize/api/internal/crawl/utils" + "sigs.k8s.io/kustomize/api/internal/crawl/index" _ "github.com/gomodule/redigo/redis" @@ -29,7 +31,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, seen SeenMap) error + Crawl(ctx context.Context, output chan<- CrawledDocument, seen utils.SeenMap) error // Get the document data given the FilePath, Repo, and Ref/Tag/Branch. FetchDocument(context.Context, *doc.Document) error @@ -52,21 +54,6 @@ 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 @@ -89,7 +76,7 @@ func findMatch(d *doc.Document, crawlers []Crawler) Crawler { } func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc, - seen SeenMap, stack *CrawlSeed) { + seen utils.SeenMap, stack *CrawlSeed) { seen.Add(cdoc.ID()) @@ -115,7 +102,7 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc, } func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc, - seen SeenMap, stack *CrawlSeed) { + seen utils.SeenMap, stack *CrawlSeed) { UpdatedDocCount := 0 seenDocCount := 0 @@ -166,7 +153,6 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C // `bases` field. seen.Add(tail.ID()) - if err := match.FetchDocument(ctx, tail); err != nil { logger.Printf("FetchDocument failed on doc(%s): %v", tail.Path(), err) FetchDocumentErrCount++ @@ -212,7 +198,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 SeenMap) { + conv Converter, indx IndexFunc, seen utils.SeenMap) { // stack tracks the documents directly referred in other documents. stack := make(CrawlSeed, 0) @@ -248,7 +234,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, seen SeenMap) []error { + crawlers []Crawler, seen utils.SeenMap) []error { errs := make([]error, len(crawlers)) wg := sync.WaitGroup{} @@ -292,7 +278,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 SeenMap) { + indx IndexFunc, seen utils.SeenMap) { // stack tracks the documents directly referred in other documents. stack := make(CrawlSeed, 0) diff --git a/api/internal/crawl/crawler/crawler_test.go b/api/internal/crawl/crawler/crawler_test.go index 7dace4da6..d18a4afce 100644 --- a/api/internal/crawl/crawler/crawler_test.go +++ b/api/internal/crawl/crawler/crawler_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + "sigs.k8s.io/kustomize/api/internal/crawl/utils" + "sigs.k8s.io/kustomize/api/internal/crawl/index" "sigs.k8s.io/kustomize/api/internal/crawl/doc" @@ -76,7 +78,7 @@ func newCrawler(matchPrefix string, err error, // Crawl implements the Crawler interface for testing. func (c testCrawler) Crawl(_ context.Context, - output chan<- CrawledDocument, _ SeenMap) error { + output chan<- CrawledDocument, _ utils.SeenMap) error { for i, d := range c.docs { isResource := true @@ -182,7 +184,7 @@ func TestCrawlGithubRunner(t *testing.T) { defer close(output) defer wg.Done() - seen := NewSeenMap() + seen := utils.NewSeenMap() errs := CrawlGithubRunner(context.Background(), output, test.tc, seen) @@ -324,7 +326,7 @@ resources: visited[d.ID()]++ return nil }, - NewSeenMap(), + utils.NewSeenMap(), ) if lv, lc := len(visited), len(tc.corpus); lv != lc { t.Errorf("error: %d of %d documents visited.", lv, lc) diff --git a/api/internal/crawl/crawler/github/crawler.go b/api/internal/crawl/crawler/github/crawler.go index 046ba4af0..e1aeff401 100644 --- a/api/internal/crawl/crawler/github/crawler.go +++ b/api/internal/crawl/crawler/github/crawler.go @@ -16,6 +16,8 @@ import ( "strings" "time" + "sigs.k8s.io/kustomize/api/internal/crawl/utils" + "sigs.k8s.io/kustomize/api/internal/crawl/crawler" "sigs.k8s.io/kustomize/api/internal/crawl/doc" "sigs.k8s.io/kustomize/api/internal/crawl/httpclient" @@ -68,7 +70,7 @@ func (gc githubCrawler) DefaultBranch(repo string) string { // Implements crawler.Crawler. func (gc githubCrawler) Crawl(ctx context.Context, - output chan<- crawler.CrawledDocument, seen crawler.SeenMap) error { + output chan<- crawler.CrawledDocument, seen utils.SeenMap) error { noETagClient := GhClient{ RequestConfig: gc.client.RequestConfig, @@ -195,9 +197,9 @@ func (gc githubCrawler) Match(d *doc.Document) bool { type RangeQueryResult struct { totalDocCnt uint64 - seenDocCnt uint64 - newDocCnt uint64 - errorCnt uint64 + seenDocCnt uint64 + newDocCnt uint64 + errorCnt uint64 } func (r *RangeQueryResult) Add(other RangeQueryResult) { @@ -209,7 +211,7 @@ func (r *RangeQueryResult) Add(other RangeQueryResult) { 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 been seen before. %d are new and sent to the output channel."+ " %d have kustomizationResultAdapter errors.", r.totalDocCnt, r.seenDocCnt, r.newDocCnt, r.errorCnt) } @@ -217,7 +219,7 @@ func (r *RangeQueryResult) String() string { // 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, seen crawler.SeenMap, + output chan<- crawler.CrawledDocument, seen utils.SeenMap, branchMap map[string]string) (RangeQueryResult, error) { queryPages := make(chan GhResponseInfo) @@ -271,7 +273,7 @@ func processQuery(ctx context.Context, gcl GhClient, query string, return result, errs } -func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen crawler.SeenMap, +func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen utils.SeenMap, branchMap map[string]string) (crawler.CrawledDocument, error) { url := gcl.ReposRequest(k.Repository.FullName) defaultBranch, err := gcl.GetDefaultBranch(url, k.Repository.URL, branchMap) diff --git a/api/internal/crawl/crawler/github/queries.go b/api/internal/crawl/crawler/github/queries.go index 557e0f371..ba05af38f 100644 --- a/api/internal/crawl/crawler/github/queries.go +++ b/api/internal/crawl/crawler/github/queries.go @@ -117,7 +117,7 @@ type RequestConfig struct { // understand why the request object is useful. func (rc RequestConfig) CodeSearchRequestWith(query Query) request { vals := url.Values{ - "sort": []string{"indexed"}, + "sort": []string{"indexed"}, "order": []string{"desc"}, } req := rc.makeRequest("search/code", query, vals) diff --git a/api/internal/crawl/doc/docname_test.go b/api/internal/crawl/doc/docname_test.go index f1b65dc8f..a03beaf06 100644 --- a/api/internal/crawl/doc/docname_test.go +++ b/api/internal/crawl/doc/docname_test.go @@ -65,7 +65,7 @@ func TestFromRelativePath(t *testing.T) { func TestDocument_RepositoryFullName(t *testing.T) { testCases := []struct { - doc Document + doc Document expectedRepositoryFullName string }{ { @@ -108,4 +108,4 @@ func TestDocument_RepositoryFullName(t *testing.T) { returnedRepositoryFullName) } } -} \ No newline at end of file +} diff --git a/api/internal/crawl/doc/unique_doc.go b/api/internal/crawl/doc/unique_doc.go new file mode 100644 index 000000000..026b345a5 --- /dev/null +++ b/api/internal/crawl/doc/unique_doc.go @@ -0,0 +1,36 @@ +package doc + +import ( + "sigs.k8s.io/kustomize/api/internal/crawl/utils" +) + +// UniqueDocuments make sure a Document with a given ID appears only once +type UniqueDocuments struct { + docs []*Document + docIDs utils.SeenMap +} + +func NewUniqueDocuments() UniqueDocuments { + return UniqueDocuments{ + docs: []*Document{}, + docIDs: utils.NewSeenMap(), + } +} + +func (uds *UniqueDocuments) Add(d *Document) { + if uds.docIDs.Seen(d.ID()) { + return + } + uds.docs = append(uds.docs, d) + uds.docIDs.Add(d.ID()) +} + +func (uds *UniqueDocuments) AddDocuments(docs []*Document) { + for _, d := range docs { + uds.Add(d) + } +} + +func (uds *UniqueDocuments) Documents() []*Document { + return uds.docs +} diff --git a/api/internal/crawl/index/kustomize.go b/api/internal/crawl/index/kustomize.go index e55c5547e..7e2e7b104 100644 --- a/api/internal/crawl/index/kustomize.go +++ b/api/internal/crawl/index/kustomize.go @@ -18,6 +18,7 @@ const ( ) type Mode int + const ( InsertOrUpdate = iota Delete diff --git a/api/internal/crawl/utils/utils.go b/api/internal/crawl/utils/utils.go new file mode 100644 index 000000000..a397b2d52 --- /dev/null +++ b/api/internal/crawl/utils/utils.go @@ -0,0 +1,16 @@ +package utils + +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{}) +} From 3ead42fe27b75e3415f256e899830a4956aacff9 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Wed, 15 Jan 2020 12:05:27 -0800 Subject: [PATCH 08/15] Add --index flag to kustomize_stats config file --- api/internal/crawl/config/crawler/kustomize_stats/job.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/internal/crawl/config/crawler/kustomize_stats/job.yaml b/api/internal/crawl/config/crawler/kustomize_stats/job.yaml index 15ec4c4fd..9e2457983 100644 --- a/api/internal/crawl/config/crawler/kustomize_stats/job.yaml +++ b/api/internal/crawl/config/crawler/kustomize_stats/job.yaml @@ -11,7 +11,7 @@ spec: image: gcr.io/haiyanmeng-gke-dev/kustomize_stats:v1 imagePullPolicy: Always command: ["/kustomize_stats"] - args: ["--kinds=50", "--identifiers=50", "--kustomize-features=50"] + args: ["--index=kustomize", "--kinds=50", "--identifiers=50", "--kustomize-features=50"] env: - name: GITHUB_ACCESS_TOKEN valueFrom: From 5477bde7e5a0a89d05c392a75d8cb47161bf9eb2 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Wed, 15 Jan 2020 14:55:37 -0800 Subject: [PATCH 09/15] Use an env variable for index name and fix the call to NewKustomizeIndex in backend --- api/internal/crawl/backend/search_backend.go | 2 +- .../crawl/search_cmds/creationTime.md | 20 +++++++++---------- .../crawl/search_cmds/defaultBranch.md | 4 ++-- .../crawl/search_cmds/fieldExistence.md | 6 +++--- .../crawl/search_cmds/keyword_search.md | 10 +++++----- api/internal/crawl/search_cmds/misc.md | 4 ++-- .../crawl/search_cmds/repositoryUrl.md | 12 +++++------ api/internal/crawl/search_cmds/text_search.md | 18 ++++++++--------- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/api/internal/crawl/backend/search_backend.go b/api/internal/crawl/backend/search_backend.go index e6062fbf6..cdc76e6ef 100644 --- a/api/internal/crawl/backend/search_backend.go +++ b/api/internal/crawl/backend/search_backend.go @@ -44,7 +44,7 @@ type kustomizeSearch struct { // /register: not implemented, but meant as an endpoint for adding new // kustomization files to the corpus. func NewKustomizeSearch(ctx context.Context) (*kustomizeSearch, error) { - idx, err := index.NewKustomizeIndex(ctx) + idx, err := index.NewKustomizeIndex(ctx, "kustomize") if err != nil { return nil, err } diff --git a/api/internal/crawl/search_cmds/creationTime.md b/api/internal/crawl/search_cmds/creationTime.md index 4029e4b0b..3ebfaf157 100644 --- a/api/internal/crawl/search_cmds/creationTime.md +++ b/api/internal/crawl/search_cmds/creationTime.md @@ -1,6 +1,6 @@ Find out the largest value of the `creationTime` field: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "max_creationTime" : { "max" : { "field" : "creationTime" } } @@ -11,7 +11,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Find out the smallest value of the `creationTime` field: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "min_creationTime" : { "min" : { "field" : "creationTime" } } @@ -22,7 +22,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Find out the smallest value of the `creationTime` field of all the kustomization files: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -40,7 +40,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Find out the smallest value of the `creationTime` field of all kustomize resource files: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -58,7 +58,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Query all the documents whose `creationTime` <= `2016-07-29T17:38:26.000Z`: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "range": { @@ -73,7 +73,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Query all the documents whose `creationTime` falls within the specific range: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "range": { @@ -89,7 +89,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Aggregate how many new kustomization files were added into Github each month: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -112,7 +112,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Conte Aggregate how many new kustomize resource files were added into Github each month: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -135,7 +135,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Conte Aggregate how many new kustomization files were added into Github each year: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -158,7 +158,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Conte Aggregate how many new kustomize resource files were added into Github each year: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { diff --git a/api/internal/crawl/search_cmds/defaultBranch.md b/api/internal/crawl/search_cmds/defaultBranch.md index 89822d4c6..d4cab8e6b 100644 --- a/api/internal/crawl/search_cmds/defaultBranch.md +++ b/api/internal/crawl/search_cmds/defaultBranch.md @@ -1,6 +1,6 @@ Count distinct values of the `defaultBranch` field: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "defaultBranch_count" : { @@ -17,7 +17,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont List all the github branches where kustomization files and kustomize resource files live, and how many kustomization files and kustomize resource files live in each branch: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "defaultBranch" : { diff --git a/api/internal/crawl/search_cmds/fieldExistence.md b/api/internal/crawl/search_cmds/fieldExistence.md index 591abdbff..f804c784a 100644 --- a/api/internal/crawl/search_cmds/fieldExistence.md +++ b/api/internal/crawl/search_cmds/fieldExistence.md @@ -1,7 +1,7 @@ Count the documents whose `document` field is empty (The reason why the `document` field of a document is empty is because of empty documents): ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 10000, "query": { @@ -19,7 +19,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Find all the documents having the `creationTime` field set: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "exists": { @@ -32,7 +32,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Find all the documents whose `creationTime` field is not set: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 10000, "query": { diff --git a/api/internal/crawl/search_cmds/keyword_search.md b/api/internal/crawl/search_cmds/keyword_search.md index e3c152d00..588f938fb 100644 --- a/api/internal/crawl/search_cmds/keyword_search.md +++ b/api/internal/crawl/search_cmds/keyword_search.md @@ -1,7 +1,7 @@ Count the documents in the index whose `repositoryUrl` field starts with `https://github.com/`: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -17,7 +17,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Count the documents in the index whose `repositoryUrl` field does not start with `https://github.com/`: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -33,7 +33,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search all the documents matching the given `repositoryUrl` and `filePath`, and return a version for each search hit: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 10000, "version": true, @@ -52,7 +52,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search all the documents whose filePath ends with one of these following three filenames: `kustomization.yaml`, `kustomization.yml`, `kustomization`: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -68,7 +68,7 @@ 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' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { diff --git a/api/internal/crawl/search_cmds/misc.md b/api/internal/crawl/search_cmds/misc.md index ac2659d62..303ae5d3b 100644 --- a/api/internal/crawl/search_cmds/misc.md +++ b/api/internal/crawl/search_cmds/misc.md @@ -10,10 +10,10 @@ curl "${ElasticSearchURL}:9200/_cat/indices?v" Get the mapping of the index: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_mapping?pretty" +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_mapping?pretty" ``` Delete the kustomize index from the ElasticSearch cluster (**Use this command with caution**): ``` -curl -X DELETE "${ElasticSearchURL}:9200/kustomize?pretty" +curl -X DELETE "${ElasticSearchURL}:9200/${INDEXNAME}?pretty" ``` \ No newline at end of file diff --git a/api/internal/crawl/search_cmds/repositoryUrl.md b/api/internal/crawl/search_cmds/repositoryUrl.md index ef7802e04..291aa1c69 100644 --- a/api/internal/crawl/search_cmds/repositoryUrl.md +++ b/api/internal/crawl/search_cmds/repositoryUrl.md @@ -1,6 +1,6 @@ Count distinct values of the `repositoryUrl` field: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "repositoryUrl_count" : { @@ -16,7 +16,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Count how many Github repositories include kustomization files: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -39,7 +39,7 @@ curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Cont Count how many Github repositories include kustomize resource files: ``` -curl -X POST "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -64,7 +64,7 @@ List all the github repositories including kustomization files and kustomize res and how many kustomization files and kustomize resource files each github repository includes (the github repository including the most kustomization files is listed first): ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "aggs" : { "repositoryUrl" : { @@ -80,7 +80,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Conte List the top 20 Github repositories including the most amount of kustomization files: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -103,7 +103,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Conte List the top 20 Github repositories including the most amount of kustomize resource files: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?size=0&pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { diff --git a/api/internal/crawl/search_cmds/text_search.md b/api/internal/crawl/search_cmds/text_search.md index 37a7701b5..dcc033fb8 100644 --- a/api/internal/crawl/search_cmds/text_search.md +++ b/api/internal/crawl/search_cmds/text_search.md @@ -1,6 +1,6 @@ Search for all the kustomize resource files including a Deployment object: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "match" : { @@ -16,7 +16,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kustomize resource files including a Deployment object, but only including the `kinds` field in the result: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "_source": { "includes": ["kinds"] @@ -35,7 +35,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kustomize resource files including both a Deployment object and a Service object: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "match" : { @@ -52,7 +52,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Count the number of documents including Deployment and the number of documents including Service: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 0, "aggs" : { @@ -71,7 +71,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kustomization files involving CRDs: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 10000, "query": { @@ -87,7 +87,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kustomization files defining configMapGenerator: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "size": 10000, "query": { @@ -103,7 +103,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the documents having a `kind` field: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -118,7 +118,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kuostmization files having a `kind` field: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "bool": { @@ -134,7 +134,7 @@ curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type Search for all the kustomization files defining the `generatorOptions:disableNameSuffixHash` feature: ``` -curl -X GET "${ElasticSearchURL}:9200/kustomize/_search?pretty" -H 'Content-Type: application/json' -d' +curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "match" : { From d2f9cf171f8706975dda563a05cae9970fdb831f Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Wed, 15 Jan 2020 18:10:20 -0800 Subject: [PATCH 10/15] kyaml and cmd/config release --- releasing/VERSIONS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/releasing/VERSIONS b/releasing/VERSIONS index 20b5411c0..f079c963f 100644 --- a/releasing/VERSIONS +++ b/releasing/VERSIONS @@ -6,7 +6,7 @@ # kyaml version export kyaml_major=0 export kyaml_minor=0 -export kyaml_patch=7 +export kyaml_patch=8 # 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=8 +export cmd_config_patch=9 # cmd/kubectl version export cmd_kubectl_major=0 From 8633763e9d49d0c4b661dd7b007488defae5ddcc Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Wed, 15 Jan 2020 18:33:41 -0800 Subject: [PATCH 11/15] expose xargs and wrap commands as libraries --- cmd/config/configcobra/cmds.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index 878c7d87c..5b5fa1729 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -58,6 +58,8 @@ var ( Sink = commands.SinkCommand Source = commands.SourceCommand Tree = commands.TreeCommand + Wrap = commands.WrapCommand + XArgs = commands.XArgsCommand StackOnError = &commands.StackOnError ExitOnError = &commands.ExitOnError From d49b8cdf905af065bad2e913d3f863e8532ea9db Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Wed, 15 Jan 2020 20:52:12 -0800 Subject: [PATCH 12/15] add annotate command to cmd/config --- cmd/config/configcobra/cmds.go | 2 + cmd/config/docs/commands/annotate.md | 18 + cmd/config/internal/commands/annotate.go | 102 ++++ cmd/config/internal/commands/annotate_test.go | 469 ++++++++++++++++++ .../internal/generateddocs/commands/docs.go | 14 + 5 files changed, 605 insertions(+) create mode 100644 cmd/config/docs/commands/annotate.md create mode 100644 cmd/config/internal/commands/annotate.go create mode 100644 cmd/config/internal/commands/annotate_test.go diff --git a/cmd/config/configcobra/cmds.go b/cmd/config/configcobra/cmds.go index 5b5fa1729..04de41914 100644 --- a/cmd/config/configcobra/cmds.go +++ b/cmd/config/configcobra/cmds.go @@ -45,6 +45,7 @@ Advanced Documentation Topics: // Export commands publicly for composition var ( + Annotate = commands.AnnotateCommand Cat = commands.CatCommand Count = commands.CountCommand CreateSetter = commands.CreateSetterCommand @@ -91,6 +92,7 @@ func NewConfigCommand(name string) *cobra.Command { name = strings.TrimSpace(name + " config") commands.ExitOnError = true + root.AddCommand(commands.AnnotateCommand(name)) root.AddCommand(commands.GrepCommand(name)) root.AddCommand(commands.TreeCommand(name)) root.AddCommand(commands.CatCommand(name)) diff --git a/cmd/config/docs/commands/annotate.md b/cmd/config/docs/commands/annotate.md new file mode 100644 index 000000000..37299a933 --- /dev/null +++ b/cmd/config/docs/commands/annotate.md @@ -0,0 +1,18 @@ +## annotate + +[Alpha] Set an annotation on Resources. + +### Synopsis + +[Alpha] Set an annotation on Resources. + + DIR: + Path to local directory. + +### Examples + + kustomize config annotate my-dir/ --kv foo=bar + + kustomize config annotate my-dir/ --kv foo=bar --kv a=b + + kustomize config annotate my-dir/ --kv foo=bar --kind Deployment --name foo diff --git a/cmd/config/internal/commands/annotate.go b/cmd/config/internal/commands/annotate.go new file mode 100644 index 000000000..f3a80ff98 --- /dev/null +++ b/cmd/config/internal/commands/annotate.go @@ -0,0 +1,102 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "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/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// NewAnnotateRunner returns a command runner. +func NewAnnotateRunner(parent string) *AnnotateRunner { + r := &AnnotateRunner{} + c := &cobra.Command{ + Use: "annotate [DIR]", + Args: cobra.MaximumNArgs(1), + Short: commands.AnnotateShort, + Long: commands.AnnotateLong, + Example: commands.AnnotateExamples, + RunE: r.runE, + } + fixDocs(parent, c) + r.Command = c + c.Flags().StringVar(&r.Kind, "kind", "", "Resource kind to annotate") + c.Flags().StringVar(&r.ApiVersion, "apiVersion", "", "Resource apiVersion to annotate") + c.Flags().StringVar(&r.Name, "name", "", "Resource name to annotate") + c.Flags().StringVar(&r.Namespace, "namespace", "", "Resource namespace to annotate") + c.Flags().StringSliceVar(&r.Values, "kv", []string{}, "annotation as KEY=VALUE") + return r +} + +func AnnotateCommand(parent string) *cobra.Command { + return NewAnnotateRunner(parent).Command +} + +type AnnotateRunner struct { + Command *cobra.Command + Values []string + Kind string + Name string + ApiVersion string + Namespace string + Path string +} + +func (r *AnnotateRunner) runE(c *cobra.Command, args []string) error { + var input []kio.Reader + var output []kio.Writer + if len(args) == 0 { + rw := &kio.ByteReadWriter{Reader: c.InOrStdin(), Writer: c.OutOrStdout()} + input = []kio.Reader{rw} + output = []kio.Writer{rw} + } else { + rw := &kio.LocalPackageReadWriter{PackagePath: args[0], NoDeleteFiles: true} + input = []kio.Reader{rw} + output = []kio.Writer{rw} + } + return handleError(c, kio.Pipeline{ + Inputs: input, + Filters: []kio.Filter{r}, + Outputs: output}.Execute()) +} + +func (r *AnnotateRunner) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { + for i := range nodes { + n := nodes[i] + m, err := n.GetMeta() + if err != nil { + return nil, err + } + if r.Kind != "" && r.Kind != m.Kind { + continue + } + if r.ApiVersion != "" && r.ApiVersion != m.APIVersion { + continue + } + if r.Namespace != "" && r.Namespace != m.Namespace { + continue + } + if r.Name != "" && r.Name != m.Name { + continue + } + + for i := range r.Values { + // split key, value pairs + kv := strings.SplitN(r.Values[i], "=", 2) + if len(kv) != 2 { + return nil, errors.Errorf("must specify --kv as KEY=VALUE: %s", r.Values[i]) + } + if err := n.PipeE(yaml.SetAnnotation(kv[0], kv[1])); err != nil { + return nil, err + } + } + + } + return nodes, nil +} diff --git a/cmd/config/internal/commands/annotate_test.go b/cmd/config/internal/commands/annotate_test.go new file mode 100644 index 000000000..8354cff9b --- /dev/null +++ b/cmd/config/internal/commands/annotate_test.go @@ -0,0 +1,469 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package commands + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/kio" +) + +func TestAnnotateCommand(t *testing.T) { + var tests = []struct { + name string + args []string + expected string + }{ + { + name: "single value", + args: []string{"--kv", "a=b"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + { + name: "multi value", + args: []string{"--kv", "a=b", "--kv", "c=d"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + a: 'b' + c: 'd' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + c: 'd' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + c: 'd' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + c: 'd' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + { + name: "filter kind", + args: []string{"--kv", "a=b", "--kind", "Service"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + { + name: "filter apiVersion", + args: []string{"--kv", "a=b", "--apiVersion", "v1"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + { + name: "filter name", + args: []string{"--kv", "a=b", "--name", "bar"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + { + name: "filter namespace", + args: []string{"--kv", "a=b", "--namespace", "bar"}, + expected: `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +`, + }, + } + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + d := setTestFiles(t) + defer os.RemoveAll(d) + + a := NewAnnotateRunner("") + a.Command.SetArgs(append([]string{d}, tt.args...)) + a.Command.SilenceUsage = true + a.Command.SilenceErrors = true + + err := a.Command.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + actual := &bytes.Buffer{} + err = kio.Pipeline{ + Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: d}}, + Outputs: []kio.Writer{kio.ByteWriter{Writer: actual, KeepReaderAnnotations: true}}, + }.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(actual.String())) { + t.FailNow() + } + }) + } +} + +func setTestFiles(t *testing.T) string { + d, err := ioutil.TempDir("", "kustomize-cat-test") + if !assert.NoError(t, err) { + t.FailNow() + } + err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(` +kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx +spec: + selector: + app: nginx +`), 0600) + if !assert.NoError(t, err) { + defer os.RemoveAll(d) + t.FailNow() + } + err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(` +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + namespace: foo +spec: + replicas: 3 +`), 0600) + if !assert.NoError(t, err) { + defer os.RemoveAll(d) + t.FailNow() + } + return d +} diff --git a/cmd/config/internal/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index aca3a2d7d..b4cfce577 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -4,6 +4,20 @@ // Code generated by "mdtogo"; DO NOT EDIT. package commands +var AnnotateShort = `[Alpha] Set an annotation on Resources.` +var AnnotateLong = ` +[Alpha] Set an annotation on Resources. + + DIR: + Path to local directory. +` +var AnnotateExamples = ` + kustomize config annotate my-dir/ --kv foo=bar + + kustomize config annotate my-dir/ --kv foo=bar --kv a=b + + kustomize config annotate my-dir/ --kv foo=bar --kind Deployment --name foo` + var CatShort = `[Alpha] Print Resource Config from a local directory.` var CatLong = ` [Alpha] Print Resource Config from a local directory. From 9f80da28ae76ce9dbb1a1a5f90ad777496552675 Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Thu, 16 Jan 2020 09:20:24 -0800 Subject: [PATCH 13/15] Refactor the stats code for generators and transformers --- .../crawl/cmd/kustomize_stats/main.go | 95 ++++++------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/api/internal/crawl/cmd/kustomize_stats/main.go b/api/internal/crawl/cmd/kustomize_stats/main.go index d3a436ebe..103405079 100644 --- a/api/internal/crawl/cmd/kustomize_stats/main.go +++ b/api/internal/crawl/cmd/kustomize_stats/main.go @@ -46,84 +46,50 @@ func SortMapKeyByValue(m map[string]int) []string { return keys } -func GeneratorAndTransformerStats(ctx context.Context, - generatorDocs []*doc.Document, transformerDocs []*doc.Document, - idx *index.KustomizeIndex) { - // allGenerators includes all the documents referred in the generators field - allGenerators := doc.NewUniqueDocuments() +func GeneratorOrTransformerStats(ctx context.Context, + docs []*doc.Document, isGenerator bool, idx *index.KustomizeIndex) { - // allTransformers includes all the documents referred in the transformers field - allTransformers := doc.NewUniqueDocuments() + fieldName := "generators" + if !isGenerator { + fieldName = "transformers" + } - // docUsingGeneratorCount counts the number of the kustomization files using generators - docUsingGeneratorCount := 0 + // allReferredDocs includes all the documents referred in the field + allReferredDocs := doc.NewUniqueDocuments() - // docUsingTransformerCount counts the number of the kustomization files using transformers - docUsingTransformerCount := 0 + // docUsingGeneratorCount counts the number of the kustomization files using generators or transformers + docCount := 0 - // collect all the documents referred in the generators and transformers fields - for _, d := range generatorDocs { + // collect all the documents referred in the field + for _, d := range docs { kdoc := doc.KustomizationDocument{ Document: *d, } - generators, err := kdoc.GetResources(false, false, true) + referredDocs, err := kdoc.GetResources(false, !isGenerator, isGenerator) if err != nil { - log.Printf("failed to parse the generators field of the Document (%s): %v", - d.Path(), err) + log.Printf("failed to parse the %s field of the Document (%s): %v", + fieldName, d.Path(), err) } - if len(generators) > 0 { - docUsingGeneratorCount++ - allGenerators.AddDocuments(generators) + if len(referredDocs) > 0 { + docCount++ + allReferredDocs.AddDocuments(referredDocs) } } - for _, d := range transformerDocs { - kdoc := doc.KustomizationDocument{ - Document: *d, - } - transformers, err := kdoc.GetResources(false, true, false) - if err != nil { - log.Printf("failed to parse the transformers field of the Document (%s): %v", - d.Path(), err) - } - if len(transformers) > 0 { - docUsingTransformerCount++ - allTransformers.AddDocuments(transformers) - } - } + fileCount, dirCount, fileTypeDocs, dirTypeDocs := DocumentTypeSummary(ctx, allReferredDocs.Documents()) - // fileGeneratorCount counts file-type generators - // dirGeneratorCount counts dir-type generators - fileGeneratorCount, dirGeneratorCount, generatorFiles, generatorDirs := DocumentTypeSummary(ctx, allGenerators.Documents()) + // check whether any of the files are not in the index + nonExistFileCount := ExistInIndex(idx, fileTypeDocs, fieldName + " file ") + // check whether any of the dirs are not in the index + nonExistDirCount := ExistInIndex(idx, dirTypeDocs, fieldName + " dir ") - // fileTransformerCount counts file-type transformers - // dirTransformerCount counts dir-type transformers - fileTransformerCount, dirTransformerCount, transformerFiles, transformerDirs := DocumentTypeSummary(ctx, allTransformers.Documents()) + GitRepositorySummary(fileTypeDocs, fieldName + " files") + GitRepositorySummary(dirTypeDocs, fieldName + " dirs") - // check whether any of the generator files are not in the index - nonExistGeneratorFileCount := ExistInIndex(idx, generatorFiles, "generator file ") - // check whether any of the generator dirs are not in the index - nonExistGeneratorDirCount := ExistInIndex(idx, generatorDirs, "generator dir ") - - // check whether any of the transformer files are not in the index - nonExistTransformerFileCount := ExistInIndex(idx, transformerFiles, "transformer file ") - // check whether any of the transformer dirs are not in the index - nonExistTransformerDirCount := ExistInIndex(idx, transformerDirs, "transformer dir ") - - GitRepositorySummary(generatorFiles, "generator files") - GitRepositorySummary(generatorDirs, "generator dirs") - GitRepositorySummary(transformerFiles, "transformer files") - GitRepositorySummary(transformerDirs, "transformer dirs") - - fmt.Printf(`%d kustomization files use generators: %d generators are files and %d generators are dirs. -%d kustomization files use tranformers: %d transformers are files and %d transformers are dirs.`, - docUsingGeneratorCount, fileGeneratorCount, dirGeneratorCount, - docUsingTransformerCount, fileTransformerCount, dirTransformerCount) - fmt.Printf("\n") - fmt.Printf("%d generator files do not exist in the index\n", nonExistGeneratorFileCount) - fmt.Printf("%d generator dirs do not exist in the index\n", nonExistGeneratorDirCount) - fmt.Printf("%d transformer files do not exist in the index\n", nonExistTransformerFileCount) - fmt.Printf("%d transformer dirs do not exist in the index\n", nonExistTransformerDirCount) + fmt.Printf("%d kustomization files use %s: %d %s are files and %d %s are dirs.\n", + docCount, fieldName, fileCount, fieldName, dirCount, fieldName) + fmt.Printf("%d %s files do not exist in the index\n", nonExistFileCount, fieldName) + fmt.Printf("%d %s dirs do not exist in the index\n", nonExistDirCount, fieldName) } // GitRepositorySummary counts the distribution of docs: @@ -314,5 +280,6 @@ There are %d documents in the kustomize index. } } - GeneratorAndTransformerStats(ctx, generatorDocs, transformersDocs, idx) + GeneratorOrTransformerStats(ctx, generatorDocs, true, idx) + GeneratorOrTransformerStats(ctx, transformersDocs, false, idx) } From c96cd82cabdcc1358f980afb24db640cf3d0c340 Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Thu, 16 Jan 2020 11:49:44 -0800 Subject: [PATCH 14/15] Refactor `config annotate` --- cmd/config/internal/commands/annotate.go | 3 +- cmd/config/internal/commands/annotate_test.go | 737 +++++++++--------- 2 files changed, 380 insertions(+), 360 deletions(-) diff --git a/cmd/config/internal/commands/annotate.go b/cmd/config/internal/commands/annotate.go index f3a80ff98..1a22e9b37 100644 --- a/cmd/config/internal/commands/annotate.go +++ b/cmd/config/internal/commands/annotate.go @@ -63,7 +63,8 @@ func (r *AnnotateRunner) runE(c *cobra.Command, args []string) error { return handleError(c, kio.Pipeline{ Inputs: input, Filters: []kio.Filter{r}, - Outputs: output}.Execute()) + Outputs: output, + }.Execute()) } func (r *AnnotateRunner) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { diff --git a/cmd/config/internal/commands/annotate_test.go b/cmd/config/internal/commands/annotate_test.go index 8354cff9b..13f180961 100644 --- a/cmd/config/internal/commands/annotate_test.go +++ b/cmd/config/internal/commands/annotate_test.go @@ -22,362 +22,40 @@ func TestAnnotateCommand(t *testing.T) { expected string }{ { - name: "single value", - args: []string{"--kv", "a=b"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - a: 'b' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - a: 'b' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - a: 'b' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - a: 'b' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "single value", + args: []string{"--kv", "a=b"}, + expected: expectedSingleValue, }, { - name: "multi value", - args: []string{"--kv", "a=b", "--kv", "c=d"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - a: 'b' - c: 'd' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - a: 'b' - c: 'd' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - a: 'b' - c: 'd' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - a: 'b' - c: 'd' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "multi value", + args: []string{"--kv", "a=b", "--kv", "c=d"}, + expected: expectedMultiValue, }, { - name: "filter kind", - args: []string{"--kv", "a=b", "--kind", "Service"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - a: 'b' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "filter kind", + args: []string{"--kv", "a=b", "--kind", "Service"}, + expected: expectedFilterKindService, }, { - name: "filter apiVersion", - args: []string{"--kv", "a=b", "--apiVersion", "v1"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - a: 'b' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "filter apiVersion", + args: []string{"--kv", "a=b", "--apiVersion", "v1"}, + expected: expectedFilterApiVersionV1, }, { - name: "filter name", - args: []string{"--kv", "a=b", "--name", "bar"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - a: 'b' - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "filter name", + args: []string{"--kv", "a=b", "--name", "bar"}, + expected: expectedFilterNameBar, }, { - name: "filter namespace", - args: []string{"--kv", "a=b", "--namespace", "bar"}, - expected: `kind: Deployment -metadata: - labels: - app: nginx2 - name: foo - annotations: - app: nginx2 - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f1.yaml' -spec: - replicas: 1 ---- -kind: Service -metadata: - name: foo - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f1.yaml' -spec: - selector: - app: nginx ---- -apiVersion: v1 -kind: Abstraction -metadata: - name: foo - configFn: - container: - image: gcr.io/example/reconciler:v1 - annotations: - config.kubernetes.io/local-config: "true" - a: 'b' - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'f2.yaml' - namespace: bar -spec: - replicas: 3 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: nginx - name: bar - annotations: - app: nginx - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'f2.yaml' - namespace: foo -spec: - replicas: 3 -`, + name: "filter namespace", + args: []string{"--kv", "a=b", "--namespace", "bar"}, + expected: expectedFilterNamespaceBar, }, } for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - d := setTestFiles(t) + d := initTestDir(t) defer os.RemoveAll(d) a := NewAnnotateRunner("") @@ -398,20 +76,35 @@ spec: if !assert.NoError(t, err) { t.FailNow() } - - if !assert.Equal(t, strings.TrimSpace(tt.expected), strings.TrimSpace(actual.String())) { + if !assert.Equal(t, + strings.TrimSpace(tt.expected), + strings.TrimSpace(actual.String())) { t.FailNow() } }) } } -func setTestFiles(t *testing.T) string { - d, err := ioutil.TempDir("", "kustomize-cat-test") +func initTestDir(t *testing.T) string { + d, err := ioutil.TempDir("", "kustomize-annotate-test") if !assert.NoError(t, err) { t.FailNow() } - err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(` + err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(f1Input), 0600) + if !assert.NoError(t, err) { + defer os.RemoveAll(d) + t.FailNow() + } + err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(f2Input), 0600) + if !assert.NoError(t, err) { + defer os.RemoveAll(d) + t.FailNow() + } + return d +} + +var ( + f1Input = ` kind: Deployment metadata: labels: @@ -430,12 +123,9 @@ metadata: spec: selector: app: nginx -`), 0600) - if !assert.NoError(t, err) { - defer os.RemoveAll(d) - t.FailNow() - } - err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(` +` + + f2Input = ` apiVersion: v1 kind: Abstraction metadata: @@ -460,10 +150,339 @@ metadata: namespace: foo spec: replicas: 3 -`), 0600) - if !assert.NoError(t, err) { - defer os.RemoveAll(d) - t.FailNow() - } - return d -} +` + + expectedSingleValue = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` + + expectedMultiValue = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + a: 'b' + c: 'd' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + c: 'd' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + c: 'd' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + c: 'd' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` + + expectedFilterKindService = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` + + expectedFilterApiVersionV1 = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` + + expectedFilterNameBar = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + a: 'b' + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` + + expectedFilterNamespaceBar = `kind: Deployment +metadata: + labels: + app: nginx2 + name: foo + annotations: + app: nginx2 + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f1.yaml' +spec: + replicas: 1 +--- +kind: Service +metadata: + name: foo + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f1.yaml' +spec: + selector: + app: nginx +--- +apiVersion: v1 +kind: Abstraction +metadata: + name: foo + configFn: + container: + image: gcr.io/example/reconciler:v1 + annotations: + config.kubernetes.io/local-config: "true" + a: 'b' + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'f2.yaml' + namespace: bar +spec: + replicas: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: nginx + name: bar + annotations: + app: nginx + config.kubernetes.io/index: '1' + config.kubernetes.io/path: 'f2.yaml' + namespace: foo +spec: + replicas: 3 +` +) From d93b5a161a0d78f0dce223a40196bd75f3506b14 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Thu, 16 Jan 2020 12:58:04 -0800 Subject: [PATCH 15/15] Adds new InventorySet abstraction --- cmd/kubectl/kubectlcobra/inventory.go | 97 ++++++++ cmd/kubectl/kubectlcobra/inventory_test.go | 271 +++++++++++++++++++++ 2 files changed, 368 insertions(+) diff --git a/cmd/kubectl/kubectlcobra/inventory.go b/cmd/kubectl/kubectlcobra/inventory.go index 09e367bfe..336bbb5e4 100644 --- a/cmd/kubectl/kubectlcobra/inventory.go +++ b/cmd/kubectl/kubectlcobra/inventory.go @@ -6,6 +6,7 @@ package kubectlcobra import ( "fmt" + "sort" "strings" "k8s.io/apimachinery/pkg/runtime/schema" @@ -81,3 +82,99 @@ func (i *Inventory) String() string { i.GroupKind.Group, fieldSeparator, i.GroupKind.Kind) } + +// InventorySet encapsulates a grouping of unique Inventory +// structs. Organizes the Inventory structs with a map, +// which ensures there are no duplicates. Allows set +// operations such as merging sets and subtracting sets. +type InventorySet struct { + set map[string]*Inventory +} + +// NewInventorySet returns a pointer to an InventorySet +// struct grouping the passed Inventory items. +func NewInventorySet(items []*Inventory) *InventorySet { + invSet := InventorySet{set: map[string]*Inventory{}} + invSet.AddItems(items) + return &invSet +} + +// GetItems returns the set of pointers to Inventory +// structs. +func (is *InventorySet) GetItems() []*Inventory { + items := []*Inventory{} + for _, item := range is.set { + items = append(items, item) + } + return items +} + +// AddItems adds Inventory structs to the set which +// are not already in the set. +func (is *InventorySet) AddItems(items []*Inventory) { + for _, item := range items { + if item != nil { + is.set[item.String()] = item + } + } +} + +// DeleteItem removes an Inventory struct from the +// set if it exists in the set. Returns true if the +// Inventory item was deleted, false if it did not exist +// in the set. +func (is *InventorySet) DeleteItem(item *Inventory) bool { + if item == nil { + return false + } + if _, ok := is.set[item.String()]; ok { + delete(is.set, item.String()) + return true + } + return false +} + +// Merge combines the unique set of Inventory items from the +// current set with the passed "other" set, returning a new +// set or error. Returns an error if the passed set to merge +// is nil. +func (is *InventorySet) Merge(other *InventorySet) (*InventorySet, error) { + if other == nil { + return nil, fmt.Errorf("InventorySet to merge is nil.") + } + // Copy the current InventorySet into result + result := NewInventorySet(is.GetItems()) + result.AddItems(other.GetItems()) + return result, nil +} + +// Subtract removes the Inventory items in the "other" set from the +// current set, returning a new set. This does not modify the current +// set. Returns an error if the passed set to subtract is nil. +func (is *InventorySet) Subtract(other *InventorySet) (*InventorySet, error) { + if other == nil { + return nil, fmt.Errorf("InventorySet to subtract is nil.") + } + // Copy the current InventorySet into result + result := NewInventorySet(is.GetItems()) + // Remove each item in "other" which exists in "result" + for _, item := range other.GetItems() { + result.DeleteItem(item) + } + return result, nil +} + +// String returns a string describing set of Inventory structs. +func (is *InventorySet) String() string { + strs := []string{} + for _, item := range is.GetItems() { + strs = append(strs, item.String()) + } + sort.Strings(strs) + return strings.Join(strs, ", ") +} + +// Size returns the number of Inventory structs in the set. +func (is *InventorySet) Size() int { + return len(is.set) +} diff --git a/cmd/kubectl/kubectlcobra/inventory_test.go b/cmd/kubectl/kubectlcobra/inventory_test.go index 2f6aaf080..93fd9a0e8 100644 --- a/cmd/kubectl/kubectlcobra/inventory_test.go +++ b/cmd/kubectl/kubectlcobra/inventory_test.go @@ -216,3 +216,274 @@ func TestParseInventory(t *testing.T) { } } } + +var inventory1 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-1", + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, +} + +var inventory2 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-2", + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, +} + +var inventory3 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-3", + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Service", + }, +} + +var inventory4 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-4", + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "DaemonSet", + }, +} + +func TestNewInventorySet(t *testing.T) { + tests := []struct { + items []*Inventory + expectedStr string + expectedSize int + }{ + { + items: []*Inventory{}, + expectedStr: "", + expectedSize: 0, + }, + { + items: []*Inventory{&inventory1}, + expectedStr: "test-namespace_test-inv-1_apps_Deployment", + expectedSize: 1, + }, + { + items: []*Inventory{&inventory1, &inventory2}, + expectedStr: "test-namespace_test-inv-1_apps_Deployment, test-namespace_test-inv-2__Pod", + expectedSize: 2, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.items) + actualStr := invSet.String() + actualSize := invSet.Size() + if test.expectedStr != actualStr { + t.Errorf("Expected InventorySet (%s), got (%s)\n", test.expectedStr, actualStr) + } + if test.expectedSize != actualSize { + t.Errorf("Expected InventorySet size (%d), got (%d)\n", test.expectedSize, actualSize) + } + actualItems := invSet.GetItems() + if len(test.items) != len(actualItems) { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.items), len(actualItems)) + } + } +} + +func TestInventorySetAddItems(t *testing.T) { + tests := []struct { + initialItems []*Inventory + addItems []*Inventory + expectedItems []*Inventory + }{ + // Adding no items to empty inventory set. + { + initialItems: []*Inventory{}, + addItems: []*Inventory{}, + expectedItems: []*Inventory{}, + }, + // Adding item to empty inventory set. + { + initialItems: []*Inventory{}, + addItems: []*Inventory{&inventory1}, + expectedItems: []*Inventory{&inventory1}, + }, + // Adding no items does not change the inventory set + { + initialItems: []*Inventory{&inventory1}, + addItems: []*Inventory{}, + expectedItems: []*Inventory{&inventory1}, + }, + // Adding an item which alread exists does not increase size. + { + initialItems: []*Inventory{&inventory1, &inventory2}, + addItems: []*Inventory{&inventory1}, + expectedItems: []*Inventory{&inventory1, &inventory2}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + addItems: []*Inventory{&inventory3, &inventory4}, + expectedItems: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4}, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.initialItems) + invSet.AddItems(test.addItems) + if len(test.expectedItems) != invSet.Size() { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size()) + } + } +} + +func TestInventorySetDeleteItem(t *testing.T) { + tests := []struct { + initialItems []*Inventory + deleteItem *Inventory + expected bool + expectedItems []*Inventory + }{ + { + initialItems: []*Inventory{}, + deleteItem: nil, + expected: false, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{}, + deleteItem: &inventory1, + expected: false, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory2}, + deleteItem: &inventory1, + expected: false, + expectedItems: []*Inventory{&inventory2}, + }, + { + initialItems: []*Inventory{&inventory1}, + deleteItem: &inventory1, + expected: true, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + deleteItem: &inventory1, + expected: true, + expectedItems: []*Inventory{&inventory2}, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.initialItems) + actual := invSet.DeleteItem(test.deleteItem) + if test.expected != actual { + t.Errorf("Expected return value (%t), got (%t)\n", test.expected, actual) + } + if len(test.expectedItems) != invSet.Size() { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size()) + } + } +} + +func TestInventorySetMerge(t *testing.T) { + tests := []struct { + set1 []*Inventory + set2 []*Inventory + merged []*Inventory + }{ + { + set1: []*Inventory{}, + set2: []*Inventory{}, + merged: []*Inventory{}, + }, + { + set1: []*Inventory{}, + set2: []*Inventory{&inventory1}, + merged: []*Inventory{&inventory1}, + }, + { + set1: []*Inventory{&inventory1}, + set2: []*Inventory{}, + merged: []*Inventory{&inventory1}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory1}, + merged: []*Inventory{&inventory1, &inventory2}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory1, &inventory2}, + merged: []*Inventory{&inventory1, &inventory2}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory3, &inventory4}, + merged: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4}, + }, + } + + for _, test := range tests { + invSet1 := NewInventorySet(test.set1) + invSet2 := NewInventorySet(test.set2) + expected := NewInventorySet(test.merged) + merged, _ := invSet1.Merge(invSet2) + if expected.Size() != merged.Size() { + t.Errorf("Expected merged inventory set size (%d), got (%d)\n", expected.Size(), merged.Size()) + } + } +} + +func TestInventorySetSubtract(t *testing.T) { + tests := []struct { + initialItems []*Inventory + subtractItems []*Inventory + expected []*Inventory + }{ + { + initialItems: []*Inventory{}, + subtractItems: []*Inventory{}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{}, + subtractItems: []*Inventory{&inventory1}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1}, + subtractItems: []*Inventory{}, + expected: []*Inventory{&inventory1}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory1}, + expected: []*Inventory{&inventory2}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory1, &inventory2}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory3, &inventory4}, + expected: []*Inventory{&inventory1, &inventory2}, + }, + } + + for _, test := range tests { + invInitialItems := NewInventorySet(test.initialItems) + invSubtractItems := NewInventorySet(test.subtractItems) + expected := NewInventorySet(test.expected) + actual, _ := invInitialItems.Subtract(invSubtractItems) + if expected.Size() != actual.Size() { + t.Errorf("Expected subtracted inventory set size (%d), got (%d)\n", expected.Size(), actual.Size()) + } + } +}