Compare commits

..

7 Commits

Author SHA1 Message Date
Phillip Wittrock
2554cd00eb update go.mod for release 2020-01-02 09:02:06 -08:00
Phillip Wittrock
383244cd63 Release cmd/config 0.0.4
Merge remote-tracking branch 'upstream/master' into release-cmd/config-v0.0
2020-01-02 09:01:56 -08:00
Phillip Wittrock
b9da33afd4 update go.mod for release 2019-12-20 09:44:25 -08:00
Phillip Wittrock
23e339b86c Merge remote-tracking branch 'upstream/master' into release-cmd/config-v0.0 2019-12-20 09:44:21 -08:00
Phillip Wittrock
9555009df8 update go.mod for release 2019-12-16 14:21:02 -08:00
Phillip Wittrock
91a10c560c Merge remote-tracking branch 'upstream/master' into release-cmd/config-v0.0 2019-12-16 14:20:58 -08:00
Phillip Wittrock
780cb19c4d drop replace 2019-12-13 13:50:17 -08:00
298 changed files with 3772 additions and 42331 deletions

View File

@@ -7,13 +7,8 @@ linters:
- bodyclose - bodyclose
- deadcode - deadcode
- depguard - depguard
# - dogsled
- dupl - dupl
# - errcheck
# - funlen
# - gochecknoinits
- goconst - goconst
# - gocritic
- gocyclo - gocyclo
- gofmt - gofmt
- goimports - goimports
@@ -26,7 +21,6 @@ linters:
- lll - lll
- misspell - misspell
- nakedret - nakedret
# - scopelint
- staticcheck - staticcheck
- structcheck - structcheck
# stylecheck demands that acronyms not be treated as words # stylecheck demands that acronyms not be treated as words
@@ -34,10 +28,9 @@ linters:
# - stylecheck # - stylecheck
- typecheck - typecheck
- unconvert - unconvert
- unparam
- unused - unused
- unparam
- varcheck - varcheck
# - whitespace
linters-settings: linters-settings:
dupl: dupl:

View File

@@ -36,7 +36,6 @@ install: true
script: script:
- make verify-kustomize - make verify-kustomize
- ./travis/kyaml-pre-commit.sh - ./travis/kyaml-pre-commit.sh
- ./travis/check-go-mod.sh
# TBD. Suppressing for now. # TBD. Suppressing for now.
notifications: notifications:

View File

@@ -17,16 +17,6 @@ verify-kustomize: \
test-examples-kustomize-against-HEAD \ test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-latest test-examples-kustomize-against-latest
# The following target referenced by a file in
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
.PHONY: prow-presubmit-check
prow-presubmit-check: \
lint-kustomize \
test-unit-kustomize-all
.PHONY: verify-kustomize-e2e
verify-kustomize-e2e: test-examples-e2e-kustomize
# Other builds in this repo might want a different linter version. # Other builds in this repo might want a different linter version.
# Without one Makefile to rule them all, the different makes # Without one Makefile to rule them all, the different makes
# cannot assume that golanci-lint is at the version they want # cannot assume that golanci-lint is at the version they want
@@ -58,11 +48,6 @@ $(MYGOBIN)/goimports:
cd api; \ cd api; \
go install golang.org/x/tools/cmd/goimports go install golang.org/x/tools/cmd/goimports
# Install resource from whatever is checked out.
$(MYGOBIN)/resource:
cd cmd/resource; \
go install .
# To pin pluginator, use this recipe instead: # To pin pluginator, use this recipe instead:
# cd api; # cd api;
# go install sigs.k8s.io/kustomize/pluginator/v2 # go install sigs.k8s.io/kustomize/pluginator/v2
@@ -208,16 +193,6 @@ test-unit-kustomize-all: \
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh HEAD ./hack/testExamplesAgainstKustomize.sh HEAD
.PHONY:
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
( \
set -e; \
/bin/rm -f $(MYGOBIN)/kustomize; \
echo "Installing kustomize from ."; \
cd kustomize; go install .; cd ..; \
./hack/testExamplesE2EAgainstKustomize.sh .; \
)
.PHONY: .PHONY:
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
( \ ( \
@@ -262,16 +237,6 @@ $(MYGOBIN)/helm:
rm -rf $$d \ rm -rf $$d \
) )
$(MYGOBIN)/kind:
( \
set -e; \
d=$(shell mktemp -d); cd $$d; \
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(shell uname)-amd64; \
chmod +x ./kind; \
mv ./kind $(MYGOBIN); \
rm -rf $$d; \
)
.PHONY: clean .PHONY: clean
clean: kustomize-external-go-plugin-clean clean: kustomize-external-go-plugin-clean
go clean --cache go clean --cache

View File

@@ -52,7 +52,7 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals) matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
if len(matches) != 1 { if len(matches) != 1 {
return fmt.Errorf("namespace tranformation produces ID conflict: %+v", matches) return fmt.Errorf("namespace tranformation produces ID conflict: %#v", matches)
} }
} }
return nil return nil

View File

@@ -44,7 +44,7 @@ type kustomizeSearch struct {
// /register: not implemented, but meant as an endpoint for adding new // /register: not implemented, but meant as an endpoint for adding new
// kustomization files to the corpus. // kustomization files to the corpus.
func NewKustomizeSearch(ctx context.Context) (*kustomizeSearch, error) { func NewKustomizeSearch(ctx context.Context) (*kustomizeSearch, error) {
idx, err := index.NewKustomizeIndex(ctx, "kustomize") idx, err := index.NewKustomizeIndex(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -2,15 +2,12 @@ package main
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
"time" "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"
"sigs.k8s.io/kustomize/api/internal/crawl/crawler/github" "sigs.k8s.io/kustomize/api/internal/crawl/crawler/github"
"sigs.k8s.io/kustomize/api/internal/crawl/doc" "sigs.k8s.io/kustomize/api/internal/crawl/doc"
@@ -28,7 +25,6 @@ const (
) )
type CrawlMode int type CrawlMode int
const ( const (
CrawlUnknown CrawlMode = iota CrawlUnknown CrawlMode = iota
// Crawl all the kustomization files in all the repositories of a Github user // Crawl all the kustomization files in all the repositories of a Github user
@@ -49,7 +45,7 @@ func NewCrawlMode(s string) CrawlMode {
return CrawlUser return CrawlUser
case "github-repo": case "github-repo":
return CrawlRepo return CrawlRepo
case "index+github": case "":
return CrawlIndexAndGithub return CrawlIndexAndGithub
case "index": case "index":
return CrawlIndex return CrawlIndex
@@ -60,33 +56,30 @@ func NewCrawlMode(s string) CrawlMode {
} }
} }
func main() { func Usage() {
indexNamePtr := flag.String( fmt.Printf("Usage: %s [mode] [githubUser|githubRepo]\n", os.Args[0])
"index", "kustomize", "The name of the ElasticSearch index.") fmt.Printf("\tmode can be one of [github-user, github-repo, index, github]\n")
modePtr := flag.String("mode", "index+github", fmt.Printf("%s: crawl all the documents in the index and crawling all the kustomization files on Github\n", os.Args[0])
`The crawling mode, which can be one of [github-user, github-repo, index, github, index+github]. fmt.Printf("%s index: crawl all the documents in the index\n", os.Args[0])
* github-user: crawl all the kustomization files in all the repositories of a Github user (--github-user must be specified for this mode). fmt.Printf("%s gihub: crawl all the kustomization files on Github\n", os.Args[0])
* github-repo: crawl all the kustomization files in a Github repository (--github-repo must be specified for this mode). fmt.Printf("%s github-user <github-user>: Crawl all the kustomization files in all the repositories of a Github user\n", os.Args[0])
* index: crawl all the documents in the index. fmt.Printf("\tFor example, %s github-user kubernetes-sigs\n", os.Args[0])
* gihub: crawl all the kustomization files on Github. fmt.Printf("%s github-repo <github-repo>: Crawl all the kustomization files in a Github repo\n", os.Args[0])
* index+github: crawl all the documents in the index and crawling all the kustomization files on Github.`) fmt.Printf("\tFor example, %s github-repo kubernetes-sigs/kustomize\n", os.Args[0])
githubUserPtr := flag.String("github-user", "", }
"A github user name (e.g., kubernetes-sigs). This flag is required for the `github-user` mode.")
githubRepoPtr := flag.String("github-repo", "",
"A github repository name (e.g., kubernetes-sigs/kustomize). This flag is required for the `github-repo` mode.")
flag.Parse()
func main() {
githubToken := os.Getenv(githubAccessTokenVar) githubToken := os.Getenv(githubAccessTokenVar)
if githubToken == "" { if githubToken == "" {
log.Printf("Must set the variable '%s' to make github requests.\n", fmt.Printf("Must set the variable '%s' to make github requests.\n",
githubAccessTokenVar) githubAccessTokenVar)
return return
} }
ctx := context.Background() ctx := context.Background()
idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr) idx, err := index.NewKustomizeIndex(ctx)
if err != nil { if err != nil {
log.Printf("Could not create an index: %v\n", err) fmt.Printf("Could not create an index: %v\n", err)
return return
} }
@@ -94,7 +87,7 @@ func main() {
cache, err := redis.DialURL(cacheURL) cache, err := redis.DialURL(cacheURL)
clientCache := &http.Client{} clientCache := &http.Client{}
if err != nil { if err != nil {
log.Printf("Error: redis could not make a connection: %v\n", err) fmt.Printf("Error: redis could not make a connection: %v\n", err)
} else { } else {
clientCache = httpclient.NewClient(cache) clientCache = httpclient.NewClient(cache)
} }
@@ -115,10 +108,10 @@ func main() {
case *doc.KustomizationDocument: case *doc.KustomizationDocument:
switch mode { switch mode {
case index.Delete: case index.Delete:
log.Printf("Deleting: %v", d) fmt.Println("Deleting: ", d)
return idx.Delete(d.ID()) return idx.Delete(d.ID())
default: default:
log.Printf("Inserting: %v", d) fmt.Println("Inserting: ", d)
return idx.Put(d.ID(), d) return idx.Put(d.ID(), d)
} }
default: default:
@@ -126,19 +119,23 @@ func main() {
} }
} }
// seen tracks the IDs of all the documents in the index and their corresponding file types. // seen tracks the IDs of all the documents in the index.
// This helps avoid indexing a given document multiple times. // This helps avoid indexing a given document multiple times.
seen := utils.NewSeenMap() seen := make(map[string]struct{})
mode := NewCrawlMode(*modePtr) var mode CrawlMode
if len(os.Args) == 1 {
mode = CrawlIndexAndGithub
} else {
mode = NewCrawlMode(os.Args[1])
}
ghCrawlerConstructor := func(user, repo string) crawler.Crawler { ghCrawlerConstructor := func(user, repo string) crawler.Crawler {
if user != "" { if user != "" {
return github.NewCrawler(githubToken, retryCount, clientCache, return github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith( github.QueryWith(
github.Filename("kustomization.yaml"), github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"), github.Filename("kustomization.yml"),
github.Filename("kustomization"),
github.User(user)), github.User(user)),
) )
} else if repo != "" { } else if repo != "" {
@@ -146,15 +143,13 @@ func main() {
github.QueryWith( github.QueryWith(
github.Filename("kustomization.yaml"), github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"), github.Filename("kustomization.yml"),
github.Filename("kustomization"),
github.Repo(repo)), github.Repo(repo)),
) )
} else { } else {
return github.NewCrawler(githubToken, retryCount, clientCache, return github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith( github.QueryWith(
github.Filename("kustomization.yaml"), github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"), github.Filename("kustomization.yml")),
github.Filename("kustomization")),
) )
} }
} }
@@ -167,11 +162,11 @@ func main() {
it := idx.IterateQuery(query, 10000, 60*time.Second) it := idx.IterateQuery(query, 10000, 60*time.Second)
for it.Next() { for it.Next() {
for _, hit := range it.Value().Hits.Hits { for _, hit := range it.Value().Hits.Hits {
seedDocs = append(seedDocs, hit.Document.Document.Copy()) seedDocs = append(seedDocs, hit.Document.Copy())
} }
} }
if err := it.Err(); err != nil { if err := it.Err(); err != nil {
log.Fatalf("getSeedDocsFunc Error iterating: %v\n", err) fmt.Printf("Error iterating: %v\n", err)
} }
} }
@@ -187,29 +182,23 @@ func main() {
crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, indexFunc, seen) crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, indexFunc, seen)
case CrawlGithub: case CrawlGithub:
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")} crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
// add all the documents in the index into seen.
// this greatly reduces the time overhead of CrawlGithub.
getSeedDocsFunc()
for _, d := range seedDocs {
seen[d.ID()] = d.FileType
}
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen) crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlUser: case CrawlUser:
if *githubUserPtr == "" { if len(os.Args) < 3 {
flag.Usage() Usage()
log.Fatalf("Please specify a github user with the github-user flag!") log.Fatalf("Please specify a github user!")
} }
crawlers := []crawler.Crawler{ghCrawlerConstructor(*githubUserPtr, "")} crawlers := []crawler.Crawler{ghCrawlerConstructor(os.Args[2], "")}
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen) crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlRepo: case CrawlRepo:
if *githubRepoPtr == "" { if len(os.Args) < 3 {
flag.Usage() Usage()
log.Fatalf("Please specify a github repository with the github-repo flag!") log.Fatalf("Please specify a github repo!")
} }
crawlers := []crawler.Crawler{ghCrawlerConstructor("", *githubRepoPtr)} crawlers := []crawler.Crawler{ghCrawlerConstructor("", os.Args[2])}
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen) crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlUnknown: case CrawlUnknown:
flag.Usage() Usage()
log.Fatalf("The --mode flag must be one of [github-user, github-repo, index, github, index+github].") log.Fatalf("The crawler mode must be one of [github-user, github-repo, index, github]")
} }
} }

View File

@@ -1,14 +0,0 @@
FROM golang:1.11 AS build
ARG GO111MODULE=on
WORKDIR /go/src/sigs.k8s.io/kustomize/api/internal/crawl
COPY . /go/src/sigs.k8s.io/kustomize/api/internal/crawl
RUN go mod download
RUN CGO_ENABLED=0 go install ./cmd/kustomize_stats
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /go/bin/kustomize_stats /
ENTRYPOINT ["/kustomize_stats"]

View File

@@ -1,249 +0,0 @@
package main
import (
"context"
"crypto/sha256"
"flag"
"fmt"
"log"
"sort"
"time"
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
"sigs.k8s.io/kustomize/api/internal/crawl/index"
)
// iterateArr adds each item in arr into countMap.
func iterateArr(arr []string, countMap map[string]int) {
for _, item := range arr {
if _, ok := countMap[item]; !ok {
countMap[item] = 1
} else {
countMap[item]++
}
}
}
// SortMapKeyByValueInt 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 SortMapKeyByValueInt(m map[string]int) []string {
keys := make([]string, 0, len(m))
for key := range m {
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]] })
return keys
}
// 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 SortMapKeyByValueLen(m map[string][]string) []string {
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
// sort keys according to their values in the map m
sort.Slice(keys, func(i, j int) bool { return len(m[keys[i]]) > len(m[keys[j]]) })
return keys
}
func GeneratorOrTransformerStats(docs []*doc.KustomizationDocument) {
n := len(docs)
if n == 0 {
return
}
fileType := docs[0].FileType
fmt.Printf("There are totally %d %s files.\n", n, fileType)
GitRepositorySummary(docs, fileType)
// key of kindToUrls: a string in the KustomizationDocument.Kinds field
// value of kindToUrls: a slice of string urls defining a given kind.
kindToUrls := make(map[string][]string)
for _, d := range docs {
url := fmt.Sprintf("%s/blob/%s/%s", d.RepositoryURL, d.DefaultBranch, d.FilePath)
for _, kind := range d.Kinds {
if _, ok := kindToUrls[kind]; !ok {
kindToUrls[kind] = []string{url}
} else {
kindToUrls[kind] = append(kindToUrls[kind], url)
}
}
}
fmt.Printf("There are totally %d kinds of %s\n", len(kindToUrls), fileType)
sortedKeys := SortMapKeyByValueLen(kindToUrls)
for _, k := range sortedKeys {
sort.Strings(kindToUrls[k])
fmt.Printf("%s kind %s appears %d times\n", fileType, k, len(kindToUrls[k]))
for _, url := range kindToUrls[k] {
fmt.Printf("%s\n", url)
}
}
}
// 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.KustomizationDocument, fileType 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 := SortMapKeyByValueInt(m)
topN := 10
i := 0
for _, k := range sortedKeys {
if i >= topN {
break
}
fmt.Printf("%d %s are from %s\n", m[k], fileType, k)
i++
}
}
func main() {
topKindsPtr := flag.Int(
"kinds", -1,
`the number of kubernetes object kinds to be listed according to their popularities.
By default, all the kinds will be listed.
If you only want to list the 10 most popular kinds, set the flag to 10.`)
topIdentifiersPtr := flag.Int(
"identifiers", -1,
`the number of identifiers to be listed according to their popularities.
By default, all the identifiers will be listed.
If you only want to list the 10 most popular identifiers, set the flag to 10.`)
topKustomizeFeaturesPtr := flag.Int(
"kustomize-features", -1,
`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, *indexNamePtr)
if err != nil {
log.Fatalf("Could not create an index: %v\n", err)
}
// count tracks the number of documents in the index
count := 0
// kustomizationFilecount tracks the number of kustomization files in the index
kustomizationFilecount := 0
kindsMap := make(map[string]int)
identifiersMap := make(map[string]int)
kustomizeIdentifiersMap := make(map[string]int)
// ids tracks the unique IDs of the documents in the index
ids := make(map[string]struct{})
// generatorFiles include all the non-kustomization files whose FileType is generator
generatorFiles := make([]*doc.KustomizationDocument, 0)
// transformersFiles include all the non-kustomization files whose FileType is transformer
transformersFiles := make([]*doc.KustomizationDocument, 0)
checksums := make(map[string]int)
// get all the documents in the index
query := []byte(`{ "query":{ "match_all":{} } }`)
it := idx.IterateQuery(query, 10000, 60*time.Second)
for it.Next() {
for _, hit := range it.Value().Hits.Hits {
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(hit.Document.DocumentData)))
if _, ok := checksums[sum]; ok {
checksums[sum]++
} else {
checksums[sum] = 1
}
// check whether there is any duplicate IDs in the index
if _, ok := ids[hit.ID]; !ok {
ids[hit.ID] = struct{}{}
} else {
log.Printf("Found duplicate ID (%s)\n", hit.ID)
}
count++
iterateArr(hit.Document.Kinds, kindsMap)
iterateArr(hit.Document.Identifiers, identifiersMap)
if doc.IsKustomizationFile(hit.Document.FilePath) {
kustomizationFilecount++
iterateArr(hit.Document.Identifiers, kustomizeIdentifiersMap)
} else {
switch hit.Document.FileType {
case "generator":
generatorFiles = append(generatorFiles, hit.Document.Copy())
case "transformer":
transformersFiles = append(transformersFiles, hit.Document.Copy())
}
}
}
}
if err := it.Err(); err != nil {
log.Fatalf("Error iterating: %v\n", err)
}
sortedKindsMapKeys := SortMapKeyByValueInt(kindsMap)
sortedIdentifiersMapKeys := SortMapKeyByValueInt(identifiersMap)
sortedKustomizeIdentifiersMapKeys := SortMapKeyByValueInt(kustomizeIdentifiersMap)
fmt.Printf(`The count of unique document IDs in the kustomize index: %d
There are %d documents in the kustomize index.
%d kinds of kubernetes objects are customized:`, len(ids), count, len(kindsMap))
fmt.Printf("\n")
kindCount := 0
for _, key := range sortedKindsMapKeys {
if *topKindsPtr < 0 || (*topKindsPtr >= 0 && kindCount < *topKindsPtr) {
fmt.Printf("\tkind `%s` is customimzed in %d documents\n", key, kindsMap[key])
kindCount++
}
}
fmt.Printf("%d kinds of identifiers are found:\n", len(identifiersMap))
identifierCount := 0
for _, key := range sortedIdentifiersMapKeys {
if *topIdentifiersPtr < 0 || (*topIdentifiersPtr >= 0 && identifierCount < *topIdentifiersPtr) {
fmt.Printf("\tidentifier `%s` appears in %d documents\n", key, identifiersMap[key])
identifierCount++
}
}
fmt.Printf(`There are %d kustomization files in the kustomize index.
%d kinds of kustomize features are found:`, kustomizationFilecount, len(kustomizeIdentifiersMap))
fmt.Printf("\n")
kustomizeFeatureCount := 0
for _, key := range sortedKustomizeIdentifiersMapKeys {
if *topKustomizeFeaturesPtr < 0 || (*topKustomizeFeaturesPtr >= 0 && kustomizeFeatureCount < *topKustomizeFeaturesPtr) {
fmt.Printf("\tfeature `%s` is used in %d documents\n", key, kustomizeIdentifiersMap[key])
kustomizeFeatureCount++
}
}
GeneratorOrTransformerStats(generatorFiles)
GeneratorOrTransformerStats(transformersFiles)
fmt.Printf("There are total %d checksums of document contents\n", len(checksums))
sortedChecksums := SortMapKeyByValueInt(checksums)
sortedChecksums = sortedChecksums[:20]
fmt.Printf("The top 20 checksums are:\n")
for _, key := range sortedChecksums {
fmt.Printf("checksum %s apprears %d\n", key, checksums[key])
}
}

View File

@@ -1,8 +0,0 @@
This binary takes as its input a json file including GKE logs (which can be
[exported](https://cloud.google.com/logging/docs/export/configure_export_v2) into
[Cloud Storage](https://cloud.google.com/storage/docs/)),
and extracts the `textPayload` field of each log entry.
Here is an log entry example:
{"insertId":"1sxuh4jg5lw6w10","labels":{"compute.googleapis.com/resource_name":"gke-crawler2-default-pool-5e55ea05-gzgv","container.googleapis.com/namespace_name":"default","container.googleapis.com/pod_name":"kustomize-stats-5bczg","container.googleapis.com/stream":"stdout"},"logName":"projects/haiyanmeng-gke-dev/logs/kustomize-stats","receiveTimestamp":"2020-01-06T23:33:07.012831742Z","resource":{"labels":{"cluster_name":"crawler2","container_name":"kustomize-stats","instance_id":"8183086081854184383","namespace_id":"default","pod_id":"kustomize-stats-5bczg","project_id":"haiyanmeng-gke-dev","zone":"us-central1-a"},"type":"container"},"severity":"INFO","textPayload":"The kustomize index already exists\n","timestamp":"2020-01-06T23:32:46.628930547Z"}

View File

@@ -1,7 +0,0 @@
wget <log-file-url> -O log
go build .
./log-parser log >out
cat out | grep "kind \`" | cut -d\` -f2 | tail -n 50
cat out | grep "kind \`" | awk '{print $6}' | tail -n 50
cat out | grep "feature \`" | grep -v "\`resources\`" | grep -v -e "\`apiVersion\`" | grep -v -e "\`apiversion\`" | cut -d\` -f2
cat out | grep "feature \`" | grep -v "\`resources\`" | grep -v -e "\`apiVersion\`" | grep -v -e "\`apiversion\`" | awk '{print $6}'

View File

@@ -1,49 +0,0 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("The usage of the command is: \n\t%s <log-file.json>", os.Args[0])
}
file, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
closeFile := func(file *os.File) {
if err := file.Close(); err != nil {
log.Fatal(err)
}
}
defer closeFile(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
var entry interface{}
if err := json.Unmarshal([]byte(line), &entry); err != nil {
log.Printf("failed to unmarshal a log entry: %s\n", line)
}
m := entry.(map[string]interface{})
if payload, ok := m["textPayload"]; ok {
// use fmt.Printf here instead of log.Printf to avoid the time and code location info the log package provides
fmt.Printf("%s", payload)
} else {
log.Printf("the log entry does not have the `textPayload` field: %s\n", line)
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}

View File

@@ -2,4 +2,5 @@ configmapGenerator:
- name: elasticsearch-config - name: elasticsearch-config
literals: literals:
- es-url="http://esbasic-master:9200" - es-url="http://esbasic-master:9200"
- kustomize-index-name="kustomize"
- plugin-index-name="plugin" - plugin-index-name="plugin"

View File

@@ -1 +0,0 @@
github_api_secret.txt

View File

@@ -1,10 +1,9 @@
apiVersion: batch/v1beta1 apiVersion: batch/v1beta1
kind: CronJob kind: CronJob
metadata: metadata:
name: crawler-cronjob name: crawler
spec: spec:
# run the cronjob at 00:00 every 7 days schedule: "5 0 * * */1"
schedule: "0 0 */7 * *"
jobTemplate: jobTemplate:
spec: spec:
template: template:
@@ -12,9 +11,7 @@ spec:
restartPolicy: OnFailure restartPolicy: OnFailure
containers: containers:
- name: crawler - name: crawler
image: gcr.io/haiyanmeng-gke-dev/crawler:v1 image: gcr.io/kustomize-search/crawler:latest
command: ["/crawler"]
args: ["--mode=index+github", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"]
imagePullPolicy: Always imagePullPolicy: Always
env: env:
- name: GITHUB_ACCESS_TOKEN - name: GITHUB_ACCESS_TOKEN

View File

@@ -1,4 +1,4 @@
The crawler job can run in one of the following mode: There are three ways of running the crawler job.
# Crawling all the documents in the index and crawling all the kustomization files on Github # Crawling all the documents in the index and crawling all the kustomization files on Github
@@ -7,13 +7,14 @@ of the container should be:
``` ```
command: ["/crawler"] command: ["/crawler"]
args: []
``` ```
Or Or
``` ```
command: ["/crawler"] command: ["/crawler"]
args: ["--mode=index+github"] args: [""]
``` ```
# Crawling all the documents in the index # Crawling all the documents in the index
@@ -22,7 +23,7 @@ The `command` and `args` field of the container should be:
``` ```
command: ["/crawler"] command: ["/crawler"]
args: ["--mode=index"] args: ["index"]
``` ```
# Crawling all the kustomization files on Github # Crawling all the kustomization files on Github
@@ -31,7 +32,7 @@ The `command` and `args` field of the container should be:
``` ```
command: ["/crawler"] command: ["/crawler"]
args: ["--mode=github"] args: ["github"]
``` ```
# Crawling all the kustomization files in a Github repo # Crawling all the kustomization files in a Github repo
@@ -40,7 +41,7 @@ The `command` and `args` field of the container should be like:
``` ```
command: ["/crawler"] command: ["/crawler"]
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize"] args: ["github-repo", "kubernetes-sigs/kustomize"]
``` ```
# Crawling all the kustomization files in all the repositories of a Github user # Crawling all the kustomization files in all the repositories of a Github user
@@ -49,5 +50,5 @@ The `command` and `args` field of the container should be like:
``` ```
command: ["/crawler"] command: ["/crawler"]
args: ["--github-user", "--github-user=kubernetes-sigs"] args: ["github-user", "kubernetes-sigs"]
``` ```

View File

@@ -11,7 +11,7 @@ spec:
image: gcr.io/haiyanmeng-gke-dev/crawler:v1 image: gcr.io/haiyanmeng-gke-dev/crawler:v1
imagePullPolicy: Always imagePullPolicy: Always
command: ["/crawler"] command: ["/crawler"]
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"] args: ["github-repo", "kubernetes-sigs/kustomize"]
env: env:
- name: GITHUB_ACCESS_TOKEN - name: GITHUB_ACCESS_TOKEN
valueFrom: valueFrom:

View File

@@ -1,20 +0,0 @@
apiVersion: batch/v1
kind: Job
metadata:
name: kustomize-stats
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: kustomize-stats
image: gcr.io/haiyanmeng-gke-dev/kustomize_stats:v1
imagePullPolicy: Always
command: ["/kustomize_stats"]
args: ["--index=kustomize", "--kinds=51", "--identifiers=50", "--kustomize-features=50"]
env:
- name: ELASTICSEARCH_URL
valueFrom:
configMapKeyRef:
name: elasticsearch-config
key: es-url

View File

@@ -1,3 +0,0 @@
resources:
- ../base
- job.yaml

View File

@@ -1,23 +0,0 @@
# ESBackup depends on ESCluster, and is depended by ESSnapshot.
# Creating `esbackup/kustomize-backbup` will create the `kustomize-backup` snapshot repository.
# Deleting `esbackup/kustomize-backbup` will delete the `kustomize-backup` snapshot repository.
# Deleting `esbackup/kustomize-backbup` will NOT delete the snapshots in the `kustomize-backup` snapshot repository, instead makes all the snapshots in the repository inaccessible.
# Deleting `esbackup/kustomize-backbup` will NOT delete the essnapshot objects depending on it, but will cause those essnapshot objects to be reconciled, which update the status of the essnapshot objects to reflect the fact that the esbackup object is missing.
# If you delete the `kustomize-backup` snapshot repository directly without deleting `esbackup/kustomize-backbup`, the ESBackup object will not recreate the snapshot repository.
apiVersion: elasticsearch.cloud.google.com/v1alpha1
kind: ESBackup
metadata:
name: kustomize-backup
spec:
storage:
gcs:
# the bucket must exist for the snapshot respository to be created successfully.
bucket: kustomize-backup
# the path does not need to exist.
# If the path does not exist, the controller will create the folder in the GCS bucket.
# If the path already exists and includes snapshots, these snapshots can be used.
path: kustomize
secret:
name: kustomizesa
escluster:
name: esbasic

View File

@@ -1,4 +1,3 @@
# ESCluster is depended by ESBackup and ESRestore.
apiVersion: elasticsearch.cloud.google.com/v1alpha1 apiVersion: elasticsearch.cloud.google.com/v1alpha1
kind: ESCluster kind: ESCluster
metadata: metadata:
@@ -9,13 +8,6 @@ spec:
- repository-gcs - repository-gcs
- ingest-user-agent - ingest-user-agent
- ingest-geoip - ingest-geoip
# To set `gcpserviceaccount`,
# First, create and download a GCP service account into a json file, named `sakey.json` following the instruction:
# https://www.elastic.co/guide/en/elasticsearch/plugins/6.5/repository-gcs-usage.html#repository-gcs-using-service-account
# Second, create a secret for the service account using the following command:
# $ kubectl create secret generic kustomizesa --from-file=./sakey.json
gcpserviceaccount:
name: kustomizesa
config: config:
env: env:
example: test example: test

View File

@@ -1,19 +0,0 @@
# ESRestore depends on both ESCluster and ESSnapshot.
# Creating `esrestore/kustomize-restore` will restore the `kuostmize` index in the `kustomize-snapshot` snapshot to a new index named `kusotmize-restore`.
# Deleting `esrestore/kustomize-restore` will not delete the restored index.
# Deleting `esrestore/kustomize-restore` should happen before deleting `essnapshot/kustomize-snapshot`.
# After the restore is complete, if the `kusotmize-restore` index is deleted manually, the ESRestore object will NOT restore the `kustomize` index to it again.
# The correct way of using ESRestore is: create a ESRestore object to restore the index; delete the ESRestore object after the restore is complete.
apiVersion: elasticsearch.cloud.google.com/v1alpha1
kind: ESRestore
metadata:
name: kustomize-restore
spec:
include_global_state: true
ignore_unavailable: true
rename_pattern: kustomize
rename_replacement: kustomize-restore
essnapshot:
name: kustomize-snapshot
escluster:
name: esbasic

View File

@@ -1,23 +0,0 @@
# ESSnapshot depends on ESBackup, and is depended by ESRestore.
# Creating `essnapshot/kustomize-snapshot` will create a snapshot named `kustomize-snapshot` in the `kustomize-backup` snapshot repository.
# After being created, the `kustomize-snapshot` snapshot will not be automatically updated when the `kuostomize` index is updated.
# If you delete `essnapshot/kustomize-snapshot` and recreate it, the new snapshot will capture the current status of the `kustomize` index.
# Deleting `essnapshot/kustomize-snapshot` will delete the snapshot.
# Deleting `essnapshot/kustomize-snapshot` should happen before deleting `esbackup/kustomize-backup`.
# If the `kustomize-snapshot` snapshot is deleted directly without deleting `essnapshot/kustomize-snapshot`, the ESSnapshot object will recreate the snapshot.
# The correct way of using ESSnapshot is: create an ESSnapshot object to create a snapshot, keep the ESSnapshot object until the snapshot is no longer needed.
# To update the snapshot to capture the latest version of the index, you can either:
# 1) delete the snapshot, and wait for the ESSnapshot object to recreate the snapshot;
# 2) delete the ESSnapshot object, and recreate the ESSnapshot object.
apiVersion: elasticsearch.cloud.google.com/v1alpha1
kind: ESSnapshot
metadata:
name: kustomize-snapshot
spec:
# indices are optional. If not specified all indices are selected.
indices:
- kustomize
include_global_state: true
ignore_unavailable: true
esbackup:
name: kustomize-backup

View File

@@ -10,8 +10,6 @@ import (
"os" "os"
"sync" "sync"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/index" "sigs.k8s.io/kustomize/api/internal/crawl/index"
_ "github.com/gomodule/redigo/redis" _ "github.com/gomodule/redigo/redis"
@@ -31,15 +29,13 @@ type Crawler interface {
// Crawl returns when it is done processing. This method does not take // Crawl returns when it is done processing. This method does not take
// ownership of the channel. The channel is write only, and it // ownership of the channel. The channel is write only, and it
// designates where the crawler should forward the documents. // designates where the crawler should forward the documents.
Crawl(ctx context.Context, output chan<- CrawledDocument, seen utils.SeenMap) error Crawl(ctx context.Context, output chan<- CrawledDocument) error
// Get the document data given the FilePath, Repo, and Ref/Tag/Branch. // Get the document data given the FilePath, Repo, and Ref/Tag/Branch.
FetchDocument(context.Context, *doc.Document) error FetchDocument(context.Context, *doc.Document) error
// Write to the document what the created time is. // Write to the document what the created time is.
SetCreated(context.Context, *doc.Document) error SetCreated(context.Context, *doc.Document) error
SetDefaultBranch(*doc.Document)
Match(*doc.Document) bool Match(*doc.Document) bool
} }
@@ -47,12 +43,7 @@ type CrawledDocument interface {
ID() string ID() string
GetDocument() *doc.Document GetDocument() *doc.Document
// Get all the Documents directly referred in a Document. // Get all the Documents directly referred in a Document.
// For a Document representing a non-kustomization file, an empty slice will be returned. GetResources() ([]*doc.Document, error)
// 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 WasCached() bool
} }
@@ -78,27 +69,25 @@ func findMatch(d *doc.Document, crawlers []Crawler) Crawler {
} }
func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc, func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
seen utils.SeenMap, stack *CrawlSeed) { seen map[string]struct{}, stack *CrawlSeed) {
seen.Set(cdoc.ID(), cdoc.GetDocument().FileType) seen[cdoc.ID()] = struct{}{}
match.SetDefaultBranch(cdoc.GetDocument())
// Insert into index // Insert into index
if err := indx(cdoc, index.InsertOrUpdate); err != nil { if err := indx(cdoc, index.InsertOrUpdate); err != nil {
logger.Printf("Failed to insert or update doc(%s): %v", logger.Printf("Failed to insert or update %s %s: %v",
cdoc.GetDocument().Path(), err) cdoc.GetDocument().RepositoryURL, cdoc.GetDocument().FilePath, err)
return return
} }
deps, err := cdoc.GetResources(true, true, true) deps, err := cdoc.GetResources()
if err != nil { if err != nil {
logger.Println(err) logger.Println(err)
return return
} }
for _, dep := range deps { for _, dep := range deps {
if seen.Seen(dep.ID()) && seen.Value(dep.ID()) == dep.FileType { if _, ok := seen[dep.ID()]; ok {
continue continue
} }
*stack = append(*stack, dep) *stack = append(*stack, dep)
@@ -106,7 +95,7 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
} }
func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc, func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc,
seen utils.SeenMap, stack *CrawlSeed, refreshDoc bool, updateFileType bool) { seen map[string]struct{}, stack *CrawlSeed) {
UpdatedDocCount := 0 UpdatedDocCount := 0
seenDocCount := 0 seenDocCount := 0
@@ -116,7 +105,6 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
SetCreatedErrCount := 0 SetCreatedErrCount := 0
convErrCount := 0 convErrCount := 0
deleteDocCount := 0 deleteDocCount := 0
crawledDocCount := 0
// During the execution of the for loop, more Documents may be added into (*docsPtr). // During the execution of the for loop, more Documents may be added into (*docsPtr).
for len(*docsPtr) > 0 { for len(*docsPtr) > 0 {
@@ -126,19 +114,13 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
// remove the last Document in (*docPtr) // remove the last Document in (*docPtr)
*docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)] *docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)]
crawledDocCount++ if _, ok := seen[tail.ID()]; ok {
logger.Printf("Crawling doc %d: %s", crawledDocCount, tail.Path()) seenDocCount++
continue
if seen.Seen(tail.ID()) {
if !updateFileType || seen.Value(tail.ID()) == tail.FileType {
logger.Printf("this doc has been seen before")
seenDocCount++
continue
}
} }
if tail.WasCached() { if tail.WasCached() {
logger.Printf("doc(%s) is cached already", tail.Path()) logger.Printf("%s %s is cached already", tail.RepositoryURL, tail.FilePath)
cachedDocCount++ cachedDocCount++
continue continue
} }
@@ -150,52 +132,36 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
continue continue
} }
if tail.User == "" { logger.Println("Crawling ", tail.RepositoryURL, tail.FilePath)
tail.User = doc.UserName(tail.RepositoryURL) if err := match.FetchDocument(ctx, tail); err != nil {
} logger.Printf("FetchDocument failed on %s %s: %v",
tail.RepositoryURL, tail.FilePath, err)
// If the Document represents a kustomization root, FetchDcoument will change FetchDocumentErrCount++
// the `filePath` field of the Document by adding `kustomization.yaml` or // delete the document from the index
// `kustomization.yml` or `kustomization` into the the field. cdoc := &doc.KustomizationDocument{
// Therefore, it is necessary to add the ID of the Document into seen before Document: *tail,
// calling FetchDocument. Otherwise, the binary may enter into an infinite loop
// if a kustomization file points to its kustmozation root in its `resources` or
// `bases` field.
seen.Set(tail.ID(), tail.FileType)
if refreshDoc || tail.DefaultBranch == "" {
match.SetDefaultBranch(tail)
}
if refreshDoc || tail.DocumentData == "" {
if err := match.FetchDocument(ctx, tail); err != nil {
logger.Printf("FetchDocument failed on doc(%s): %v", tail.Path(), err)
FetchDocumentErrCount++
// delete the document from the index
cdoc := &doc.KustomizationDocument{
Document: *tail,
}
seen.Set(cdoc.ID(), tail.FileType)
if err := indx(cdoc, index.Delete); err != nil {
logger.Printf("Failed to delete doc(%s): %v", cdoc.Path(), err)
}
deleteDocCount++
continue
} }
seen[cdoc.ID()] = struct{}{}
if err := indx(cdoc, index.Delete); err != nil {
logger.Printf("Failed to delete %s %s: %v",
cdoc.RepositoryURL, cdoc.FilePath, err)
}
deleteDocCount++
continue
} }
if refreshDoc || tail.CreationTime == nil { if err := match.SetCreated(ctx, tail); err != nil {
if err := match.SetCreated(ctx, tail); err != nil { logger.Printf("SetCreated failed on %s %s: %v",
logger.Printf("SetCreated failed on doc(%s): %v", tail.Path(), err) tail.RepositoryURL, tail.FilePath, err)
SetCreatedErrCount++ SetCreatedErrCount++
}
} }
cdoc, err := conv(tail) cdoc, err := conv(tail)
// If conv returns an error, cdoc can still be added into the index so that // If conv returns an error, cdoc can still be added into the index so that
// cdoc.Document can be searched. // cdoc.Document can be searched.
if err != nil { if err != nil {
logger.Printf("conv failed on doc(%s): %v", tail.Path(), err) logger.Printf("conv failed on %s %s: %v",
tail.RepositoryURL, tail.FilePath, err)
convErrCount++ convErrCount++
} }
@@ -216,7 +182,7 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
// CrawlFromSeed updates all the documents in seed, and crawls all the new // CrawlFromSeed updates all the documents in seed, and crawls all the new
// documents referred in the seed. // documents referred in the seed.
func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler, func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
conv Converter, indx IndexFunc, seen utils.SeenMap) { conv Converter, indx IndexFunc, seen map[string]struct{}) {
// stack tracks the documents directly referred in other documents. // stack tracks the documents directly referred in other documents.
stack := make(CrawlSeed, 0) stack := make(CrawlSeed, 0)
@@ -224,14 +190,14 @@ func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
// Exploit seed to update bulk of corpus. // Exploit seed to update bulk of corpus.
logger.Printf("updating %d documents from seed\n", len(seed)) logger.Printf("updating %d documents from seed\n", len(seed))
// each unique document in seed will be crawled once. // each unique document in seed will be crawled once.
doCrawl(ctx, &seed, crawlers, conv, indx, seen, &stack, true, false) doCrawl(ctx, &seed, crawlers, conv, indx, seen, &stack)
// Traverse any new documents added while updating corpus. // Traverse any new documents added while updating corpus.
logger.Printf("crawling %d new documents found in the seed\n", len(stack)) logger.Printf("crawling %d new documents found in the seed\n", len(stack))
// While crawling each document in stack, the documents directly referred in the document // While crawling each document in stack, the documents directly referred in the document
// will be added into stack. // will be added into stack.
// After this statement is done, stack will become empty. // After this statement is done, stack will become empty.
doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack, false, true) doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack)
} }
// CrawlGithubRunner is a blocking function and only returns once all of the // CrawlGithubRunner is a blocking function and only returns once all of the
@@ -252,7 +218,7 @@ func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
// from the seed will be processed before any other documents from the // from the seed will be processed before any other documents from the
// crawlers. // crawlers.
func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument, func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
crawlers []Crawler, seen utils.SeenMap) []error { crawlers []Crawler) []error {
errs := make([]error, len(crawlers)) errs := make([]error, len(crawlers))
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
@@ -286,7 +252,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
} }
}() }()
defer close(docs) defer close(docs)
errs[idx] = crawler.Crawl(ctx, docs, seen) errs[idx] = crawler.Crawl(ctx, docs)
}(i, crawler, docs) // Copies the index and the crawler }(i, crawler, docs) // Copies the index and the crawler
} }
@@ -296,7 +262,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
// CrawlGithub crawls all the kustomization files on Github. // CrawlGithub crawls all the kustomization files on Github.
func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter, func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
indx IndexFunc, seen utils.SeenMap) { indx IndexFunc, seen map[string]struct{}) {
// stack tracks the documents directly referred in other documents. // stack tracks the documents directly referred in other documents.
stack := make(CrawlSeed, 0) stack := make(CrawlSeed, 0)
@@ -308,14 +274,8 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
docCount := 0
for cdoc := range ch { for cdoc := range ch {
docCount++ if _, ok := seen[cdoc.ID()]; ok {
logger.Printf("Processing doc %d found on Github", docCount)
// all the docs here are kustomization files found by querying Github, and
// their `FileType` fields all should be empty.
if seen.Seen(cdoc.ID()) {
logger.Printf("the doc has been seen before")
continue continue
} }
match := findMatch(cdoc.GetDocument(), crawlers) match := findMatch(cdoc.GetDocument(), crawlers)
@@ -329,7 +289,7 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
}() }()
logger.Println("processing the documents found from crawling github") logger.Println("processing the documents found from crawling github")
if errs := CrawlGithubRunner(ctx, ch, crawlers, seen); errs != nil { if errs := CrawlGithubRunner(ctx, ch, crawlers); errs != nil {
for _, err := range errs { for _, err := range errs {
logIfErr(err) logIfErr(err)
} }
@@ -340,5 +300,5 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
// Handle deps of newly discovered documents. // Handle deps of newly discovered documents.
logger.Printf("crawling the %d new documents referred by other documents", logger.Printf("crawling the %d new documents referred by other documents",
len(stack)) len(stack))
doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack, false, true) doCrawl(ctx, &stack, crawlers, conv, indx, seen, &stack)
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
@@ -12,8 +11,6 @@ import (
"testing" "testing"
"time" "time"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/index" "sigs.k8s.io/kustomize/api/internal/crawl/index"
"sigs.k8s.io/kustomize/api/internal/crawl/doc" "sigs.k8s.io/kustomize/api/internal/crawl/doc"
@@ -37,8 +34,6 @@ func (c testCrawler) Match(d *doc.Document) bool {
return d != nil return d != nil
} }
func (c testCrawler) SetDefaultBranch(d *doc.Document) {}
func (c testCrawler) FetchDocument(_ context.Context, d *doc.Document) error { func (c testCrawler) FetchDocument(_ context.Context, d *doc.Document) error {
if i, ok := c.lukp[d.ID()]; ok { if i, ok := c.lukp[d.ID()]; ok {
d.DocumentData = c.docs[i].DocumentData d.DocumentData = c.docs[i].DocumentData
@@ -80,7 +75,7 @@ func newCrawler(matchPrefix string, err error,
// Crawl implements the Crawler interface for testing. // Crawl implements the Crawler interface for testing.
func (c testCrawler) Crawl(_ context.Context, func (c testCrawler) Crawl(_ context.Context,
output chan<- CrawledDocument, _ utils.SeenMap) error { output chan<- CrawledDocument) error {
for i, d := range c.docs { for i, d := range c.docs {
isResource := true isResource := true
@@ -115,7 +110,7 @@ func (s sortableDocs) Len() int {
} }
func TestCrawlGithubRunner(t *testing.T) { func TestCrawlGithubRunner(t *testing.T) {
log.Println("testing CrawlGithubRunner") fmt.Println("testing CrawlGithubRunner")
tests := []struct { tests := []struct {
tc []Crawler tc []Crawler
errs []error errs []error
@@ -186,9 +181,8 @@ func TestCrawlGithubRunner(t *testing.T) {
defer close(output) defer close(output)
defer wg.Done() defer wg.Done()
seen := utils.NewSeenMap()
errs := CrawlGithubRunner(context.Background(), errs := CrawlGithubRunner(context.Background(),
output, test.tc, seen) output, test.tc)
// Check that errors are returned as they should be. // Check that errors are returned as they should be.
if !reflect.DeepEqual(errs, test.errs) { if !reflect.DeepEqual(errs, test.errs) {
@@ -221,7 +215,7 @@ func TestCrawlGithubRunner(t *testing.T) {
} }
func TestCrawlFromSeed(t *testing.T) { func TestCrawlFromSeed(t *testing.T) {
log.Println("testing CrawlFromSeed") fmt.Println("testing CrawlFromSeed")
tests := []struct { tests := []struct {
seed CrawlSeed seed CrawlSeed
@@ -328,7 +322,7 @@ resources:
visited[d.ID()]++ visited[d.ID()]++
return nil return nil
}, },
utils.NewSeenMap(), make(map[string]struct{}),
) )
if lv, lc := len(visited), len(tc.corpus); lv != lc { if lv, lc := len(visited), len(tc.corpus); lv != lc {
t.Errorf("error: %d of %d documents visited.", lv, lc) t.Errorf("error: %d of %d documents visited.", lv, lc)

View File

@@ -16,8 +16,6 @@ import (
"strings" "strings"
"time" "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"
"sigs.k8s.io/kustomize/api/internal/crawl/doc" "sigs.k8s.io/kustomize/api/internal/crawl/doc"
"sigs.k8s.io/kustomize/api/internal/crawl/httpclient" "sigs.k8s.io/kustomize/api/internal/crawl/httpclient"
@@ -32,8 +30,6 @@ var logger = log.New(os.Stdout, "Github Crawler: ",
type githubCrawler struct { type githubCrawler struct {
client GhClient client GhClient
query Query query Query
// branchMap maps github repositories to their default branches
branchMap map[string]string
} }
type GhClient struct { type GhClient struct {
@@ -55,60 +51,13 @@ func NewCrawler(accessToken string, retryCount uint64, client *http.Client,
}, },
accessToken: accessToken, accessToken: accessToken,
}, },
query: query, query: query,
branchMap: map[string]string{},
} }
} }
func (gc githubCrawler) SetDefaultBranch(d *doc.Document) {
url := gc.client.ReposRequest(d.RepositoryFullName())
defaultBranch, err := gc.client.GetDefaultBranch(url, d.RepositoryURL, gc.branchMap)
if err != nil {
logger.Printf(
"(error: %v) setting default_branch to master\n", err)
defaultBranch = "master"
}
d.DefaultBranch = defaultBranch
gc.branchMap[d.RepositoryURL] = d.DefaultBranch
}
func (gc githubCrawler) DefaultBranch(repo string) string {
return gc.branchMap[repo]
}
// Implements crawler.Crawler. // Implements crawler.Crawler.
func (gc githubCrawler) Crawl(ctx context.Context, func (gc githubCrawler) Crawl(
output chan<- crawler.CrawledDocument, seen utils.SeenMap) error { ctx context.Context, output chan<- crawler.CrawledDocument) error {
ranges := []RangeWithin{
RangeWithin{
start: uint64(0),
end: githubMaxFileSize,
},
}
errs := make(multiError, 0)
for len(ranges) > 0 {
tailRange := ranges[len(ranges) - 1]
ranges = ranges[:(len(ranges) - 1)]
reProcessQueryRanges, err := gc.CrawlSingleRange(ctx, output, seen, tailRange.start, tailRange.end)
if err != nil {
errs = append(errs, err)
}
ranges = append(ranges, reProcessQueryRanges...)
}
if len(errs) > 0 {
return errs
}
return nil
}
func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
output chan<- crawler.CrawledDocument, seen utils.SeenMap,
lowerBound, upperBound uint64) ([]RangeWithin, error) {
log.Printf("CrawlSingleRange [%d, %d]", lowerBound, upperBound)
noETagClient := GhClient{ noETagClient := GhClient{
RequestConfig: gc.client.RequestConfig, RequestConfig: gc.client.RequestConfig,
@@ -117,25 +66,12 @@ func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
accessToken: gc.client.accessToken, accessToken: gc.client.accessToken,
} }
var reProcessQueryRanges []RangeWithin
var ranges []string
var err error
// Since Github returns a max of 1000 results per query, we can use // Since Github returns a max of 1000 results per query, we can use
// multiple queries that split the search space into chunks of at most // multiple queries that split the search space into chunks of at most
// 1000 files to get all of the data. // 1000 files to get all of the data.
for i := 0; i < 5; i++ { ranges, err := FindRangesForRepoSearch(newCache(noETagClient, gc.query))
ranges, err = FindRangesForRepoSearch(newCache(noETagClient, gc.query),
lowerBound, upperBound)
if err == nil {
logger.Printf("FindRangesForRepoSearch succeeded after %d retries", i)
break
} else {
time.Sleep(time.Minute)
}
}
if err != nil { if err != nil {
return reProcessQueryRanges, fmt.Errorf("could not split %v into ranges, %v\n", return fmt.Errorf("could not split %v into ranges, %v\n",
gc.query, err) gc.query, err)
} }
@@ -143,25 +79,18 @@ func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
// Query each range for files. // Query each range for files.
errs := make(multiError, 0) errs := make(multiError, 0)
queryResult := RangeQueryResult{}
for _, query := range ranges { for _, query := range ranges {
reProcessQuery, rangeResult, err := processQuery(ctx, gc.client, query, output, seen, gc.branchMap) err := processQuery(ctx, gc.client, query, output)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
queryResult.Add(rangeResult)
if reProcessQuery {
reProcessQueryRanges = append(reProcessQueryRanges, RangeSizes(query))
}
} }
logger.Printf("Summary of Crawl: %s", queryResult.String())
if len(errs) > 0 { if len(errs) > 0 {
return reProcessQueryRanges, errs return errs
} }
return reProcessQueryRanges, nil return nil
} }
// FetchDocument first tries to fetch the document with d.FilePath. If it fails, // FetchDocument first tries to fetch the document with d.FilePath. If it fails,
@@ -178,13 +107,9 @@ func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error
"/" + repoSpec.Ref + "/" + repoSpec.Path "/" + repoSpec.Ref + "/" + repoSpec.Path
handle := func(resp *http.Response, err error, path string) error { handle := func(resp *http.Response, err error, path string) error {
if resp == nil {
return fmt.Errorf("empty http response (url: %s; path: %s), error: %v",
url, path, err)
}
if err == nil && resp.StatusCode == http.StatusOK { if err == nil && resp.StatusCode == http.StatusOK {
d.IsSame = httpclient.FromCache(resp.Header) d.IsSame = httpclient.FromCache(resp.Header)
defer CloseResponseBody(resp) defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
@@ -236,32 +161,10 @@ func (gc githubCrawler) Match(d *doc.Document) bool {
return strings.Contains(repoSpec.Host, "github.com") return strings.Contains(repoSpec.Host, "github.com")
} }
type RangeQueryResult struct {
totalDocCnt uint64
seenDocCnt uint64
newDocCnt uint64
errorCnt uint64
}
func (r *RangeQueryResult) Add(other RangeQueryResult) {
r.totalDocCnt += other.totalDocCnt
r.newDocCnt += other.newDocCnt
r.seenDocCnt += other.seenDocCnt
r.errorCnt += other.errorCnt
}
func (r *RangeQueryResult) String() string {
return fmt.Sprintf("got %d files from API. "+
"%d have been seen before. %d are new and sent to the output channel."+
" %d have kustomizationResultAdapter errors.",
r.totalDocCnt, r.seenDocCnt, r.newDocCnt, r.errorCnt)
}
// processQuery follows all of the pages in a query, and updates/adds the // processQuery follows all of the pages in a query, and updates/adds the
// documents from the crawl to the datastore/index. // documents from the crawl to the datastore/index.
func processQuery(ctx context.Context, gcl GhClient, query string, func processQuery(ctx context.Context, gcl GhClient, query string,
output chan<- crawler.CrawledDocument, seen utils.SeenMap, output chan<- crawler.CrawledDocument) error {
branchMap map[string]string) (bool, RangeQueryResult, error) {
queryPages := make(chan GhResponseInfo) queryPages := make(chan GhResponseInfo)
@@ -277,84 +180,57 @@ func processQuery(ctx context.Context, gcl GhClient, query string,
close(queryPages) close(queryPages)
}() }()
reProcessQuery := false
errs := make(multiError, 0) errs := make(multiError, 0)
result := RangeQueryResult{} errorCnt := 0
pageID := 1 totalCnt := 0
for page := range queryPages { for page := range queryPages {
if page.Error != nil { if page.Error != nil {
errs = append(errs, page.Error) errs = append(errs, page.Error)
continue continue
} }
pageResult := RangeQueryResult{}
for _, file := range page.Parsed.Items { for _, file := range page.Parsed.Items {
k, err := kustomizationResultAdapter(gcl, file, seen, branchMap) k, err := kustomizationResultAdapter(gcl, file)
if err != nil { if err != nil {
logger.Printf("kustomizationResultAdapter failed: %v", err) logger.Printf("kustomizationResultAdapter failed: %v", err)
errs = append(errs, err) errs = append(errs, err)
pageResult.errorCnt++ errorCnt++
} }
if k != nil { if k != nil {
pageResult.newDocCnt++
output <- k output <- k
} else {
pageResult.seenDocCnt++
} }
pageResult.totalDocCnt++ totalCnt++
} }
logger.Printf("processQuery [TotalCount %d - page %d]: %s", logger.Printf("got %d files out of %d from API. %d of %d had errors\n",
page.Parsed.TotalCount, pageID, pageResult.String()) totalCnt, page.Parsed.TotalCount, errorCnt, totalCnt)
result.Add(pageResult)
pageID++
if page.Parsed.TotalCount > githubMaxResultsPerQuery {
reProcessQuery = true
}
} }
logger.Printf("Summary of processQuery: %s", result.String()) return errs
return reProcessQuery, result, errs
} }
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen utils.SeenMap, func kustomizationResultAdapter(gcl GhClient, k GhFileSpec) (
branchMap map[string]string) (crawler.CrawledDocument, error) { crawler.CrawledDocument, error) {
url := gcl.ReposRequest(k.Repository.FullName)
defaultBranch, err := gcl.GetDefaultBranch(url, k.Repository.URL, branchMap)
if err != nil {
logger.Printf(
"(error: %v) setting default_branch to master\n", err)
defaultBranch = "master"
}
// document here is a kustomization file found by querying Github, whose
// `FileType` field should be empty.
document := doc.Document{
FilePath: k.Path,
DefaultBranch: defaultBranch,
RepositoryURL: k.Repository.URL,
User: doc.UserName(k.Repository.URL),
}
if seen.Seen(document.ID()) {
return nil, nil
}
data, err := gcl.GetFileData(k) data, err := gcl.GetFileData(k)
if err != nil { if err != nil {
return nil, err return nil, err
} }
url := gcl.ReposRequest(k.Repository.FullName)
defaultBranch, err := gcl.GetDefaultBranch(url)
if err != nil {
logger.Printf(
"(error: %v) setting default_branch to master\n", err)
defaultBranch = "master"
}
d := doc.KustomizationDocument{ d := doc.KustomizationDocument{
Document: doc.Document{ Document: doc.Document{
DocumentData: string(data), DocumentData: string(data),
FilePath: k.Path, FilePath: k.Path,
DefaultBranch: defaultBranch, DefaultBranch: defaultBranch,
RepositoryURL: k.Repository.URL, RepositoryURL: k.Repository.URL,
User: doc.UserName(k.Repository.URL),
}, },
} }
creationTime, err := gcl.GetFileCreationTime(k) creationTime, err := gcl.GetFileCreationTime(k)
@@ -379,7 +255,7 @@ func (gcl GhClient) ForwardPaginatedQuery(ctx context.Context, query string,
output chan<- GhResponseInfo) error { output chan<- GhResponseInfo) error {
logger.Println("querying: ", query) logger.Println("querying: ", query)
response := gcl.parseGithubResponseWithRetry(query) response := gcl.parseGithubResponse(query)
if response.Error != nil { if response.Error != nil {
return response.Error return response.Error
@@ -392,7 +268,7 @@ func (gcl GhClient) ForwardPaginatedQuery(ctx context.Context, query string,
case <-ctx.Done(): case <-ctx.Done():
return nil return nil
default: default:
response = gcl.parseGithubResponseWithRetry(response.NextURL) response = gcl.parseGithubResponse(response.NextURL)
if response.Error != nil { if response.Error != nil {
return response.Error return response.Error
} }
@@ -420,10 +296,7 @@ func (gcl GhClient) GetFileData(k GhFileSpec) ([]byte, error) {
return nil, fmt.Errorf("%+v: could not read '%s' metadata: %v", return nil, fmt.Errorf("%+v: could not read '%s' metadata: %v",
k, url, err) k, url, err)
} }
resp.Body.Close()
if err := resp.Body.Close(); err != nil {
return nil, err
}
type githubContentRawURL struct { type githubContentRawURL struct {
DownloadURL string `json:"download_url,omitempty"` DownloadURL string `json:"download_url,omitempty"`
@@ -442,32 +315,18 @@ func (gcl GhClient) GetFileData(k GhFileSpec) ([]byte, error) {
k, rawURL.DownloadURL, err) k, rawURL.DownloadURL, err)
} }
defer CloseResponseBody(resp) defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body) data, err = ioutil.ReadAll(resp.Body)
return data, err return data, err
} }
func CloseResponseBody(resp *http.Response) { func (gcl GhClient) GetDefaultBranch(url string) (string, error) {
if err := resp.Body.Close(); err != nil {
log.Printf("failed to close response body: %v", err)
}
}
// GetDefaultBranch gets the default branch of a github repository.
// m is a map which maps a github repository to its default branch.
// If repo is already in m, the default branch for url will be obtained from m;
// otherwise, a query will be made to github to obtain the default branch.
func (gcl GhClient) GetDefaultBranch(url, repo string, m map[string]string) (string, error) {
if v, ok := m[repo]; ok {
return v, nil
}
resp, err := gcl.GetReposData(url) resp, err := gcl.GetReposData(url)
if err != nil { if err != nil {
return "", fmt.Errorf( return "", fmt.Errorf(
"'%s' could not get default_branch: %v", url, err) "'%s' could not get default_branch: %v", url, err)
} }
defer CloseResponseBody(resp) defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", fmt.Errorf( return "", fmt.Errorf(
@@ -519,7 +378,7 @@ func (gcl GhClient) GetFileCreationTime(
} }
} }
defer CloseResponseBody(resp) defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return defaultTime, fmt.Errorf( return defaultTime, fmt.Errorf(
@@ -587,8 +446,6 @@ type githubResponse struct {
// This is the number of files that match the query. // This is the number of files that match the query.
TotalCount uint64 `json:"total_count,omitempty"` TotalCount uint64 `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
// Github representation of a file. // Github representation of a file.
Items []GhFileSpec `json:"items,omitempty"` Items []GhFileSpec `json:"items,omitempty"`
} }
@@ -631,17 +488,6 @@ func parseGithubLinkFormat(links string) (string, string) {
return next, last return next, last
} }
func (gcl GhClient) parseGithubResponseWithRetry(getRequest string) GhResponseInfo {
resp := gcl.parseGithubResponse(getRequest)
retries := 0
for resp.Parsed.IncompleteResults {
resp = gcl.parseGithubResponse(getRequest)
retries++
}
log.Printf("The result of query(%s) is complete after %d retries", getRequest, retries)
return resp
}
func (gcl GhClient) parseGithubResponse(getRequest string) GhResponseInfo { func (gcl GhClient) parseGithubResponse(getRequest string) GhResponseInfo {
resp, err := gcl.SearchGithubAPI(getRequest) resp, err := gcl.SearchGithubAPI(getRequest)
requestInfo := GhResponseInfo{ requestInfo := GhResponseInfo{
@@ -655,7 +501,7 @@ func (gcl GhClient) parseGithubResponse(getRequest string) GhResponseInfo {
} }
var data []byte var data []byte
defer CloseResponseBody(resp) defer resp.Body.Close()
data, requestInfo.Error = ioutil.ReadAll(resp.Body) data, requestInfo.Error = ioutil.ReadAll(resp.Body)
if requestInfo.Error != nil { if requestInfo.Error != nil {
return requestInfo return requestInfo
@@ -719,8 +565,8 @@ func (gcl GhClient) Do(query string) (*http.Response, error) {
// gcl.client.Do: a non-2xx status code doesn't cause an error. // gcl.client.Do: a non-2xx status code doesn't cause an error.
// See https://golang.org/pkg/net/http/#Client.Do for more info. // See https://golang.org/pkg/net/http/#Client.Do for more info.
resp, err := gcl.client.Do(req) resp, err := gcl.client.Do(req)
if resp != nil && resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("GhClient.Do(%s) failed with response code: %d", err = fmt.Errorf("GhClient.Do(%s) failed with response code: %d",
query, resp.StatusCode) query, resp.StatusCode)
} }
@@ -734,7 +580,7 @@ func (gcl GhClient) getWithRetry(
retryCount := gcl.retryCount retryCount := gcl.retryCount
for resp != nil && resp.StatusCode == http.StatusForbidden && retryCount > 0 { for resp.StatusCode == http.StatusForbidden && retryCount > 0 {
retryTime := resp.Header.Get("Retry-After") retryTime := resp.Header.Get("Retry-After")
i, errAtoi := strconv.Atoi(retryTime) i, errAtoi := strconv.Atoi(retryTime)
if errAtoi != nil { if errAtoi != nil {

View File

@@ -116,11 +116,9 @@ type RequestConfig struct {
// the URL method to get the string value of the URL. See request.CopyWith, to // the URL method to get the string value of the URL. See request.CopyWith, to
// understand why the request object is useful. // understand why the request object is useful.
func (rc RequestConfig) CodeSearchRequestWith(query Query) request { func (rc RequestConfig) CodeSearchRequestWith(query Query) request {
vals := url.Values{ req := rc.makeRequest("search/code", query)
"sort": []string{"indexed"}, req.vals.Set("sort", "indexed")
"order": []string{"desc"}, req.vals.Set("order", "desc")
}
req := rc.makeRequest("search/code", query, vals)
return req return req
} }
@@ -128,25 +126,27 @@ func (rc RequestConfig) CodeSearchRequestWith(query Query) request {
// query for the Github API to find the dowload information of this filepath. // query for the Github API to find the dowload information of this filepath.
func (rc RequestConfig) ContentsRequest(fullRepoName, path string) string { func (rc RequestConfig) ContentsRequest(fullRepoName, path string) string {
uri := fmt.Sprintf("repos/%s/contents/%s", fullRepoName, path) uri := fmt.Sprintf("repos/%s/contents/%s", fullRepoName, path)
return rc.makeRequest(uri, Query{}, url.Values{}).URL() return rc.makeRequest(uri, Query{}).URL()
} }
func (rc RequestConfig) ReposRequest(fullRepoName string) string { func (rc RequestConfig) ReposRequest(fullRepoName string) string {
uri := fmt.Sprintf("repos/%s", fullRepoName) uri := fmt.Sprintf("repos/%s", fullRepoName)
return rc.makeRequest(uri, Query{}, url.Values{}).URL() return rc.makeRequest(uri, Query{}).URL()
}
func escapeSpace(s string) string {
return strings.Replace(s, " ", "%20", -1)
} }
// CommitsRequest given the repo name, and a filepath returns a formatted query // CommitsRequest given the repo name, and a filepath returns a formatted query
// for the Github API to find the commits that affect this file. // for the Github API to find the commits that affect this file.
func (rc RequestConfig) CommitsRequest(fullRepoName, path string) string { func (rc RequestConfig) CommitsRequest(fullRepoName, path string) string {
uri := fmt.Sprintf("repos/%s/commits", fullRepoName) uri := fmt.Sprintf("repos/%s/commits", fullRepoName)
vals := url.Values{ return rc.makeRequest(uri, Query{Path(escapeSpace(path))}).URL()
"path": []string{path},
}
return rc.makeRequest(uri, Query{}, vals).URL()
} }
func (rc RequestConfig) makeRequest(path string, query Query, vals url.Values) request { func (rc RequestConfig) makeRequest(path string, query Query) request {
vals := url.Values{}
vals.Set(perPageArg, fmt.Sprint(rc.perPage)) vals.Set(perPageArg, fmt.Sprint(rc.perPage))
return request{ return request{
@@ -166,7 +166,7 @@ type request struct {
query Query query Query
} }
// CopyWith copies the requests and adds the extra query parameters. It is useful // CopyWith copies the requests and adds the extra query parameters. Usefull
// for dynamically adding sizes to a filename only query without modifying it. // for dynamically adding sizes to a filename only query without modifying it.
func (r request) CopyWith(queryParams ...queryField) request { func (r request) CopyWith(queryParams ...queryField) request {
cpy := r cpy := r

View File

@@ -101,7 +101,7 @@ func TestGithubSearchQuery(t *testing.T) {
"examples/helloWorld/kustomization.yaml?per_page=100", "examples/helloWorld/kustomization.yaml?per_page=100",
expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" + expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" +
"path=examples%2FhelloWorld%2Fkustomization.yaml&per_page=100", "q=path:examples/helloWorld/kustomization.yaml&per_page=100",
}, },
{ {
rc: RequestConfig{ rc: RequestConfig{
@@ -121,7 +121,7 @@ func TestGithubSearchQuery(t *testing.T) {
"examples%201/helloWorld/kustomization.yaml?per_page=100", "examples%201/helloWorld/kustomization.yaml?per_page=100",
expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" + expectedCommitsQuery: "https://api.github.com/repos/kubernetes-sigs/kustomize/commits?" +
"path=examples+1%2FhelloWorld%2Fkustomization.yaml&per_page=100", "q=path:examples%201/helloWorld/kustomization.yaml&per_page=100",
}, },
} }

View File

@@ -93,15 +93,13 @@ package github
// apiCallsPerResult * 10(pages) * 100(resultsPerPage) * totalResults / 1000 // apiCallsPerResult * 10(pages) * 100(resultsPerPage) * totalResults / 1000
// = apiCallsPerResult * totalResults. // = apiCallsPerResult * totalResults.
// //
// So it could very well take apiCallsPerResult * 50 times longer to actually // So it could very well take apiCallsPerResult * 50 times longer to acutally
// fetch the results (assuming the quotas for the API calls are the same as the // fetch the results (assuming the quotas for the API calls are the same as the
// search API), than it does to perform these range searches. // search API), than it does to perform these range searches.
import ( import (
"fmt" "fmt"
"math/bits" "math/bits"
"strconv"
"strings"
) )
// Files cannot be more than 2^19 bytes, according to // Files cannot be more than 2^19 bytes, according to
@@ -114,7 +112,7 @@ const (
// Interface instead of struct for testing purposes. // Interface instead of struct for testing purposes.
// Not expecting to have multiple implementations. // Not expecting to have multiple implementations.
type cachedSearch interface { type cachedSearch interface {
CountResults(uint64, uint64) (uint64, error) CountResults(uint64) (uint64, error)
RequestString(filesize rangeFormatter) string RequestString(filesize rangeFormatter) string
} }
@@ -141,7 +139,7 @@ type cachedSearch interface {
// problematic). The current cache implementation looks at the // problematic). The current cache implementation looks at the
// predecessor entry to find out if the current value is monotonic. // predecessor entry to find out if the current value is monotonic.
// This is where the bit trick is used, since each step in the binary // This is where the bit trick is used, since each step in the binary
// search is adding or omitting to add a decreasing power of 2 to the query // search is adding or ommiting to add a decreasing power of 2 to the query
// value, we can remove the least significant set bit to find the // value, we can remove the least significant set bit to find the
// predecessor in constant time. Ultimately since the search is rate // predecessor in constant time. Ultimately since the search is rate
// limited, we could also easily afford to compute this in linear time // limited, we could also easily afford to compute this in linear time
@@ -163,16 +161,16 @@ func newCache(client GhClient, query Query) githubCachedSearch {
} }
} }
func (c githubCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64, error) { func (c githubCachedSearch) CountResults(upperBound uint64) (uint64, error) {
count, cached := c.cache[upperBound] count, cached := c.cache[upperBound]
if cached { if cached {
return count, nil return count, nil
} }
sizeRange := RangeWithin{lowerBound, upperBound} sizeRange := RangeWithin{0, upperBound}
rangeRequest := c.RequestString(sizeRange) rangeRequest := c.RequestString(sizeRange)
result := c.gcl.parseGithubResponseWithRetry(rangeRequest) result := c.gcl.parseGithubResponse(rangeRequest)
if result.Error != nil { if result.Error != nil {
return count, result.Error return count, result.Error
} }
@@ -206,7 +204,7 @@ func (c githubCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64,
"Retrying query... current lower bound: %d, got: %d\n", "Retrying query... current lower bound: %d, got: %d\n",
c.cache[prev], result.Parsed.TotalCount) c.cache[prev], result.Parsed.TotalCount)
result = c.gcl.parseGithubResponseWithRetry(rangeRequest) result = c.gcl.parseGithubResponse(rangeRequest)
if result.Error != nil { if result.Error != nil {
return count, result.Error return count, result.Error
} }
@@ -221,8 +219,8 @@ func (c githubCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64,
} }
count = result.Parsed.TotalCount count = result.Parsed.TotalCount
logger.Printf("Caching new query %s, with count %d (incomplete_results: %v)\n", logger.Printf("Caching new query %s, with count %d\n",
sizeRange.RangeString(), count, result.Parsed.IncompleteResults) sizeRange.RangeString(), count)
c.cache[upperBound] = count c.cache[upperBound] = count
return count, nil return count, nil
} }
@@ -240,8 +238,8 @@ func (c githubCachedSearch) RequestString(filesize rangeFormatter) string {
// This would mean that the search as it is could not find all files. If queries // This would mean that the search as it is could not find all files. If queries
// are sorted by last indexed, and retrieved on regular intervals, it should be // are sorted by last indexed, and retrieved on regular intervals, it should be
// sufficient to get most if not all documents. // sufficient to get most if not all documents.
func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64) ([]string, error) { func FindRangesForRepoSearch(cache cachedSearch) ([]string, error) {
totalFiles, err := cache.CountResults(lowerBound, upperBound) totalFiles, err := cache.CountResults(githubMaxFileSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -249,7 +247,7 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
if githubMaxResultsPerQuery >= totalFiles { if githubMaxResultsPerQuery >= totalFiles {
return []string{ return []string{
cache.RequestString(RangeWithin{lowerBound, upperBound}), cache.RequestString(RangeWithin{0, githubMaxFileSize}),
}, nil }, nil
} }
@@ -277,7 +275,6 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
// range. // range.
filesAccessible := uint64(0) filesAccessible := uint64(0)
sizes := make([]uint64, 0) sizes := make([]uint64, 0)
sizes = append(sizes, lowerBound)
for filesAccessible < totalFiles { for filesAccessible < totalFiles {
target := filesAccessible + githubMaxResultsPerQuery target := filesAccessible + githubMaxResultsPerQuery
if target >= totalFiles { if target >= totalFiles {
@@ -287,22 +284,22 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
logger.Printf("%d accessible files, next target = %d\n", logger.Printf("%d accessible files, next target = %d\n",
filesAccessible, target) filesAccessible, target)
size, err := FindFileSize(cache, target, lowerBound, upperBound) cur, err := lowerBoundFileCount(cache, target)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If there are more than 1000 files in the next bucket, we must // If there are more than 1000 files in the next bucket, we must
// advance anyway and lose out on some files :(. // advance anyway and lose out on some files :(.
if l := len(sizes); l > 0 && sizes[l-1] == size { if l := len(sizes); l > 0 && sizes[l-1] == cur {
size++ cur++
} }
nextAccessible, err := cache.CountResults(lowerBound, size) nextAccessible, err := cache.CountResults(cur)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"cache should be populated at %d already, got %v", "cache should be populated at %d already, got %v",
size, err) cur, err)
} }
if nextAccessible < filesAccessible { if nextAccessible < filesAccessible {
return nil, fmt.Errorf( return nil, fmt.Errorf(
@@ -312,31 +309,31 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
filesAccessible = nextAccessible filesAccessible = nextAccessible
if nextAccessible < totalFiles { if nextAccessible < totalFiles {
sizes = append(sizes, size) sizes = append(sizes, cur)
} }
} }
sizes = append(sizes, upperBound)
return formatFilesizeRanges(cache, sizes), nil return formatFilesizeRanges(cache, sizes), nil
} }
// FindFileSize finds the filesize range from [lowerBound, return value] that has // lowerBoundFileCount finds the filesize range from [0, return value] that has
// the largest file count that is smaller than or equal to // the largest file count that is smaller than or equal to
// githubMaxResultsPerQuery. It is important to note that this returned value // githubMaxResultsPerQuery. It is important to note that this returned value
// could already be in a previous range if the next file size has more than 1000 // could already be in a previous range if the next file size has more than 1000
// results. It is left to the caller to handle this bit of logic and guarantee // results. It is left to the caller to handle this bit of logic and guarantee
// forward progession in this case. // forward progession in this case.
func FindFileSize( func lowerBoundFileCount(
cache cachedSearch, targetFileCount, lowerBound, upperBound uint64) (uint64, error) { cache cachedSearch, targetFileCount uint64) (uint64, error) {
// Binary search for file sizes that make up the next <=1000 element // Binary search for file sizes that make up the next <=1000 element
// chunk. // chunk.
cur := lowerBound cur := uint64(0)
increase := (upperBound - lowerBound) / 2 increase := githubMaxFileSize / 2
for increase > 0 { for increase > 0 {
mid := cur + increase mid := cur + increase
count, err := cache.CountResults(lowerBound, mid) count, err := cache.CountResults(mid)
if err != nil { if err != nil {
return count, err return count, err
} }
@@ -356,24 +353,26 @@ func FindFileSize(
} }
func formatFilesizeRanges(cache cachedSearch, sizes []uint64) []string { func formatFilesizeRanges(cache cachedSearch, sizes []uint64) []string {
n := len(sizes) ranges := make([]string, 0, len(sizes)+1)
if n < 2 {
return []string{} if len(sizes) > 0 {
ranges = append(ranges, cache.RequestString(
RangeLessThan{sizes[0] + 1},
))
} }
ranges := make([]string, 0, n-1) for i := 0; i < len(sizes)-1; i += 1 {
ranges = append(ranges, cache.RequestString(RangeWithin{sizes[0], sizes[1]})) ranges = append(ranges, cache.RequestString(
for i := 1; i < n-1; i++ { RangeWithin{sizes[i] + 1, sizes[i+1]},
ranges = append(ranges, cache.RequestString(RangeWithin{sizes[i] + 1, sizes[i+1]})) ))
if i != len(sizes)-2 {
continue
}
ranges = append(ranges, cache.RequestString(
RangeGreaterThan{sizes[i+1]},
))
} }
return ranges return ranges
} }
func RangeSizes(s string) RangeWithin {
start := strings.Index(s, "+size:") + len("+size:")
end := strings.Index(s, "&")
ranges := strings.Split(s[start:end], "..")
lowerBound, _ := strconv.ParseUint(ranges[0], 10, 64)
upperBound, _ := strconv.ParseUint(ranges[1], 10, 64)
return RangeWithin{lowerBound, upperBound}
}

View File

@@ -2,7 +2,6 @@ package github
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"testing" "testing"
) )
@@ -11,8 +10,8 @@ type testCachedSearch struct {
cache map[uint64]uint64 cache map[uint64]uint64
} }
func (c testCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64, error) { func (c testCachedSearch) CountResults(upperBound uint64) (uint64, error) {
log.Printf("CountResults(%05x)\n", upperBound) fmt.Printf("CountResults(%05x)\n", upperBound)
count, ok := c.cache[upperBound] count, ok := c.cache[upperBound]
if !ok { if !ok {
return count, fmt.Errorf("cache not set at %x", upperBound) return count, fmt.Errorf("cache not set at %x", upperBound)
@@ -73,29 +72,19 @@ func TestRangeSplitting(t *testing.T) {
}, },
} }
requests, err := FindRangesForRepoSearch(cache, 0, 524288) requests, err := FindRangesForRepoSearch(cache)
if err != nil { if err != nil {
t.Errorf("Error while finding ranges: %v", err) t.Errorf("Error while finding ranges: %v", err)
} }
expected := []string{ expected := []string{
"0..106", // cache.RequestString(RangeWithin{0x00, 0x6a}), "<107", // cache.RequestString(RangeLessThan{0x6b}),
"107..128", // cache.RequestString(RangeWithin{0x6b, 0x80}), "107..128", // cache.RequestString(RangeWithin{0x6b, 0x80}),
"129..256", // cache.RequestString(RangeWithin{0x81, 0x100}), "129..256", // cache.RequestString(RangeWithin{0x81, 0x100}),
"257..4095", // cache.RequestString(RangeWithin{0x101, 0xfff}), "257..4095", // cache.RequestString(RangeWithin{0x101, 0xfff}),
"4096..524288", // cache.RequestString(RangeWithin{0x1000, 0x80000}), ">4095", // cache.RequestString(RangeGreaterThan{0xfff}),
} }
if !reflect.DeepEqual(requests, expected) { if !reflect.DeepEqual(requests, expected) {
t.Errorf("Expected requests (%v) to equal (%v)", requests, expected) t.Errorf("Expected requests (%v) to equal (%v)", requests, expected)
} }
} }
func TestRangeSizes(t *testing.T) {
s := "https://api.github.com/search/code?q=filename:kustomization.yaml+filename:kustomization.yml" +
"+filename:kustomization+size:2365..10000&order=desc&per_page=100&sort=indexed"
returnedResult := RangeSizes(s)
expectedResult := RangeWithin{uint64(2365), uint64(10000)}
if !reflect.DeepEqual(returnedResult, expectedResult) {
t.Errorf("RangeSizes expected (%v), got (%v)",expectedResult, returnedResult)
}
}

View File

@@ -2,8 +2,6 @@ package doc
import ( import (
"fmt" "fmt"
"log"
"path/filepath"
"sort" "sort"
"strings" "strings"
@@ -46,36 +44,21 @@ type KustomizationDocument struct {
type set map[string]struct{} type set map[string]struct{}
func (doc *KustomizationDocument) Copy() *KustomizationDocument {
return &KustomizationDocument{
Document: *(doc.Document.Copy()),
Kinds: doc.Kinds,
Identifiers: doc.Identifiers,
Values: doc.Values,
}
}
func (doc *KustomizationDocument) String() string { func (doc *KustomizationDocument) String() string {
return fmt.Sprintf("%s %s %s %v %v %v len(identifiers):%v len(values):%v", return fmt.Sprintf("%s %s %s %v %v %v len(identifiers):%v len(values):%v",
doc.RepositoryURL, doc.FilePath, doc.DefaultBranch, doc.CreationTime, doc.RepositoryURL, doc.FilePath, doc.DefaultBranch, doc.CreationTime,
doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values)) doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values))
} }
// IsKustomizationFile determines whether a file path is a kustomization file // Implements the CrawlerDocument interface.
func IsKustomizationFile(path string) bool { func (doc *KustomizationDocument) GetResources() ([]*Document, error) {
basename := filepath.Base(path) isResource := true
for _, name := range konfig.RecognizedKustomizationFileNames() { for _, suffix := range konfig.RecognizedKustomizationFileNames() {
if basename == name { if strings.HasSuffix(doc.FilePath, "/"+suffix) {
return true isResource = false
} }
} }
return false if isResource {
}
// Implements the CrawlerDocument interface.
func (doc *KustomizationDocument) GetResources(
includeResources, includeTransformers, includeGenerators bool) ([]*Document, error) {
if !IsKustomizationFile(doc.FilePath) {
return []*Document{}, nil return []*Document{}, nil
} }
@@ -93,46 +76,19 @@ func (doc *KustomizationDocument) GetResources(
} }
k.FixKustomizationPostUnmarshalling() k.FixKustomizationPostUnmarshalling()
res := make([]*Document, 0) res := make([]*Document, 0, len(k.Resources))
for _, r := range k.Resources {
if includeResources { next, err := doc.Document.FromRelativePath(r)
resourceDocs := doc.CollectDocuments(k.Resources, "resource") if err != nil {
res = append(res, resourceDocs...) fmt.Printf("GetResources error: %v\n", err)
} continue
}
if includeGenerators { res = append(res, &next)
generatorDocs := doc.CollectDocuments(k.Generators, "generator")
res = append(res, generatorDocs...)
}
if includeTransformers {
transformerDocs := doc.CollectDocuments(k.Transformers, "transformer")
res = append(res, transformerDocs...)
} }
return res, nil 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, fileType 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("CollectDocuments error: %v\n", err)
continue
}
next.FileType = fileType
docs = append(docs, &next)
}
return docs
}
func (doc *KustomizationDocument) readBytes() ([]map[string]interface{}, error) { func (doc *KustomizationDocument) readBytes() ([]map[string]interface{}, error) {
data := []byte(doc.DocumentData) data := []byte(doc.DocumentData)

View File

@@ -189,13 +189,11 @@ metadata:
} }
} }
type TestStructForGetResources struct {
doc KustomizationDocument
resources []*Document
}
func TestGetResources(t *testing.T) { func TestGetResources(t *testing.T) {
tests := []TestStructForGetResources{ tests := []struct {
doc KustomizationDocument
resources []*Document
}{
{ {
doc: KustomizationDocument{ doc: KustomizationDocument{
Document: Document{ Document: Document{
@@ -215,27 +213,19 @@ resources:
{ {
RepositoryURL: "sigs.k8s.io/kustomize", RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/base", FilePath: "some/path/to/base",
FileType: "resource",
User: "sigs.k8s.io",
}, },
{ {
RepositoryURL: "sigs.k8s.io/kustomize", RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/otherbase", FilePath: "some/path/to/otherbase",
FileType: "resource",
User: "sigs.k8s.io",
}, },
{ {
RepositoryURL: "sigs.k8s.io/kustomize", RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/kdir/file.yaml", FilePath: "some/path/to/kdir/file.yaml",
FileType: "resource",
User: "sigs.k8s.io",
}, },
{ {
RepositoryURL: "https://github.com/kubernetes-sigs/kustomize", RepositoryURL: "https://github.com/kubernetes-sigs/kustomize",
FilePath: "examples/helloWorld", FilePath: "examples/helloWorld",
DefaultBranch: "v3.1.0", DefaultBranch: "v3.1.0",
FileType: "resource",
User: "kubernetes-sigs",
}, },
}, },
}, },
@@ -258,12 +248,9 @@ resources:
resources: []*Document{}, resources: []*Document{},
}, },
} }
runTest(t, tests, true, false, false)
}
func runTest(t *testing.T, tests []TestStructForGetResources, includeResources, includeTransformers, includeGenerators bool) {
for _, test := range tests { for _, test := range tests {
res, err := test.doc.GetResources(includeResources, includeTransformers, includeGenerators) res, err := test.doc.GetResources()
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v\n", err) t.Errorf("Unexpected error: %v\n", err)
continue continue
@@ -297,83 +284,3 @@ func runTest(t *testing.T, tests []TestStructForGetResources, includeResources,
} }
} }
} }
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",
FileType: "generator",
User: "sigs.k8s.io",
},
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/kdir/file.yaml",
FileType: "resource",
User: "sigs.k8s.io",
},
},
},
}
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",
FileType: "transformer",
User: "sigs.k8s.io",
},
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/kdir/gen.yaml",
FileType: "generator",
User: "sigs.k8s.io",
},
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/kdir/file.yaml",
FileType: "resource",
User: "sigs.k8s.io",
},
},
},
}
runTest(t, tests, true, true, true)
}

View File

@@ -11,18 +11,12 @@ import (
) )
type Document struct { type Document struct {
RepositoryURL string `json:"repositoryUrl,omitempty"` RepositoryURL string `json:"repositoryUrl,omitempty"`
// User makes it easy to aggregate data in the user level instead
// of the repository level
User string `json:"user,omitempty"`
FilePath string `json:"filePath,omitempty"` FilePath string `json:"filePath,omitempty"`
DefaultBranch string `json:"defaultBranch,omitempty"` DefaultBranch string `json:"defaultBranch,omitempty"`
DocumentData string `json:"document,omitempty"` DocumentData string `json:"document,omitempty"`
CreationTime *time.Time `json:"creationTime,omitempty"` CreationTime *time.Time `json:"creationTime,omitempty"`
IsSame bool `json:"-"` IsSame bool `json:"-"`
// FileType can be one of the following:
// "generator", "transformer", "resource", "".
FileType string `json:"fileType,omitempty"`
} }
// Implements the CrawlerDocument interface. // Implements the CrawlerDocument interface.
@@ -33,21 +27,14 @@ func (doc *Document) GetDocument() *Document {
func (doc *Document) Copy() *Document { func (doc *Document) Copy() *Document {
return &Document{ return &Document{
RepositoryURL: doc.RepositoryURL, RepositoryURL: doc.RepositoryURL,
User: doc.User,
FilePath: doc.FilePath, FilePath: doc.FilePath,
DefaultBranch: doc.DefaultBranch, DefaultBranch: doc.DefaultBranch,
DocumentData: doc.DocumentData, DocumentData: doc.DocumentData,
CreationTime: doc.CreationTime, CreationTime: doc.CreationTime,
IsSame: doc.IsSame, IsSame: doc.IsSame,
FileType: doc.FileType,
} }
} }
func (doc *Document) Path() string {
return fmt.Sprintf("repoURL: %s filePath: %s branch: %s",
doc.RepositoryURL, doc.FilePath, doc.DefaultBranch)
}
// Implements the CrawlerDocument interface. // Implements the CrawlerDocument interface.
func (doc *Document) WasCached() bool { func (doc *Document) WasCached() bool {
return doc.IsSame return doc.IsSame
@@ -60,7 +47,6 @@ func (doc *Document) FromRelativePath(newFile string) (Document, error) {
RepositoryURL: repoSpec.Host + path.Clean(repoSpec.OrgRepo), RepositoryURL: repoSpec.Host + path.Clean(repoSpec.OrgRepo),
FilePath: path.Clean(repoSpec.Path), FilePath: path.Clean(repoSpec.Path),
DefaultBranch: repoSpec.Ref, DefaultBranch: repoSpec.Ref,
User: UserName(repoSpec.Host + path.Clean(repoSpec.OrgRepo)),
}, nil }, nil
} }
// else document is probably relative path. // else document is probably relative path.
@@ -68,7 +54,6 @@ func (doc *Document) FromRelativePath(newFile string) (Document, error) {
ret := Document{ ret := Document{
RepositoryURL: doc.RepositoryURL, RepositoryURL: doc.RepositoryURL,
DefaultBranch: doc.DefaultBranch, DefaultBranch: doc.DefaultBranch,
User: UserName(doc.RepositoryURL),
} }
ogDir, _ := path.Split(doc.FilePath) ogDir, _ := path.Split(doc.FilePath)
@@ -93,7 +78,13 @@ func (doc *Document) ID() string {
} }
func (doc *Document) RepositoryFullName() string { func (doc *Document) RepositoryFullName() string {
url := TrimUrl(doc.RepositoryURL) url := strings.TrimRight(doc.RepositoryURL, "/")
gitPrefix := "git@github.com:"
if strings.HasPrefix(url, gitPrefix) {
url = url[len(gitPrefix):]
}
sections := strings.Split(url, "/") sections := strings.Split(url, "/")
l := len(sections) l := len(sections)
if l < 2 { if l < 2 {
@@ -101,24 +92,3 @@ func (doc *Document) RepositoryFullName() string {
} }
return path.Join(sections[l-2], sections[l-1]) return path.Join(sections[l-2], sections[l-1])
} }
// TrimUrl removes all the trailing slashes and the "git@github.com:" prefix (if exists).
func TrimUrl(s string) string {
url := strings.TrimRight(s, "/")
gitPrefix := "git@github.com:"
if strings.HasPrefix(url, gitPrefix) {
url = url[len(gitPrefix):]
}
return url
}
func UserName(repositoryURL string) string {
url := TrimUrl(repositoryURL)
sections := strings.Split(url, "/")
l := len(sections)
if l < 2 {
return url
}
return sections[l-2]
}

View File

@@ -28,7 +28,6 @@ func TestFromRelativePath(t *testing.T) {
RepositoryURL: "example.com/repo", RepositoryURL: "example.com/repo",
FilePath: "path/to/other/file/resource.yaml", FilePath: "path/to/other/file/resource.yaml",
DefaultBranch: "master", DefaultBranch: "master",
User: "example.com",
}, },
}, },
{ {
@@ -37,7 +36,6 @@ func TestFromRelativePath(t *testing.T) {
RepositoryURL: "example.com/repo", RepositoryURL: "example.com/repo",
FilePath: "path/to/other/file/patch.yaml", FilePath: "path/to/other/file/patch.yaml",
DefaultBranch: "master", DefaultBranch: "master",
User: "example.com",
}, },
}, },
{ {
@@ -46,7 +44,6 @@ func TestFromRelativePath(t *testing.T) {
RepositoryURL: "example.com/repo", RepositoryURL: "example.com/repo",
FilePath: "path/to/file/service.yaml", FilePath: "path/to/file/service.yaml",
DefaultBranch: "master", DefaultBranch: "master",
User: "example.com",
}, },
}, },
}, },
@@ -68,7 +65,7 @@ func TestFromRelativePath(t *testing.T) {
func TestDocument_RepositoryFullName(t *testing.T) { func TestDocument_RepositoryFullName(t *testing.T) {
testCases := []struct { testCases := []struct {
doc Document doc Document
expectedRepositoryFullName string expectedRepositoryFullName string
}{ }{
{ {
@@ -111,40 +108,4 @@ func TestDocument_RepositoryFullName(t *testing.T) {
returnedRepositoryFullName) returnedRepositoryFullName)
} }
} }
} }
func TestDocument_UserName(t *testing.T) {
testCases := []struct {
repositoryURL string
expectedUserName string
}{
{
repositoryURL: "https://github.com/user/repo",
expectedUserName: "user",
},
{
repositoryURL: "https://github.com//user/repo////",
expectedUserName: "user",
},
{
repositoryURL: "repo/",
expectedUserName: "repo",
},
{
repositoryURL: "",
expectedUserName: "",
},
{
repositoryURL: "git@github.com:user/repo",
expectedUserName: "user",
},
}
for _, tc := range testCases {
returnedUserName := UserName(tc.repositoryURL)
if returnedUserName != tc.expectedUserName {
t.Errorf("UserName expected %s, got %s",
tc.expectedUserName, returnedUserName)
}
}
}

View File

@@ -1,36 +0,0 @@
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.Set(d.ID(), "")
}
func (uds *UniqueDocuments) AddDocuments(docs []*Document) {
for _, d := range docs {
uds.Add(d)
}
}
func (uds *UniqueDocuments) Documents() []*Document {
return uds.docs
}

View File

@@ -8,6 +8,6 @@ require (
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/rs/cors v1.7.0 github.com/rs/cors v1.7.0
sigs.k8s.io/kustomize/api v0.3.1 sigs.k8s.io/kustomize/api v0.3.0
sigs.k8s.io/yaml v1.1.0 sigs.k8s.io/yaml v1.1.0
) )

View File

@@ -133,8 +133,13 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v0.0.0-20160922145804-ca9ada445741/go.mod h1:+WVp8kdw6VhyKExm03PAMRn2ZxnPtm58pV0dBVPdhHE=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -193,6 +198,7 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/monopole/mdrip v1.0.1/go.mod h1:/7E04hlzRG9Jrp6WILZfYYm/REoJWL2l+MlsCO1eH74=
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -234,6 +240,7 @@ github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lz
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@@ -380,6 +387,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -407,8 +415,8 @@ k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
sigs.k8s.io/kustomize/api v0.3.1 h1:oqMIXvS6tFEUVuKIRUKDa05eC4Hh+cb9JYg8Zhp2d24= sigs.k8s.io/kustomize/api v0.3.0 h1:riR/YsL75nGb+aIPFdIRiqu21+OZbAXQybDS7+FUYRg=
sigs.k8s.io/kustomize/api v0.3.1/go.mod h1:A+ATnlHqzictQfQC1q3KB/T6MSr0UWQsrrLxMWkge2E= sigs.k8s.io/kustomize/api v0.3.0/go.mod h1:DWNMJBV1xvLruMpihGgnIPznMwHpwUSrxz6v3gnw5kw=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -20,18 +20,12 @@ const IndexConfig = `
"repositoryUrl": { "repositoryUrl": {
"type": "keyword" "type": "keyword"
}, },
"user": {
"type": "keyword"
},
"filePath": { "filePath": {
"type": "keyword" "type": "keyword"
}, },
"defaultBranch": { "defaultBranch": {
"type": "keyword" "type": "keyword"
}, },
"fileType": {
"type": "keyword"
},
"document": { "document": {
"type": "text" "type": "text"
}, },
@@ -93,7 +87,7 @@ func (idx *index) responseErrorOrNil(info string, res *esapi.Response,
defer res.Body.Close() defer res.Body.Close()
if res.IsError() { if res.IsError() {
return fmt.Errorf("%s: %s [%d]", messageStart, res.String(), res.StatusCode) return fmt.Errorf("%s: %s", messageStart, res.String())
} }
if reader != nil { if reader != nil {
@@ -315,9 +309,9 @@ func (idx *index) Exists(id string) (bool, error) {
op.WithPretty(), op.WithPretty(),
) )
if res != nil && !res.IsError() { if !res.IsError() {
return true, nil return true, nil
} else if res != nil && res.StatusCode == 404 { } else if res.StatusCode == 404 {
return false, nil return false, nil
} else { } else {
return false, idx.responseErrorOrNil( return false, idx.responseErrorOrNil(

View File

@@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"strings" "strings"
"time" "time"
@@ -18,7 +17,6 @@ const (
) )
type Mode int type Mode int
const ( const (
InsertOrUpdate = iota InsertOrUpdate = iota
Delete Delete
@@ -99,14 +97,14 @@ type KustomizeIndex struct {
} }
// Create index reference to the index containing the kustomize documents. // Create index reference to the index containing the kustomize documents.
func NewKustomizeIndex(ctx context.Context, indexName string) (*KustomizeIndex, error) { func NewKustomizeIndex(ctx context.Context) (*KustomizeIndex, error) {
idx, err := newIndex(ctx, indexName) idx, err := newIndex(ctx, "kustomize")
if err != nil { if err != nil {
return nil, err return nil, err
} }
indicesExistsOp := idx.client.Indices.Exists indicesExistsOp := idx.client.Indices.Exists
resp, err := indicesExistsOp([]string{indexName}, resp, err := indicesExistsOp([]string{"kustomize"},
indicesExistsOp.WithContext(idx.ctx), indicesExistsOp.WithContext(idx.ctx),
indicesExistsOp.WithPretty()) indicesExistsOp.WithPretty())
if err != nil { if err != nil {
@@ -114,9 +112,9 @@ func NewKustomizeIndex(ctx context.Context, indexName string) (*KustomizeIndex,
} }
if resp.StatusCode == 200 { if resp.StatusCode == 200 {
log.Printf("The %s index already exists", indexName) fmt.Printf("The kustomize index already exists\n")
} else { } else {
log.Printf("Creating the %s index\n", indexName) fmt.Printf("Creating the kustomize index\n")
if err := idx.CreateIndex([]byte(IndexConfig)); err != nil { if err := idx.CreateIndex([]byte(IndexConfig)); err != nil {
return nil, err return nil, err
} }
@@ -254,7 +252,7 @@ func (it *KustomizeIterator) Next() bool {
} }
if it.err == nil { if it.err == nil {
log.Printf("updating scroll: %s\n", *it.scrollImpl.ScrollID) fmt.Printf("updating scroll: %s\n", *it.scrollImpl.ScrollID)
it.err = it.update(*it.scrollImpl.ScrollID, reader) it.err = it.update(*it.scrollImpl.ScrollID, reader)
} }
@@ -270,7 +268,7 @@ func (it *KustomizeIterator) Value() KustomizeResult {
return it.scrollImpl return it.scrollImpl
} }
// Check if any errors have occurred. // Check if any errors have occured.
func (it *KustomizeIterator) Err() error { func (it *KustomizeIterator) Err() error {
return it.err return it.err
} }
@@ -343,7 +341,7 @@ func (ki *KustomizeIndex) Search(query string,
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to format query %s", query) return nil, fmt.Errorf("failed to format query %s", query)
} }
log.Printf("formated query: %s\n", data) fmt.Printf("formated query: %s\n", data)
var kr ElasticKustomizeResult var kr ElasticKustomizeResult
err = ki.index.Search(data, opts.SearchOptions, func(results io.Reader) error { err = ki.index.Search(data, opts.SearchOptions, func(results io.Reader) error {

View File

@@ -1,411 +0,0 @@
Find out the largest value of the `creationTime` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"max_creationTime" : { "max" : { "field" : "creationTime" } }
}
}
'
```
Find out the smallest value of the `creationTime` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
}
}
'
```
Find out the smallest value of the `creationTime` field of all the kustomization files:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
}
}
'
```
Find out the smallest value of the `creationTime` field of all kustomize resource files:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
}
}
'
```
Find out the smallest value of the `creationTime` field of all kustomize generator files:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
}
}
'
```
Find out the smallest value of the `creationTime` field of all kustomize transformer files:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
}
}
'
```
Query all the documents whose `creationTime` <= `2016-07-29T17:38:26.000Z`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"range": {
"creationTime": {
"lte": "2016-07-29T17:38:26.000Z"
}
}
}
}
'
```
Query all the documents whose `creationTime` falls within the specific range:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"range": {
"creationTime": {
"gte": "2016-07-29T17:38:26.000Z",
"lte": "2016-08-29T17:38:26.000Z"
}
}
}
}
'
```
Query all the kustomization files whose `creationTime` falls within the specific range:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 20,
"query": {
"bool": {
"filter": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"must": {
"range": {
"creationTime": {
"gte": "2017-09-24T15:49:57.000Z",
"lte": "2017-09-24T15:49:57.000Z"
}
}
}
}
}
}
'
```
Aggregate how many new kustomization files were added into Github each month:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "month"
}
}
}
}
'
```
Aggregate how many new kustomize resource files were added into Github each month:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "month"
}
}
}
}
'
```
Aggregate how many new kustomize generator files were added into Github each month:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "month"
}
}
}
}
'
```
Aggregate how many new kustomize transformer files were added into Github each month:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "month"
}
}
}
}
'
```
Aggregate how many new kustomization files were added into Github each year:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "year"
}
}
}
}
'
```
Aggregate how many new kustomize resource files were added into Github each year:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "year"
}
}
}
}
'
```
Aggregate how many new kustomize generator files were added into Github each year:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "year"
}
}
}
}
'
```
Aggregate how many new kustomize transformer files were added into Github each year:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
],
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"newFiles_over_time" : {
"date_histogram" : {
"field" : "creationTime",
"interval" : "year"
}
}
}
}
'
```
Find the generator files created within the given time range:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"must": {
"range": {
"creationTime": {
"gte": "2019-04-26T16:40:02.000Z",
"lte": "2019-04-26T16:40:02.000Z"
}
}
}
}
}
}
'
```
Find the transformer files created within the given time range:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"must": {
"range": {
"creationTime": {
"gte": "2019-04-26T16:40:02.000Z",
"lte": "2019-04-26T16:40:02.000Z"
}
}
}
}
}
}
'
```

View File

@@ -1,32 +0,0 @@
Count distinct values of the `defaultBranch` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"defaultBranch_count" : {
"cardinality" : {
"field" : "defaultBranch",
"precision_threshold": 40000
}
}
}
}
'
```
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 -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"defaultBranch" : {
"terms" : {
"field" : "defaultBranch",
"size": 41
}
}
}
}
'
```

View File

@@ -1,55 +0,0 @@
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 -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must_not": {
"exists": {
"field": "document"
}
}
}
}
}
'
```
Find all the documents having the `creationTime` field set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
"field": "creationTime"
}
}
}
'
```
Find all the documents whose `creationTime` field is not set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must_not": {
"exists": {
"field": "creationTime"
}
}
}
}
}
'
```
The following fields of a document in the kustomize index are always non-empty:
`repositoryUrl`, `filePath`, `defaultBranch`.
The following fields of a document in the kustomize index may be empty:
`kinds`, `identifiers`, `values`.

View File

@@ -1,301 +0,0 @@
Find all the documents having the `fileType` field set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
"field": "fileType"
}
}
}
'
```
Find all the documents whose `fileType` field is not set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must_not": {
"exists": {
"field": "fileType"
}
}
}
}
}
'
```
Search for all the documents whose `fileType` field is `resource`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
}
}
'
```
Search for all the kustomization files whose `fileType` field is `resource`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }},
{ "regexp": { "fileType": "resource" }}
]
}
}
}
'
```
Search for all the kustomize resource files:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "resource" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
}
}
'
```
Search all the kustomization files including a `generators` field:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must": {
"match" : {
"identifiers" : {
"query" : "generators"
}
}
},
"filter": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
}
}
'
```
Search for all the documents whose `fileType` field is `generator`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
}
}
'
```
Search for all the kustomization files whose `fileType` field is `generator`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }},
{ "regexp": { "fileType": "generator" }}
]
}
}
}
'
```
Search for all the kustomize generator files:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
}
}
'
```
Search all the kustomization files including a `transformers` field:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must": {
"match" : {
"identifiers" : {
"query" : "transformers"
}
}
},
"filter": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
}
}
'
```
Search for all the documents whose `fileType` field is `transformer`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
}
}
'
```
Search for all the kustomization files whose `fileType` field is `transformer`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }},
{ "regexp": { "fileType": "transformer" }}
]
}
}
}
'
```
Search for all the kustomize transformer files:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
}
}
'
```
Count distinct values of the `fileType` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"fileType_count" : {
"cardinality" : {
"field" : "fileType",
"precision_threshold": 40000
}
}
}
}
'
```
List all the values of the `fileType` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"fileType" : {
"terms" : {
"field" : "fileType"
}
}
}
}
'
```
For all the kustomization files in the index, list all the values of the
`fileType` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"fileType" : {
"terms" : {
"field" : "fileType"
}
}
}
}
'
```
For all the non-kustomization files in the index, list all the values of the
`fileType` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
}
}
},
"aggs" : {
"fileType" : {
"terms" : {
"field" : "fileType"
}
}
}
}
'
```

View File

@@ -1,29 +0,0 @@
Find all the generator files whose `kinds` field includes `ChartRenderer`, and
only output certain fields of each document:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 200,
"_source": {
"includes": ["kinds", "repositoryUrl", "defaultBranch", "filePath"]
},
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"must": {
"match" : {
"kinds" : {
"query" : "ChartRenderer"
}
}
}
}
}
}
'
```

View File

@@ -1,12 +0,0 @@
Find the document with the given `_id`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"terms": {
"_id": [ "b3a03f3327841617db696e2d6abc30e1a1bd653f1a2bbce05637f7dcae1a43f7" ]
}
}
}
'
```

View File

@@ -1,82 +0,0 @@
Count the documents in the index whose `repositoryUrl` field starts with
`https://github.com/`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "repositoryUrl": "https://github.com/.*" }}
]
}
}
}
'
```
Count the documents in the index whose `repositoryUrl` field does not start with
`https://github.com/`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "repositoryUrl": "https://github.com/.*" }}
]
}
}
}
'
```
Search all the documents matching the given `repositoryUrl` and `filePath`, and return
a version for each search hit:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"version": true,
"query": {
"bool": {
"filter": [
{ "regexp": { "repositoryUrl": "git@github.com:talos-systems/talos-controller-manager" }},
{ "regexp": { "filePath": "hack/config.*" }}
]
}
}
}
'
```
Search all the documents whose filePath ends with one of these following three filenames:
`kustomization.yaml`, `kustomization.yml`, `kustomization`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
}
}
'
```
Search all the documents whose filePath does not end with any of these following
three filenames: `kustomization.yaml`, `kustomization.yml`, `kustomization`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
}
}
'
```

View File

@@ -1,32 +0,0 @@
Check the health status of an ElasticSearch cluster:
```
curl -s -X GET "${ElasticSearchURL}:9200/_cat/health?v&pretty"
```
Check the indices in an ElasticSearch cluster:
```
curl -s "${ElasticSearchURL}:9200/_cat/indices?v"
```
Get the mapping of the index:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_mapping?pretty"
```
Delete the kustomize index from the ElasticSearch cluster (**Use this command with caution**):
```
curl -s -X DELETE "${ElasticSearchURL}:9200/${INDEXNAME}?pretty"
```
Add a new field into an existing index.
```
curl -s -X PUT "${ElasticSearchURL}:9200/${INDEXNAME}/_mapping/_doc?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"fileType": {
"type": "keyword"
}
}
}
'
```

View File

@@ -1,255 +0,0 @@
Count distinct values of the `repositoryUrl` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count how many Github repositories include kustomization files:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize resource files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize generator files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize transformer files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize resource dirs in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "resource" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize generator dirs in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
Count distinct values of the `repositoryUrl` field for all the kustomize transformer dirs in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"repositoryUrl_count" : {
"cardinality" : {
"field" : "repositoryUrl",
"precision_threshold": 40000
}
}
}
}
'
```
List all the github repositories including kustomization files and kustomize resource files,
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 -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"repositoryUrl" : {
"terms" : {
"field" : "repositoryUrl",
"size": 2082
}
}
}
}
'
```
List the top 20 Github repositories including the most amount of kustomization files:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"repositoryUrl" : {
"terms" : {
"field" : "repositoryUrl",
"size": 20
}
}
}
}
'
```
List the top 20 Github repositories including the most amount of kustomize resource files:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"repositoryUrl" : {
"terms" : {
"field" : "repositoryUrl",
"size": 20
}
}
}
}
'
```

View File

@@ -1,29 +0,0 @@
Retrieve information about all registered snapshot repositories:
```
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot?pretty"
```
Retrieve information about a given snapshot repository, `kustomize-backup`:
```
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup?pretty"
```
Verify a snapshot repository, `kustomize-backup`, manually:
```
curl -s -X POST "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/_verify?pretty"
```
List all the snapshots in a given snapshot repository:
```
curl -s -X GET "${ElasticSearchURL}:9200/_cat/snapshots/kustomize-backup?v&s=id&pretty"
```
Retrieve a summary information about a given snapshot:
```
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/kustomize-snapshot?pretty"
```
Retrieve a detailed information about a given snapshot:
```
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/kustomize-snapshot/_status?pretty"
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,148 +0,0 @@
Search for all the kustomize resource files including a Deployment object:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {
"kinds" : {
"query" : "Deployment"
}
}
}
}
'
```
Search for all the kustomize resource files including a Deployment object, but only
including the `kinds` field in the result:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"_source": {
"includes": ["kinds"]
},
"query": {
"match" : {
"kinds" : {
"query" : "Deployment"
}
}
}
}
'
```
Search for all the kustomize resource files including both a Deployment object and
a Service object:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {
"kinds" : {
"query" : "Deployment Service",
"operator" : "and"
}
}
}
}
'
```
Count the number of documents including Deployment and the number of documents
including Service:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs" : {
"messages" : {
"filters" : {
"filters" : {
"Deployment" : { "match" : { "kinds" : "Deployment" }},
"Service" : { "match" : { "kinds" : "Service" }}
}
}
}
}
}
'
```
Search for all the kustomization files involving CRDs:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"match" : {
"identifiers" : {
"query" : "crds"
}
}
}
}
'
```
Search for all the kustomization files defining configMapGenerator:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"match" : {
"identifiers" : {
"query" : "configMapGenerator"
}
}
}
}
'
```
Search for all the documents having a `kind` field:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "match" : { "identifiers" : { "query" : "kind" }}}
]
}
}
}
'
```
Search for all the kuostmization files having a `kind` field:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": ".*/kustomization((.yaml)?|(.yml)?)" }},
{ "match" : { "identifiers" : { "query" : "kind" }}}
]
}
}
}
'
```
Search for all the kustomization files defining the `generatorOptions:disableNameSuffixHash` feature:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {
"identifiers" : {
"query" : "generatorOptions:disableNameSuffixHash"
}
}
}
}
'
```

View File

@@ -1,29 +0,0 @@
Find all the trasnformer files whose `kinds` field includes `HelmValues`, and
only output certain fields of each document:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 200,
"_source": {
"includes": ["kinds", "repositoryUrl", "defaultBranch", "filePath"]
},
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }}
],
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"must": {
"match" : {
"kinds" : {
"query" : "HelmValues"
}
}
}
}
}
}
'
```

View File

@@ -1,380 +0,0 @@
Find all the documents having the `user` field set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
"field": "user"
}
}
}
'
```
Find all the documents whose `user` field is not set:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
"bool": {
"must_not": {
"exists": {
"field": "user"
}
}
}
}
}
'
```
Search for all the documents whose `user` field is `kubernetes-sigs`:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "user": "kubernetes-sigs" }}
]
}
}
}
'
```
Count distinct values of the `user` field:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
List all the values of the `user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size" : 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomization files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomization files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size": 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomize resource files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomize resource files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "resource" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size": 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomize generator files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomize generator files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "generator" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size": 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomize transformer files in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomize transformer files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must_not": {
"regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }
},
"filter": [
{ "regexp": { "fileType": "transformer" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size": 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomize generator dirs in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomize generator dirs in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "generator" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user",
"size": 20
}
}
}
}
'
```
Count distinct values of the `user` field for all the kustomize transformer dirs in the index:
```
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user_count" : {
"cardinality" : {
"field" : "user",
"precision_threshold": 40000
}
}
}
}
'
```
For all the kustomize transformer dirs in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": [
{ "regexp": { "fileType": "transformer" }},
{ "regexp": { "filePath": "(.*/)?kustomization((.yaml)?|(.yml)?)(/)*" }}
]
}
},
"aggs" : {
"user" : {
"terms" : {
"field" : "user"
}
}
}
}
'
```

View File

@@ -1,21 +0,0 @@
package utils
type SeenMap map[string]string
func (seen SeenMap) Seen(item string) bool {
_, ok := seen[item]
return ok
}
func (seen SeenMap) Set(k, v string) {
seen[k] = v
}
// The caller should make sure that key is in the map.
func (seen SeenMap) Value(k string) string {
return seen[k]
}
func NewSeenMap() SeenMap {
return make(map[string]string)
}

View File

@@ -27,28 +27,90 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
if err != nil { if err != nil {
return err return err
} }
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd := exec.Command( cmd := exec.Command(
gitProgram, gitProgram,
"clone", "init",
"--depth=1",
repoSpec.CloneSpec(),
"-b",
repoSpec.Ref,
repoSpec.Dir.String()) repoSpec.Dir.String())
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &out cmd.Stderr = &out
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
log.Printf("Error cloning git repo: %s", out.String()) log.Printf("Error initializing empty git repo: %s", out.String())
return errors.Wrapf( return errors.Wrapf(
err, err,
"trouble cloning git repo %v in %s", "trouble initializing empty git repo in %s",
repoSpec.CloneSpec(), repoSpec.Dir.String()) repoSpec.Dir.String())
}
cmd = exec.Command(
gitProgram,
"remote",
"add",
"origin",
repoSpec.CloneSpec())
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
log.Printf("Error setting git remote: %s", out.String())
return errors.Wrapf(
err,
"trouble adding remote %s",
repoSpec.CloneSpec())
}
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd = exec.Command(
gitProgram,
"fetch",
"--depth=1",
"origin",
repoSpec.Ref)
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
cmd = exec.Command(
gitProgram,
"pull",
"origin",
"master")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Dir = repoSpec.Dir.String()
err := cmd.Run()
if err != nil {
return errors.Wrapf(err, "trouble pulling %s", repoSpec.OrgRepo)
}
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd = exec.Command(gitProgram, "checkout", repoSpec.Ref)
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
return errors.Wrapf(
err, "trouble checking out href %s", repoSpec.Ref)
}
}
cmd = exec.Command(
gitProgram,
"reset",
"--hard",
"FETCH_HEAD")
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
log.Printf("Error performing git reset: %s", out.String())
return errors.Wrapf(
err, "trouble hard resetting empty repository to %s", repoSpec.Ref)
} }
cmd = exec.Command( cmd = exec.Command(
@@ -58,11 +120,10 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
"--init", "--init",
"--recursive") "--recursive")
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String() cmd.Dir = repoSpec.Dir.String()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.CloneSpec()) return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.Ref)
} }
return nil return nil

View File

@@ -4,7 +4,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// This file exists to automatically trigger installs // This file exists to automatically trigger installs
// of the given tools, and is the official 'unofficial' // of the given tools, and is the offical 'unofficial'
// way to declare a dependence on a Go binary until // way to declare a dependence on a Go binary until
// some better technique comes along. // some better technique comes along.

View File

@@ -154,9 +154,8 @@ LEGUME=chickpea
`) `)
th.WriteF("/app/overlay/configmap/dummy.txt", th.WriteF("/app/overlay/configmap/dummy.txt",
`Lorem ipsum dolor sit amet, consectetur `Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
incididunt ut labore et dolore magna aliqua.
`) `)
th.WriteF("/app/overlay/deployment/deployment.yaml", ` th.WriteF("/app/overlay/deployment/deployment.yaml", `
apiVersion: apps/v1 apiVersion: apps/v1
@@ -293,11 +292,8 @@ metadata:
--- ---
apiVersion: v1 apiVersion: v1
data: data:
nonsense: | nonsense: "Lorem ipsum dolor sit amet, consectetur\nadipiscing elit, sed do eiusmod
Lorem ipsum dolor sit amet, consectetur tempor\nincididunt ut labore et dolore magna aliqua. \n"
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
kind: ConfigMap kind: ConfigMap
metadata: metadata:
annotations: annotations:
@@ -306,6 +302,6 @@ metadata:
app: mungebot app: mungebot
org: kubernetes org: kubernetes
repo: test-infra repo: test-infra
name: test-infra-app-config-hh272bg5d4 name: test-infra-app-config-f462h769f9
`) `)
} }

View File

@@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"regexp"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@@ -86,17 +85,11 @@ func (kvl *loader) keyValuesFromFileSources(sources []string) ([]types.Pair, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
kvs = append(kvs, types.Pair{Key: k, Value: trimTrailingSpacesInLines(string(content))}) kvs = append(kvs, types.Pair{Key: k, Value: string(content)})
} }
return kvs, nil return kvs, nil
} }
// trimTrailingSpacesInLines takes string with multiple lines and trims the trailing white spaces and tabs from each line.
func trimTrailingSpacesInLines(str string) string {
re := regexp.MustCompile(`[ \t]*\n`)
return re.ReplaceAllString(str, "\n")
}
func (kvl *loader) keyValuesFromEnvFiles(paths []string) ([]types.Pair, error) { func (kvl *loader) keyValuesFromEnvFiles(paths []string) ([]types.Pair, error) {
var kvs []types.Pair var kvs []types.Pair
for _, p := range paths { for _, p := range paths {

View File

@@ -95,12 +95,3 @@ func TestKeyValuesFromFileSources(t *testing.T) {
} }
} }
} }
func TestTrimTrailingSpacesInLines(t *testing.T) {
input := "\"fooKey\": \"fooValue\" \t\n \t\t \n\t\"barKey\": \"barValue\""
expected := "\"fooKey\": \"fooValue\"\n\n\t\"barKey\": \"barValue\""
res := trimTrailingSpacesInLines(input)
if !reflect.DeepEqual(res, expected) {
t.Errorf("Trim trailing spaces in lines should succeed, got: %s exptected: %s", res, expected)
}
}

View File

@@ -23,7 +23,7 @@ linters:
- gofmt - gofmt
- goimports - goimports
# - golint # - golint
# - gosec - gosec
- gosimple - gosimple
- govet - govet
- ineffassign - ineffassign

View File

@@ -43,30 +43,6 @@ Advanced Documentation Topics:
`, `,
} }
// Export commands publicly for composition
var (
Annotate = commands.AnnotateCommand
Cat = commands.CatCommand
Count = commands.CountCommand
CreateSetter = commands.CreateSetterCommand
CreateSubstitution = commands.CreateSubstitutionCommand
Fmt = commands.FmtCommand
Grep = commands.GrepCommand
ListSetters = commands.ListSettersCommand
Merge = commands.MergeCommand
Merge3 = commands.Merge3Command
RunFn = commands.RunFnCommand
Set = commands.SetCommand
Sink = commands.SinkCommand
Source = commands.SourceCommand
Tree = commands.TreeCommand
Wrap = commands.WrapCommand
XArgs = commands.XArgsCommand
StackOnError = &commands.StackOnError
ExitOnError = &commands.ExitOnError
)
// NewConfigCommand returns a new *cobra.Command for the config command group. This may // NewConfigCommand returns a new *cobra.Command for the config command group. This may
// be embedded into other go binaries as a way of packaging the "config" command as part // be embedded into other go binaries as a way of packaging the "config" command as part
// of another binary. // of another binary.
@@ -93,7 +69,6 @@ func NewConfigCommand(name string) *cobra.Command {
name = strings.TrimSpace(name + " config") name = strings.TrimSpace(name + " config")
commands.ExitOnError = true commands.ExitOnError = true
root.AddCommand(commands.AnnotateCommand(name))
root.AddCommand(commands.GrepCommand(name)) root.AddCommand(commands.GrepCommand(name))
root.AddCommand(commands.TreeCommand(name)) root.AddCommand(commands.TreeCommand(name))
root.AddCommand(commands.CatCommand(name)) root.AddCommand(commands.CatCommand(name))
@@ -102,15 +77,8 @@ func NewConfigCommand(name string) *cobra.Command {
root.AddCommand(commands.Merge3Command(name)) root.AddCommand(commands.Merge3Command(name))
root.AddCommand(commands.CountCommand(name)) root.AddCommand(commands.CountCommand(name))
root.AddCommand(commands.RunFnCommand(name)) root.AddCommand(commands.RunFnCommand(name))
root.AddCommand(commands.XArgsCommand())
root.AddCommand(commands.WrapCommand())
root.AddCommand(commands.SetCommand(name)) root.AddCommand(commands.SetCommand(name))
root.AddCommand(commands.ListSettersCommand(name))
root.AddCommand(commands.CreateSetterCommand(name)) root.AddCommand(commands.CreateSetterCommand(name))
root.AddCommand(commands.CreateSubstitutionCommand(name))
root.AddCommand(commands.SinkCommand(name))
root.AddCommand(commands.SourceCommand(name))
root.AddCommand(&cobra.Command{ root.AddCommand(&cobra.Command{
Use: "docs-merge", Use: "docs-merge",
@@ -124,13 +92,8 @@ func NewConfigCommand(name string) *cobra.Command {
}) })
root.AddCommand(&cobra.Command{ root.AddCommand(&cobra.Command{
Use: "docs-fn", Use: "docs-fn",
Short: "[Alpha] Documentation for developing and invoking Configuration Functions.", Short: "[Alpha] Documentation for writing containerized functions run by run.",
Long: api.FunctionsImplLong, Long: api.ConfigFnLong,
})
root.AddCommand(&cobra.Command{
Use: "docs-fn-spec",
Short: "[Alpha] Documentation for Configuration Functions Specification.",
Long: api.FunctionsSpecLong,
}) })
root.AddCommand(&cobra.Command{ root.AddCommand(&cobra.Command{
Use: "docs-io-annotations", Use: "docs-io-annotations",

View File

@@ -0,0 +1,281 @@
# Configuration Functions API Semantics
Configuration Functions are functions packaged as executables in containers which enable
**shift-left practices**. They configure applications and infrastructure through
Kubernetes style Resource Configuration, but run locally pre-commit.
Configuration functions enable shift-left practices (client-side) through:
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side *"CRD"* for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with `small`, `medium`, `large`
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject `init` and `side-car` containers into Resources based off of Resource
Type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource -- e.g. ensure the `Service.selector` and `Deployment.spec.template` labels
match.
- MutatingWebHooks are scoped to a single Resource instance at a time.
- Low-level tweaks to the output of high-level abstractions -- e.g. add an `init container`
to a client *"CRD"* Resource after it was generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
Configuration Functions are implemented as executable programs published in containers which:
- Accept as input (stdin):
- A list of Resource Configuration
- A Function Configuration (to configure the function itself)
- Emit as output (stdout + exit):
- A list of Resource Configuration
- An exit code for success / failure
### Function Specification
- Functions **SHOULD** be published as container images containing a `CMD` invoking an executable.
- Functions **MUST** accept input on STDIN a `ResourceList` containing the Resources and
`functionConfig`.
- Functions **MUST** emit output on STDOUT a `ResourceList` containing the modified
Resources.
- Functions **MUST** exit non-0 on failure, and exit 0 on success.
- Functions **MAY** emit output on STDERR with error messaging.
- Functions performing validation **SHOULD** exit failure and emit error messaging
on a validation failure.
- Functions generating Resources **SHOULD** retain non-conflicting changes on the
generated Resources -- e.g. 1. the function generates a Deployment, but doesn't
specify `cpu`, 2. the user sets the `cpu` on the generated Resource, 3. the
function should keep the `cpu` when regenerating the Resource a second time.
- Functions **SHOULD** be usable outside `kustomize config run` -- e.g. though pipeline
mechanisms such as Tekton.
#### Input Format
Functions must accept on STDIN:
`ResourceList`:
- contains `items` field, same as `List.items`
- contains `functionConfig` field -- a single item with the configuration for the function itself
Example `ResourceList` Input:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 5
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 3
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Output Format
Functions must emit on STDOUT:
`ResourceList`:
- contains `items` field, same as `List.items`
Example `ResourceList` Output:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 5
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Container Environment
When run by `kustomize config run`, functions are run in containers with the
following environment:
- Network: `none`
- User: `nobody`
- Security Options: `no-new-privileges`
- Volumes: the volume containing the `functionConfig` yaml is mounted under `/local` as `ro`
### Example Function Implementation
Following is an example for implementing an nginx abstraction using a config
function.
#### `nginx-template.sh`
`nginx-template.sh` is a simple bash script which uses a *heredoc* as a templating solution
for generating Resources from the functionConfig input fields.
The script wraps itself using `config run wrap -- $0` which will:
1. Parse the `ResourceList.functionConfig` (provided to the container stdin) into env vars
2. Merge the stdout into the original list of Resources
3. Defaults filenames for newly generated Resources (if they are not set as annotations)
to `config/NAME_KIND.yaml`
4. Format the output
#!/bin/bash
# script must run wrapped by `kustomize config run wrap`
# for parsing input the functionConfig into env vars
if [ -z ${WRAPPED} ]; then
export WRAPPED=true
config run wrap -- $0
exit $?
fi
cat <<End-of-message
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: ${NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
instance: ${NAME}
template:
metadata:
labels:
app: nginx
instance: ${NAME}
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
End-of-message
#### `Dockerfile`
`Dockerfile` installs `kustomize config` and copies the script into the container image.
FROM golang:1.13-stretch
RUN go get sigs.k8s.io/kustomize/cmd/config
RUN mv /go/bin/config /usr/bin/config
COPY nginx-template.sh /usr/bin/nginx-template.sh
CMD ["nginx-template.sh]
### Example Function Usage
Following is an example of running the `kustomize config run` using the preceding API.
#### `nginx.yaml` (Input)
`dir/nginx.yaml` contains a reference to the Function. The contents of `nginx.yaml`
are passed to the Function through the `ResourceList.functionConfig` field.
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
configFn:
container:
image: gcr.io/example-functions/nginx-template:v1.0.0
spec:
replicas: 5
- `configFn.container.image`: the image to use for this API
- `annotations[config.kubernetes.io/local-config]`: mark this as not a Resource that should
be applied
#### `kustomize config run dir/` (Output)
`dir/my-instance_deployment.yaml` contains the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
replicas: 5
selector:
matchLabels:
app: nginx
instance: my-instance
template:
metadata:
labels:
app: nginx
instance: my-instance
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
`dir/my-instance_service.yaml` contains the Service:
apiVersion: v1
kind: Service
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: my-instance

View File

@@ -29,7 +29,7 @@ metadata:
### `config.kubernetes.io/index` ### `config.kubernetes.io/index`
Records the index of a Resource in file. In a multi-object YAML file, Resources are separated Records the index of a Resource in file. In a multi-object YAML file, Resources are separated
by three dashes (`---`), and the index represents the position of the Resource starting from zero. by three dashes (`---`), and the index represents the positon of the Resource starting from zero.
This annotation **SHOULD** be set when reading Resources from files. This annotation **SHOULD** be set when reading Resources from files.
It **SHOULD** be unset when writing Resources to files. It **SHOULD** be unset when writing Resources to files.

View File

@@ -1,181 +0,0 @@
# Running Configuration Functions using kustomize CLI
Configuration functions can be implemented using any toolchain and invoked using any
container workflow orchestrator including Tekton, Cloud Build, or run directly using `docker run`.
Run `config help docs-fn-spec` to see the Configuration Functions Specification.
`kustomize config run` is an example orchestrator for invoking Configuration Functions. This
document describes how to implement and invoke an example function.
## Example Function Implementation
Following is an example for implementing an nginx abstraction using a configuration
function.
### `nginx-template.sh`
`nginx-template.sh` is a simple bash script which uses a _heredoc_ as a templating solution
for generating Resources from the functionConfig input fields.
The script wraps itself using `config run wrap -- $0` which will:
1. Parse the `ResourceList.functionConfig` (provided to the container stdin) into env vars
2. Merge the stdout into the original list of Resources
3. Defaults filenames for newly generated Resources (if they are not set as annotations)
to `config/NAME_KIND.yaml`
4. Format the output
```bash
#!/bin/bash
# script must run wrapped by "kustomize config run wrap"
# for parsing input the functionConfig into env vars
if [ -z ${WRAPPED} ]; then
export WRAPPED=true
config run wrap -- $0
exit $?
fi
cat <<End-of-message
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: ${NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
instance: ${NAME}
template:
metadata:
labels:
app: nginx
instance: ${NAME}
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
End-of-message
```
### Dockerfile
`Dockerfile` installs `kustomize config` and copies the script into the container image.
```
FROM golang:1.13-stretch
RUN go get sigs.k8s.io/kustomize/cmd/config
RUN mv /go/bin/config /usr/bin/config
COPY nginx-template.sh /usr/bin/nginx-template.sh
CMD ["nginx-template.sh]
```
## Example Function Usage
Following is an example of running the `kustomize config run` using the preceding API.
When run by `kustomize config run`, functions are run in containers with the
following environment:
- Network: `none`
- User: `nobody`
- Security Options: `no-new-privileges`
- Volumes: the volume containing the `functionConfig` yaml is mounted under `/local` as `ro`
### Input
`dir/nginx.yaml` contains a reference to the Function. The contents of `nginx.yaml`
are passed to the Function through the `ResourceList.functionConfig` field.
```yaml
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
config.k8s.io/function: |
container:
image: gcr.io/example-functions/nginx-template:v1.0.0
spec:
replicas: 5
```
- `annotations[config.k8s.io/function].container.image`: the image to use for this API
- `annotations[config.kubernetes.io/local-config]`: mark this as not a Resource that should
be applied
### Output
The function is invoked using byrunning `kustomize config run dir/`.
`dir/my-instance_deployment.yaml` contains the Deployment:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
replicas: 5
selector:
matchLabels:
app: nginx
instance: my-instance
template:
metadata:
labels:
app: nginx
instance: my-instance
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
```
`dir/my-instance_service.yaml` contains the Service:
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: my-instance
```

View File

@@ -1,186 +0,0 @@
# Configuration Functions Specification
This document specifies a standard for client-side functions that operate on
Kubernetes declarative configurations. This standard enables creating
small, interoperable, and language-independent executable programs packaged as
containers that can be chained together as part of a configuration management pipeline.
The end result of such a pipeline are fully rendered configurations that can then be
applied to a control plane (e.g. Using kubectl apply for Kubernetes control plane).
As such, although this document references Kubernetes Resource Model and API conventions,
it is completely decoupled from Kuberentes API machinery and does not depend on any
in-cluster components.
This document references terms described in [Kubernetes API Conventions][1].
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in [RFC 2119][2].
## Use Cases
_Configuration functions_ enable shift-left practices (client-side) through:
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side _"CRD"_ for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with `small`, `medium`, `large`
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject `init` and `side-car` containers into Resources based off of Resource
Type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CI?CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource
- e.g. ensure the `Service.selector` and `Deployment.spec.template` labels
match.
- e.g. MutatingWebHooks are scoped to a single Resource instance at a time.
- Low-level tweaks to the output of high-level abstractions
- e.g. add an `init container` to a client _"CRD"_ Resource after it was generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
## Spec
### Input Type
A function MUST accept as input a single [Kubernetes List type][3].
The `items` field in the input will contain a sequence of [Object types][3].
A function MAY not support [Simple types][3] and List types.
An example using `v1/ConfigMapList` as input:
```yaml
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: config1
data:
p1: v1
p2: v2
- apiVersion: v1
kind: ConfigMap
metadata:
name: config2
```
An example using `v1/List` as input:
```yaml
apiVersion: v1
kind: List
items:
spec:
- apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
address: "100 Main St."
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
```
In addition, a function MUST accept as input a List of kind `ResourceList` where the
`functionConfig` field, if present, will contain the invocation-specific configuration passed to the function
by the orchestrator.
Functions MAY consider this field optional so that they can be triggered in an ad-hoc fashion.
An example using `config.kubernetes.io/v1beta1/ResourceList` as input:
```yaml
apiVersion: config.kubernetes.io/v1beta1
kind: ResourceList
functionConfig:
apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
metadata:
annotations:
config.k8s.io/function: |
container:
image: gcr.io/example/foo:v1.0.0
spec:
address: "100 Main St."
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
```
Here `FulfillmentCenter` kind with name `staging` is passed as the invocation-specific configuration
to the function.
### Output Type
A functions output MUST be the same as the input specification above
-- i.e. `ResourceList` or `List`.
This is necessary to enable chaining two or more functions together in a pipeline.
The serialization format of the output SHOULD match that of its input on each invocation
-- e.g. if the input was a `ResourceList`, the output should also be a `ResourceList`.
### Serialization Format
A function MUST support YAML as a serialization format for the input and output.
A function MUST use utf8 encoding (as YAML is a superset of JSON, JSON will also be supported
by any conforming function).
### Operations
A function MAY Create, Update, or Delete any number of items in the `items` field and output the
resultant list.
A function MAY modify annotations with prefix `config.kubernetes.io`, but must be careful about
doing so since theyre used for orchestration purposes and will likely impact subsequent functions
in the pipeline.
A function SHOULD preserve comments when input serialization format is YAML.
This allows for human authoring of configuration to coexist with changes made by functions.
### Containerization
A function MUST be implemented as a container.
A function container MUST be capable of running as a non-root user if it does not require
access to host filesystem or makes network calls.
### stdin/stdout/stderr and Exit Codes
A function MUST accept input from stdin and emit output to stdout.
Any error messages MUST be emitted to stderr.
An exit code of zero indicates function execution was successful.
A non-zero exit code indicates a failure.
[1]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
[2]: https://tools.ietf.org/html/rfc2119
[3]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds

View File

@@ -1,18 +0,0 @@
## 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

View File

@@ -1,23 +0,0 @@
## set
[Alpha] List setters for Resources.
### Synopsis
List setters for Resources.
DIR
A directory containing Resource configuration.
NAME
Optional. The name of the setter to display.
### Examples
Show setters:
$ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY
name-prefix '' PREFIX string 2

View File

@@ -22,8 +22,8 @@ order they appear in the file).
#### Config Functions: #### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function] Config functions are specified as Kubernetes types containing a metadata.configFn.container.image
field specifying an image for the container to run. This image tells run how to invoke the container. field. This field tells run how to invoke the container.
Example config function: Example config function:
@@ -31,17 +31,17 @@ order they appear in the file).
apiVersion: fn.example.com/v1beta1 apiVersion: fn.example.com/v1beta1
kind: ExampleFunctionKind kind: ExampleFunctionKind
metadata: metadata:
configFn:
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
annotations: annotations:
config.kubernetes.io/function: |
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
config.kubernetes.io/local-config: "true" # tools should ignore this config.kubernetes.io/local-config: "true" # tools should ignore this
spec: spec:
configField: configValue configField: configValue
In the preceding example, 'kustomize config run example/' would identify the function by In the preceding example, 'kustomize config run example/' would identify the function by
the metadata.annotations.[config.kubernetes.io/function] field. It would then write all Resources in the directory to the metadata.configFn field. It would then write all Resources in the directory to
a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It
would then write the container stdout back to example/, replacing the directory would then write the container stdout back to example/, replacing the directory
file contents. file contents.

View File

@@ -59,19 +59,19 @@ To create a custom setter for a field see: `kustomize help config create-setter`
List setters: Show the possible setters List setters: Show the possible setters
$ config set DIR/ $ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY NAME DESCRIPTION VALUE TYPE COUNT OWNER
name-prefix '' PREFIX string 2 name-prefix '' PREFIX string 2
Perform set: set a new value, owner and description Perform substitution: set a new value, owner and description
$ kustomize config set DIR/ name-prefix "test" --description "test environment" --set-by "dev" $ kustomize config set DIR/ name-prefix "test" --description "test environment" --set-by "dev"
set 2 values performed 2 substitutions
List setters: Show the new values Show substitutions: Show the new values
$ config set DIR/ $ config set dir
NAME DESCRIPTION VALUE TYPE COUNT SETBY NAME DESCRIPTION VALUE TYPE COUNT SUBSTITUTED OWNER
name-prefix 'test environment' test string 2 dev prefix 'test environment' test string 2 true dev
New Resource YAML: New Resource YAML:

View File

@@ -1,18 +0,0 @@
## sink
[Alpha] Implement a Sink by writing input to a local directory.
### Synopsis
[Alpha] Implement a Sink by writing input to a local directory.
kustomize config sink [DIR]
DIR:
Path to local directory. If unspecified, sink will write to stdout as if it were a single file.
`sink` writes its input to a directory
### Examples
kustomize config source DIR/ | your-function | kustomize config sink DIR/

View File

@@ -1,22 +0,0 @@
## source
[Alpha] Implement a Source by reading a local directory.
### Synopsis
[Alpha] Implement a Source by reading a local directory.
kustomize config source DIR...
DIR:
One or more paths to local directories. Contents from directories will be concatenated.
If no directories are provided, source will read from stdin as if it were a single file.
`source` emits configuration to act as input to a function
### Examples
# emity configuration directory as input source to a function
kustomize config source DIR/
kustomize config source DIR/ | your-function | kustomize config sink DIR/

View File

@@ -1,12 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package ext
import "path/filepath"
// GetOpenAPIFile returns the path to the file containing supplementary OpenAPI definitions.
// Maybe be overridden to configure which file to read OpenAPI definitions from.
var GetOpenAPIFile = func(args []string) (string, error) {
return filepath.Join(args[0], "kustomization"), nil
}

View File

@@ -9,8 +9,7 @@ require (
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
k8s.io/apimachinery v0.17.0 k8s.io/apimachinery v0.17.0
sigs.k8s.io/kustomize/kyaml v0.0.0 sigs.k8s.io/kustomize/kyaml v0.0.4
) )
replace sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml

View File

@@ -101,8 +101,6 @@ github.com/posener/complete/v2 v2.0.1-alpha.12/go.mod h1://JlL91cS2JV7rOl6LVHrRq
github.com/posener/script v1.0.4 h1:nSuXW5ZdmFnQIueLB2s0qvs4oNsUloM1Zydzh75v42w= github.com/posener/script v1.0.4 h1:nSuXW5ZdmFnQIueLB2s0qvs4oNsUloM1Zydzh75v42w=
github.com/posener/script v1.0.4/go.mod h1:Rg3ijooqulo05aGLyGsHoLmIOUzHUVK19WVgrYBPU/E= github.com/posener/script v1.0.4/go.mod h1:Rg3ijooqulo05aGLyGsHoLmIOUzHUVK19WVgrYBPU/E=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
@@ -164,5 +162,7 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
sigs.k8s.io/kustomize/kyaml v0.0.4 h1:kN8+jANVkVVdMCfgIcG42Yd4uy4Jd3wZaGx5DpF6MUE=
sigs.k8s.io/kustomize/kyaml v0.0.4/go.mod h1:waxTrzQRK9i6/5fR5HNo8xa4YwvWn8t85vMnOGFEZik=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -1,103 +0,0 @@
// 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
}

View File

@@ -1,488 +0,0 @@
// 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: expectedSingleValue,
},
{
name: "multi value",
args: []string{"--kv", "a=b", "--kv", "c=d"},
expected: expectedMultiValue,
},
{
name: "filter kind",
args: []string{"--kv", "a=b", "--kind", "Service"},
expected: expectedFilterKindService,
},
{
name: "filter apiVersion",
args: []string{"--kv", "a=b", "--apiVersion", "v1"},
expected: expectedFilterApiVersionV1,
},
{
name: "filter name",
args: []string{"--kv", "a=b", "--name", "bar"},
expected: expectedFilterNameBar,
},
{
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 := initTestDir(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 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(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:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`
f2Input = `
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
`
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
`
)

View File

@@ -121,13 +121,6 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
out = o out = o
} }
// remove this annotation explicitly, the ByteWriter won't clear it by
// default because it doesn't set it
clear := []string{"config.kubernetes.io/path"}
if r.KeepAnnotations {
clear = nil
}
var outputs []kio.Writer var outputs []kio.Writer
outputs = append(outputs, kio.ByteWriter{ outputs = append(outputs, kio.ByteWriter{
Writer: out, Writer: out,
@@ -136,7 +129,6 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
WrappingAPIVersion: r.WrapApiVersion, WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig, FunctionConfig: functionConfig,
Style: yaml.GetStyle(r.Styles...), Style: yaml.GetStyle(r.Styles...),
ClearAnnotations: clear,
}) })
return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute()) return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute())

View File

@@ -90,6 +90,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
replicas: 1 replicas: 1
--- ---
@@ -98,6 +100,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
selector: selector:
app: nginx app: nginx
@@ -110,6 +114,8 @@ metadata:
app: nginx app: nginx
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec: spec:
replicas: 3 replicas: 3
`, b.String()) { `, b.String()) {
@@ -190,6 +196,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
replicas: 1 replicas: 1
--- ---
@@ -198,6 +206,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
selector: selector:
app: nginx app: nginx
@@ -208,6 +218,8 @@ metadata:
name: foo name: foo
annotations: annotations:
config.kubernetes.io/local-config: "true" config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
configFn: configFn:
container: container:
image: gcr.io/example/image:version image: gcr.io/example/image:version
@@ -221,6 +233,8 @@ metadata:
name: bar name: bar
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec: spec:
replicas: 3 replicas: 3
`, b.String()) { `, b.String()) {
@@ -300,6 +314,8 @@ metadata:
name: foo name: foo
annotations: annotations:
config.kubernetes.io/local-config: "true" config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
configFn: configFn:
container: container:
image: gcr.io/example/reconciler:v1 image: gcr.io/example/reconciler:v1
@@ -398,6 +414,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
replicas: 1 replicas: 1
--- ---
@@ -406,6 +424,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
selector: selector:
app: nginx app: nginx
@@ -418,6 +438,8 @@ metadata:
app: nginx app: nginx
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec: spec:
replicas: 3 replicas: 3
`, string(actual)) { `, string(actual)) {
@@ -514,6 +536,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
replicas: 1 replicas: 1
--- ---
@@ -522,6 +546,8 @@ metadata:
name: foo name: foo
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec: spec:
selector: selector:
app: nginx app: nginx
@@ -534,6 +560,8 @@ metadata:
app: nginx app: nginx
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec: spec:
replicas: 3 replicas: 3
`, string(actual)) { `, string(actual)) {

View File

@@ -5,11 +5,9 @@ package commands
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/setters" "sigs.k8s.io/kustomize/kyaml/setters"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
) )
// NewCreateSetterRunner returns a command runner. // NewCreateSetterRunner returns a command runner.
@@ -25,28 +23,20 @@ func NewCreateSetterRunner(parent string) *CreateSetterRunner {
RunE: r.runE, RunE: r.runE,
} }
set.Flags().StringVar(&r.Set.SetPartialField.SetBy, "set-by", "", set.Flags().StringVar(&r.Set.SetPartialField.SetBy, "set-by", "",
"record who the field was default by.") "set the setBy annotation.")
set.Flags().StringVar(&r.Set.SetPartialField.Description, "description", "", set.Flags().StringVar(&r.Set.SetPartialField.Description, "description", "",
"record a description for the current setter value.") "set the description of the field value.")
set.Flags().StringVar(&r.Set.SetPartialField.Field, "field", "", set.Flags().StringVar(&r.Set.SetPartialField.Field, "field", "",
"name of the field to set -- e.g. --field port. defaults to all fields match"+ "name of the field to set -- e.g. --field port")
"VALUE. maybe be the field name, field path, or partial field path (suffix)")
set.Flags().StringVar(&r.Set.ResourceMeta.Name, "name", "", set.Flags().StringVar(&r.Set.ResourceMeta.Name, "name", "",
"name of the Resource on which to create the setter.") "name of the Resource on which to create the setter.")
set.Flags().MarkHidden("name")
set.Flags().StringVar(&r.Set.ResourceMeta.Kind, "kind", "", set.Flags().StringVar(&r.Set.ResourceMeta.Kind, "kind", "",
"kind of the Resource on which to create the setter.") "kind of the Resource on which to create the setter.")
set.Flags().MarkHidden("kind")
set.Flags().StringVar(&r.Set.SetPartialField.Type, "type", "", set.Flags().StringVar(&r.Set.SetPartialField.Type, "type", "",
"valid OpenAPI field type -- e.g. integer,boolean,string.") "valid OpenAPI field type -- e.g. integer,boolean,string.")
set.Flags().MarkHidden("type")
set.Flags().BoolVar(&r.Set.SetPartialField.Partial, "partial", false,
"create a partial setter for only part of the field value.")
set.Flags().MarkHidden("partial")
set.Flags().StringVar(&setterVersion, "version", "",
"use this version of the setter format")
set.Flags().MarkHidden("version")
fixDocs(parent, set) fixDocs(parent, set)
set.MarkFlagRequired("type")
set.MarkFlagRequired("field")
r.Command = set r.Command = set
return r return r
} }
@@ -56,10 +46,8 @@ func CreateSetterCommand(parent string) *cobra.Command {
} }
type CreateSetterRunner struct { type CreateSetterRunner struct {
Command *cobra.Command Command *cobra.Command
Set setters.CreateSetter Set setters.CreateSetter
CreateSetter settersutil.SetterCreator
OpenAPIFile string
} }
func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error { func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error {
@@ -67,40 +55,12 @@ func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error {
} }
func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error { func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error {
var err error
r.Set.SetPartialField.Setter.Name = args[1] r.Set.SetPartialField.Setter.Name = args[1]
r.Set.SetPartialField.Setter.Value = args[2] r.Set.SetPartialField.Setter.Value = args[2]
r.CreateSetter.Name = args[1]
r.CreateSetter.FieldValue = args[2]
r.CreateSetter.FieldName, err = c.Flags().GetString("field")
if err != nil {
return err
}
if setterVersion == "" {
if len(args) < 3 {
setterVersion = "v1"
} else if err := initSetterVersion(c, args); err != nil {
return err
}
}
if setterVersion == "v2" {
var err error
r.OpenAPIFile, err = ext.GetOpenAPIFile(args)
r.CreateSetter.Description = r.Set.SetPartialField.Description
r.CreateSetter.SetBy = r.Set.SetPartialField.SetBy
if err != nil {
return err
}
}
return nil return nil
} }
func (r *CreateSetterRunner) set(c *cobra.Command, args []string) error { func (r *CreateSetterRunner) set(c *cobra.Command, args []string) error {
if setterVersion == "v2" {
return r.CreateSetter.Create(r.OpenAPIFile, args[0])
}
rw := &kio.LocalPackageReadWriter{PackagePath: args[0]} rw := &kio.LocalPackageReadWriter{PackagePath: args[0]}
err := kio.Pipeline{ err := kio.Pipeline{
Inputs: []kio.Reader{rw}, Inputs: []kio.Reader{rw},

View File

@@ -1,131 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
"sigs.k8s.io/kustomize/kyaml/openapi"
)
func TestCreateSetterCommand(t *testing.T) {
var tests = []struct {
name string
input string
args []string
out string
expectedOpenAPI string
expectedResources string
}{
{
name: "add replicas",
args: []string{"replicas", "3", "--description", "hello world", "--set-by", "me"},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// reset the openAPI afterward
openapi.ResetOpenAPI()
defer openapi.ResetOpenAPI()
f, err := ioutil.TempFile("", "k8s-cli-")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), []byte(`
apiVersion: v1alpha1
kind: Example
`), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
old := ext.GetOpenAPIFile
defer func() { ext.GetOpenAPIFile = old }()
ext.GetOpenAPIFile = func(args []string) (s string, err error) {
return f.Name(), nil
}
r, err := ioutil.TempFile("", "k8s-cli-*.yaml")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(r.Name())
err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
runner := commands.NewCreateSetterRunner("")
out := &bytes.Buffer{}
runner.Command.SetOut(out)
runner.Command.SetArgs(append([]string{r.Name()}, test.args...))
err = runner.Command.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, test.out, out.String()) {
t.FailNow()
}
actualResources, err := ioutil.ReadFile(r.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedResources),
strings.TrimSpace(string(actualResources))) {
t.FailNow()
}
actualOpenAPI, err := ioutil.ReadFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedOpenAPI),
strings.TrimSpace(string(actualOpenAPI))) {
t.FailNow()
}
})
}
}

View File

@@ -1,81 +0,0 @@
// 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/ext"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
)
// NewCreateSubstitutionRunner returns a command runner.
func NewCreateSubstitutionRunner(parent string) *CreateSubstitutionRunner {
r := &CreateSubstitutionRunner{}
cs := &cobra.Command{
Use: "create-subst DIR NAME VALUE",
Args: cobra.ExactArgs(3),
PreRunE: r.preRunE,
RunE: r.runE,
}
cs.Flags().StringVar(&r.CreateSubstitution.FieldName, "field", "",
"name of the field to set -- e.g. --field port")
cs.Flags().StringVar(&r.CreateSubstitution.Pattern, "pattern", "",
"substitution pattern")
cs.Flags().StringSliceVar(&r.Values, "value", []string{""},
"substitution values for the pattern. format is PATTERN_MARKER=SETTER_NAME"+
"where PATTERN_MARKER is the pattern substring to replace, and SETTER_NAME is the"+
"setter from which to take the replacement value.")
_ = cs.MarkFlagRequired("pattern")
fixDocs(parent, cs)
r.Command = cs
return r
}
func CreateSubstitutionCommand(parent string) *cobra.Command {
return NewCreateSubstitutionRunner(parent).Command
}
type CreateSubstitutionRunner struct {
Command *cobra.Command
CreateSubstitution settersutil.SubstitutionCreator
OpenAPIFile string
Values []string
}
func (r *CreateSubstitutionRunner) runE(c *cobra.Command, args []string) error {
return handleError(c, r.CreateSubstitution.Create(r.OpenAPIFile, args[0]))
}
func (r *CreateSubstitutionRunner) preRunE(c *cobra.Command, args []string) error {
var err error
r.CreateSubstitution.Name = args[1]
r.CreateSubstitution.FieldValue = args[2]
if err != nil {
return err
}
r.OpenAPIFile, err = ext.GetOpenAPIFile(args)
if err != nil {
return err
}
// parse the marker values
for i := range r.Values {
parts := strings.SplitN(r.Values[i], "=", 2)
if len(parts) < 2 {
return errors.Errorf("values must be specified as PATTERN_MARKER=SETTER_NAME")
}
ref := setters2.DefinitionsPrefix + setters2.SetterDefinitionPrefix + parts[1]
r.CreateSubstitution.Values = append(
r.CreateSubstitution.Values,
setters2.Value{Marker: parts[0], Ref: ref},
)
}
return nil
}

View File

@@ -1,174 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
"sigs.k8s.io/kustomize/kyaml/openapi"
)
func TestCreateSubstitutionCommand(t *testing.T) {
var tests = []struct {
name string
inputOpenAPI string
input string
args []string
out string
expectedOpenAPI string
expectedResources string
}{
{
name: "substitution replicas",
args: []string{
"image", "nginx:1.7.9", "--pattern", "IMAGE:TAG",
"--value", "IMAGE=image", "--value", "TAG=tag"},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9
- name: sidecar
image: sidecar:1.7.9
`,
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.image:
x-k8s-cli:
setter:
name: image
value: "nginx"
io.k8s.cli.setters.tag:
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.image:
x-k8s-cli:
setter:
name: image
value: "nginx"
io.k8s.cli.setters.tag:
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref":"#/definitions/io.k8s.cli.substitutions.image"}
- name: sidecar
image: sidecar:1.7.9
`,
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// reset the openAPI afterward
openapi.ResetOpenAPI()
defer openapi.ResetOpenAPI()
f, err := ioutil.TempFile("", "k8s-cli-")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), []byte(test.inputOpenAPI), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
old := ext.GetOpenAPIFile
defer func() { ext.GetOpenAPIFile = old }()
ext.GetOpenAPIFile = func(args []string) (s string, err error) {
return f.Name(), nil
}
r, err := ioutil.TempFile("", "k8s-cli-*.yaml")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(r.Name())
err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
runner := commands.NewCreateSubstitutionRunner("")
out := &bytes.Buffer{}
runner.Command.SetOut(out)
runner.Command.SetArgs(append([]string{r.Name()}, test.args...))
err = runner.Command.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, test.out, out.String()) {
t.FailNow()
}
actualResources, err := ioutil.ReadFile(r.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedResources),
strings.TrimSpace(string(actualResources))) {
t.FailNow()
}
actualOpenAPI, err := ioutil.ReadFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedOpenAPI),
strings.TrimSpace(string(actualOpenAPI))) {
t.FailNow()
}
})
}
}

View File

@@ -1,96 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"fmt"
"io"
"os"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/setters"
"sigs.k8s.io/kustomize/kyaml/setters2"
)
// NewListSettersRunner returns a command runner.
func NewListSettersRunner(parent string) *ListSettersRunner {
r := &ListSettersRunner{}
c := &cobra.Command{
Use: "list-setters DIR [NAME]",
Args: cobra.RangeArgs(1, 2),
Short: commands.ListSettersShort,
Long: commands.ListSettersLong,
Example: commands.ListSettersExamples,
PreRunE: r.preRunE,
RunE: r.runE,
}
fixDocs(parent, c)
r.Command = c
return r
}
func ListSettersCommand(parent string) *cobra.Command {
return NewListSettersRunner(parent).Command
}
type ListSettersRunner struct {
Command *cobra.Command
Lookup setters.LookupSetters
List setters2.List
}
func (r *ListSettersRunner) preRunE(c *cobra.Command, args []string) error {
if len(args) > 1 {
r.Lookup.Name = args[1]
r.List.Name = args[1]
}
initSetterVersion(c, args)
return nil
}
func (r *ListSettersRunner) runE(c *cobra.Command, args []string) error {
if setterVersion == "v2" {
// use setters v2
path, err := ext.GetOpenAPIFile(args)
if err != nil {
return err
}
if err := r.List.List(path, args[0]); err != nil {
return err
}
table := newTable(c.OutOrStdout())
table.SetHeader([]string{"NAME", "VALUE", "SET BY", "DESCRIPTION", "COUNT"})
for i := range r.List.Setters {
s := r.List.Setters[i]
table.Append([]string{
s.Name, s.Value, s.SetBy, s.Description, fmt.Sprintf("%d", s.Count)})
}
table.Render()
if len(r.List.Setters) == 0 {
// exit non-0 if no matching setters are found
if ExitOnError {
os.Exit(1)
}
}
return nil
}
return handleError(c, lookup(r.Lookup, c, args))
}
func newTable(o io.Writer) *tablewriter.Table {
table := tablewriter.NewWriter(o)
table.SetRowLine(false)
table.SetBorder(false)
table.SetHeaderLine(false)
table.SetColumnSeparator(" ")
table.SetCenterSeparator(" ")
table.SetAlignment(tablewriter.ALIGN_LEFT)
return table
}

View File

@@ -1,299 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
"sigs.k8s.io/kustomize/kyaml/openapi"
)
func TestListSettersCommand(t *testing.T) {
var tests = []struct {
name string
openapi string
input string
args []string
expected string
}{
{
name: "list-replicas",
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.replicas:
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
description: "hello world"
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`,
expected: ` NAME VALUE SET BY DESCRIPTION COUNT
replicas 3 me hello world 1
`,
},
{
name: "list-multiple",
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: "hello world 1"
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me1
io.k8s.cli.setters.image:
description: "hello world 2"
x-k8s-cli:
setter:
name: image
value: "nginx"
setBy: me2
io.k8s.cli.setters.tag:
description: "hello world 3"
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
setBy: me3
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
- name: nginx2
image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"}
`,
expected: ` NAME VALUE SET BY DESCRIPTION COUNT
image nginx me2 hello world 2 2
replicas 3 me1 hello world 1 1
tag 1.7.9 me3 hello world 3 1
`,
},
{
name: "list-multiple-resources",
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: "hello world 1"
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me1
io.k8s.cli.setters.image:
description: "hello world 2"
x-k8s-cli:
setter:
name: image
value: "nginx"
setBy: me2
io.k8s.cli.setters.tag:
description: "hello world 3"
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
setBy: me3
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-1
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
- name: nginx2
image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-2
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
- name: nginx2
image: nginx
`,
expected: ` NAME VALUE SET BY DESCRIPTION COUNT
image nginx me2 hello world 2 3
replicas 3 me1 hello world 1 2
tag 1.7.9 me3 hello world 3 2
`,
},
{
name: "list-name",
args: []string{"image"},
openapi: `
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: "hello world 1"
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me1
io.k8s.cli.setters.image:
description: "hello world 2"
x-k8s-cli:
setter:
name: image
value: "nginx"
setBy: me2
io.k8s.cli.setters.tag:
description: "hello world 3"
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
setBy: me3
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-1
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
- name: nginx2
image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-2
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
- name: nginx2
image: nginx
`,
expected: ` NAME VALUE SET BY DESCRIPTION COUNT
image nginx me2 hello world 2 3
`,
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// reset the openAPI afterward
openapi.ResetOpenAPI()
defer openapi.ResetOpenAPI()
f, err := ioutil.TempFile("", "k8s-cli-")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(f.Name())
old := ext.GetOpenAPIFile
defer func() { ext.GetOpenAPIFile = old }()
ext.GetOpenAPIFile = func(args []string) (s string, err error) {
err = ioutil.WriteFile(f.Name(), []byte(test.openapi), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
return f.Name(), nil
}
r, err := ioutil.TempFile("", "k8s-cli-*.yaml")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(r.Name())
err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
runner := commands.NewListSettersRunner("")
actual := &bytes.Buffer{}
runner.Command.SetOut(actual)
runner.Command.SetArgs(append([]string{r.Name()}, test.args...))
err = runner.Command.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, test.expected, actual.String()) {
t.FailNow()
}
})
}
}

View File

@@ -5,22 +5,19 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/setters" "sigs.k8s.io/kustomize/kyaml/setters"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
) )
// NewSetRunner returns a command runner. // NewSetRunner returns a command runner.
func NewSetRunner(parent string) *SetRunner { func NewSetRunner(parent string) *SetRunner {
r := &SetRunner{} r := &SetRunner{}
c := &cobra.Command{ c := &cobra.Command{
Use: "set DIR NAME [VALUE]", Use: "set DIR [NAME] [VALUE]",
Args: cobra.RangeArgs(1, 3), Args: cobra.RangeArgs(1, 3),
Short: commands.SetShort, Short: commands.SetShort,
Long: commands.SetLong, Long: commands.SetLong,
@@ -30,48 +27,18 @@ func NewSetRunner(parent string) *SetRunner {
} }
fixDocs(parent, c) fixDocs(parent, c)
r.Command = c r.Command = c
c.Flags().StringVar(&r.Perform.SetBy, "set-by", "",
"annotate the field with who set it")
c.Flags().StringVar(&r.Perform.Description, "description", "",
"annotate the field with a description of its value")
c.Flags().StringVar(&setterVersion, "version", "",
"use this version of the setter format")
c.Flags().MarkHidden("version")
return r return r
} }
var setterVersion string
func SetCommand(parent string) *cobra.Command { func SetCommand(parent string) *cobra.Command {
return NewSetRunner(parent).Command return NewSetRunner(parent).Command
} }
type SetRunner struct { type SetRunner struct {
Command *cobra.Command Command *cobra.Command
Lookup setters.LookupSetters Lookup setters.LookupSetters
Perform setters.PerformSetters Perform setters.PerformSetters
Set settersutil.FieldSetter
OpenAPIFile string
}
func initSetterVersion(c *cobra.Command, args []string) error {
setterVersion = "v2"
l := setters.LookupSetters{}
// backwards compatibility for resources with setter v1
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}},
Filters: []kio.Filter{&l},
}.Execute()
if err != nil {
return err
}
if len(l.SetterCounts) > 0 {
setterVersion = "v1"
}
return nil
} }
func (r *SetRunner) preRunE(c *cobra.Command, args []string) error { func (r *SetRunner) preRunE(c *cobra.Command, args []string) error {
@@ -83,45 +50,23 @@ func (r *SetRunner) preRunE(c *cobra.Command, args []string) error {
r.Perform.Value = args[2] r.Perform.Value = args[2]
} }
if setterVersion == "" {
if len(args) < 3 {
setterVersion = "v1"
} else if err := initSetterVersion(c, args); err != nil {
return err
}
}
if setterVersion == "v2" {
var err error
r.Set.Name = args[1]
r.Set.Value = args[2]
r.Set.Description = r.Perform.Description
r.Set.SetBy = r.Perform.SetBy
r.OpenAPIFile, err = ext.GetOpenAPIFile(args)
if err != nil {
return err
}
}
return nil return nil
} }
func (r *SetRunner) runE(c *cobra.Command, args []string) error { func (r *SetRunner) runE(c *cobra.Command, args []string) error {
if setterVersion == "v2" {
count, err := r.Set.Set(r.OpenAPIFile, args[0])
fmt.Fprintf(c.OutOrStdout(), "set %d fields\n", count)
return handleError(c, err)
}
if len(args) == 3 { if len(args) == 3 {
return handleError(c, r.perform(c, args)) return handleError(c, r.perform(c, args))
} }
return handleError(c, lookup(r.Lookup, c, args))
return handleError(c, r.lookup(c, args))
} }
func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error { func (r *SetRunner) lookup(c *cobra.Command, args []string) error {
// lookup the setters // lookup the setters
err := kio.Pipeline{ err := kio.Pipeline{
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}}, Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}},
Filters: []kio.Filter{&l}, Filters: []kio.Filter{&r.Lookup},
}.Execute() }.Execute()
if err != nil { if err != nil {
return err return err
@@ -137,8 +82,8 @@ func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error {
table.SetHeader([]string{ table.SetHeader([]string{
"NAME", "DESCRIPTION", "VALUE", "TYPE", "COUNT", "SETBY", "NAME", "DESCRIPTION", "VALUE", "TYPE", "COUNT", "SETBY",
}) })
for i := range l.SetterCounts { for i := range r.Lookup.SetterCounts {
s := l.SetterCounts[i] s := r.Lookup.SetterCounts[i]
v := s.Value v := s.Value
if s.Value == "" { if s.Value == "" {
v = s.Value v = s.Value
@@ -153,11 +98,6 @@ func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error {
}) })
} }
table.Render() table.Render()
if len(l.SetterCounts) == 0 {
// exit non-0 if no matching setters are found
os.Exit(1)
}
return nil return nil
} }

View File

@@ -1,278 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
"sigs.k8s.io/kustomize/kyaml/openapi"
)
func TestSetCommand(t *testing.T) {
var tests = []struct {
name string
inputOpenAPI string
input string
args []string
out string
expectedOpenAPI string
expectedResources string
}{
{
name: "set replicas",
args: []string{"replicas", "4", "--description", "hi there", "--set-by", "pw"},
out: "set 1 fields\n",
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hi there
x-k8s-cli:
setter:
name: replicas
value: "4"
setBy: pw
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 4 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "set replicas no description",
args: []string{"replicas", "4"},
out: "set 1 fields\n",
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "3"
setBy: me
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.replicas:
description: hello world
x-k8s-cli:
setter:
name: replicas
value: "4"
setBy: me
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 4 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "set image",
args: []string{"tag", "1.8.1"},
out: "set 1 fields\n",
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.image:
x-k8s-cli:
setter:
name: image
value: "nginx"
io.k8s.cli.setters.tag:
x-k8s-cli:
setter:
name: tag
value: "1.7.9"
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref":"#/definitions/io.k8s.cli.substitutions.image"}
- name: sidecar
image: sidecar:1.7.9
`,
expectedOpenAPI: `
apiVersion: v1alpha1
kind: Example
openAPI:
definitions:
io.k8s.cli.setters.image:
x-k8s-cli:
setter:
name: image
value: "nginx"
io.k8s.cli.setters.tag:
x-k8s-cli:
setter:
name: tag
value: "1.8.1"
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE:TAG
values:
- marker: IMAGE
ref: '#/definitions/io.k8s.cli.setters.image'
- marker: TAG
ref: '#/definitions/io.k8s.cli.setters.tag'
`,
expectedResources: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
spec:
containers:
- name: nginx
image: nginx:1.8.1 # {"$ref":"#/definitions/io.k8s.cli.substitutions.image"}
- name: sidecar
image: sidecar:1.7.9
`,
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// reset the openAPI afterward
openapi.ResetOpenAPI()
defer openapi.ResetOpenAPI()
f, err := ioutil.TempFile("", "k8s-cli-")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), []byte(test.inputOpenAPI), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
old := ext.GetOpenAPIFile
defer func() { ext.GetOpenAPIFile = old }()
ext.GetOpenAPIFile = func(args []string) (s string, err error) {
return f.Name(), nil
}
r, err := ioutil.TempFile("", "k8s-cli-*.yaml")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.Remove(r.Name())
err = ioutil.WriteFile(r.Name(), []byte(test.input), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
runner := commands.NewSetRunner("")
out := &bytes.Buffer{}
runner.Command.SetOut(out)
runner.Command.SetArgs(append([]string{r.Name()}, test.args...))
err = runner.Command.Execute()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, test.out, out.String()) {
t.FailNow()
}
actualResources, err := ioutil.ReadFile(r.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedResources),
strings.TrimSpace(string(actualResources))) {
t.FailNow()
}
actualOpenAPI, err := ioutil.ReadFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(test.expectedOpenAPI),
strings.TrimSpace(string(actualOpenAPI))) {
t.FailNow()
}
})
}
}

View File

@@ -31,8 +31,6 @@ formatting substitution verbs {'%n': 'metadata.name', '%s': 'metadata.namespace'
`if true, keep index and filename annotations set on Resources.`) `if true, keep index and filename annotations set on Resources.`)
c.Flags().BoolVar(&r.Override, "override", false, c.Flags().BoolVar(&r.Override, "override", false,
`if true, override existing filepath annotations.`) `if true, override existing filepath annotations.`)
c.Flags().BoolVar(&r.UseSchema, "use-schema", false,
`if true, uses openapi resource schema to format resources.`)
r.Command = c r.Command = c
return r return r
} }
@@ -48,7 +46,6 @@ type FmtRunner struct {
SetFilenames bool SetFilenames bool
KeepAnnotations bool KeepAnnotations bool
Override bool Override bool
UseSchema bool
} }
func (r *FmtRunner) preRunE(c *cobra.Command, args []string) error { func (r *FmtRunner) preRunE(c *cobra.Command, args []string) error {
@@ -59,9 +56,7 @@ func (r *FmtRunner) preRunE(c *cobra.Command, args []string) error {
} }
func (r *FmtRunner) runE(c *cobra.Command, args []string) error { func (r *FmtRunner) runE(c *cobra.Command, args []string) error {
f := []kio.Filter{filters.FormatFilter{ f := []kio.Filter{filters.FormatFilter{}}
UseSchema: r.UseSchema,
}}
// format with file names // format with file names
if r.SetFilenames { if r.SetFilenames {

View File

@@ -143,8 +143,6 @@ func TestCmd_failFiles(t *testing.T) {
// fmt the files // fmt the files
r := commands.GetFmtRunner("") r := commands.GetFmtRunner("")
r.Command.SetArgs([]string{"notrealfile"}) r.Command.SetArgs([]string{"notrealfile"})
r.Command.SilenceUsage = true
r.Command.SilenceErrors = true
err := r.Command.Execute() err := r.Command.Execute()
assert.EqualError(t, err, "lstat notrealfile: no such file or directory") assert.EqualError(t, err, "lstat notrealfile: no such file or directory")
} }

View File

@@ -76,6 +76,7 @@ metadata:
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/index: '0' config.kubernetes.io/index: '0'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml' config.kubernetes.io/path: 'f1.yaml'
spec: spec:
replicas: 1 replicas: 1
@@ -86,6 +87,7 @@ metadata:
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/index: '1' config.kubernetes.io/index: '1'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml' config.kubernetes.io/path: 'f1.yaml'
spec: spec:
selector: selector:

View File

@@ -25,8 +25,6 @@ func GetMerge3Runner(name string) *Merge3Runner {
"Path to updated package") "Path to updated package")
c.Flags().StringVar(&r.toDir, "to", "", c.Flags().StringVar(&r.toDir, "to", "",
"Path to destination package") "Path to destination package")
c.Flags().BoolVar(&r.path, "path-merge-key", false,
"Use the path as part of the merge key when merging resources")
r.Command = c r.Command = c
return r return r
@@ -42,7 +40,6 @@ type Merge3Runner struct {
ancestor string ancestor string
fromDir string fromDir string
toDir string toDir string
path bool
} }
func (r *Merge3Runner) runE(c *cobra.Command, args []string) error { func (r *Merge3Runner) runE(c *cobra.Command, args []string) error {
@@ -50,7 +47,6 @@ func (r *Merge3Runner) runE(c *cobra.Command, args []string) error {
OriginalPath: r.ancestor, OriginalPath: r.ancestor,
UpdatedPath: r.fromDir, UpdatedPath: r.fromDir,
DestPath: r.toDir, DestPath: r.toDir,
MergeOnPath: r.path,
}.Merge() }.Merge()
if err != nil { if err != nil {
return err return err

View File

@@ -4,27 +4,22 @@
package commands package commands
import ( import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands" "sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/runfn" "sigs.k8s.io/kustomize/kyaml/runfn"
"sigs.k8s.io/kustomize/kyaml/yaml"
) )
// GetCatRunner returns a RunFnRunner. // GetCatRunner returns a RunFnRunner.
func GetRunFnRunner(name string) *RunFnRunner { func GetRunFnRunner(name string) *RunFnRunner {
r := &RunFnRunner{} r := &RunFnRunner{}
c := &cobra.Command{ c := &cobra.Command{
Use: "run [DIR]", Use: "run DIR",
Aliases: []string{"run-fns"},
Short: commands.RunFnsShort, Short: commands.RunFnsShort,
Long: commands.RunFnsLong, Long: commands.RunFnsLong,
Example: commands.RunFnsExamples, Example: commands.RunFnsExamples,
RunE: r.runE, RunE: r.runE,
PreRunE: r.preRunE, Args: cobra.ExactArgs(1),
} }
fixDocs(name, c) fixDocs(name, c)
c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true, c.Flags().BoolVar(&r.IncludeSubpackages, "include-subpackages", true,
@@ -32,18 +27,11 @@ func GetRunFnRunner(name string) *RunFnRunner {
r.Command = c r.Command = c
r.Command.Flags().BoolVar( r.Command.Flags().BoolVar(
&r.DryRun, "dry-run", false, "print results to stdout") &r.DryRun, "dry-run", false, "print results to stdout")
r.Command.Flags().BoolVar(
&r.GlobalScope, "global-scope", false, "set global scope for functions.")
r.Command.Flags().StringSliceVar( r.Command.Flags().StringSliceVar(
&r.FnPaths, "fn-path", []string{}, &r.FnPaths, "fn-path", []string{},
"read functions from these directories instead of the configuration directory.") "directories containing functions without configuration")
r.Command.Flags().StringVar( r.Command.AddCommand(XArgsCommand())
&r.Image, "image", "", r.Command.AddCommand(WrapCommand())
"run this image as a function instead of discovering them.")
r.Command.Flags().BoolVar(
&r.Network, "network", false, "enable network access for functions that declare it")
r.Command.Flags().StringVar(
&r.NetworkName, "network-name", "bridge", "the docker network to run the container in")
return r return r
} }
@@ -56,148 +44,13 @@ type RunFnRunner struct {
IncludeSubpackages bool IncludeSubpackages bool
Command *cobra.Command Command *cobra.Command
DryRun bool DryRun bool
GlobalScope bool
FnPaths []string FnPaths []string
Image string
RunFns runfn.RunFns
Network bool
NetworkName string
} }
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error { func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
return handleError(c, r.RunFns.Execute()) rec := runfn.RunFns{Path: args[0], FunctionPaths: r.FnPaths}
} if r.DryRun {
rec.Output = c.OutOrStdout()
// getFunctions parses the commandline flags and arguments into explicit }
// Functions to run. return handleError(c, rec.Execute())
func (r *RunFnRunner) getFunctions(c *cobra.Command, args, dataItems []string) (
[]*yaml.RNode, error) {
// if image isn't specified, then Functions is empty
if r.Image == "" {
return nil, nil
}
// create the function spec to set as an annotation
fn, err := yaml.Parse(`container: {}`)
if err != nil {
return nil, err
}
// TODO: add support network, volumes, etc based on flag values
err = fn.PipeE(
yaml.Lookup("container"),
yaml.SetField("image", yaml.NewScalarRNode(r.Image)))
if err != nil {
return nil, err
}
// create the function config
rc, err := yaml.Parse(`
metadata:
name: function-input
data: {}
`)
if err != nil {
return nil, err
}
// set the function annotation on the function config so it
// is parsed by RunFns
value, err := fn.String()
if err != nil {
return nil, err
}
err = rc.PipeE(
yaml.LookupCreate(yaml.MappingNode, "metadata", "annotations"),
yaml.SetField("config.kubernetes.io/function", yaml.NewScalarRNode(value)))
if err != nil {
return nil, err
}
// default the function config kind to ConfigMap, this may be overridden
var kind = "ConfigMap"
var version = "v1"
// populate the function config with data. this is a convention for functions
// to be more commandline friendly
if len(dataItems) > 0 {
dataField, err := rc.Pipe(yaml.Lookup("data"))
if err != nil {
return nil, err
}
for i, s := range dataItems {
kv := strings.SplitN(s, "=", 2)
if i == 0 && len(kv) == 1 {
// first argument may be the kind
kind = s
continue
}
if len(kv) != 2 {
return nil, fmt.Errorf("args must have keys and values separated by =")
}
err := dataField.PipeE(yaml.SetField(kv[0], yaml.NewScalarRNode(kv[1])))
if err != nil {
return nil, err
}
}
}
err = rc.PipeE(yaml.SetField("kind", yaml.NewScalarRNode(kind)))
if err != nil {
return nil, err
}
err = rc.PipeE(yaml.SetField("apiVersion", yaml.NewScalarRNode(version)))
if err != nil {
return nil, err
}
return []*yaml.RNode{rc}, nil
}
func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
if c.ArgsLenAtDash() >= 0 && r.Image == "" {
return errors.Errorf("must specify --image")
}
var dataItems []string
if c.ArgsLenAtDash() >= 0 {
dataItems = args[c.ArgsLenAtDash():]
args = args[:c.ArgsLenAtDash()]
}
if len(args) > 1 {
return errors.Errorf("0 or 1 arguments supported, function arguments go after '--'")
}
fns, err := r.getFunctions(c, args, dataItems)
if err != nil {
return err
}
// set the output to stdout if in dry-run mode or no arguments are specified
var output io.Writer
var input io.Reader
if len(args) == 0 {
output = c.OutOrStdout()
input = c.InOrStdin()
} else if r.DryRun {
output = c.OutOrStdout()
}
// set the path if specified as an argument
var path string
if len(args) == 1 {
// argument is the directory
path = args[0]
}
r.RunFns = runfn.RunFns{
FunctionPaths: r.FnPaths,
GlobalScope: r.GlobalScope,
Functions: fns,
Output: output,
Input: input,
Path: path,
Network: r.Network,
NetworkName: r.NetworkName,
}
// don't consider args for the function
return nil
} }

View File

@@ -1,282 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"io"
"os"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
// flags and arguments into the RunFns structure to be executed.
func TestRunFnCommand_preRunE(t *testing.T) {
tests := []struct {
name string
args []string
expected string
err string
path string
input io.Reader
output io.Writer
functionPaths []string
network bool
networkName string
}{
{
name: "config map",
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
path: "dir",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {a: b, c: d, e: f}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "config map stdin / stdout",
args: []string{"run", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
input: os.Stdin,
output: os.Stdout,
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {a: b, c: d, e: f}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "config map dry-run",
args: []string{"run", "dir", "--image", "foo:bar", "--dry-run", "--", "a=b", "c=d", "e=f"},
output: os.Stdout,
path: "dir",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {a: b, c: d, e: f}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "config map no args",
args: []string{"run", "dir", "--image", "foo:bar"},
path: "dir",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "network enabled",
args: []string{"run", "dir", "--image", "foo:bar", "--network"},
path: "dir",
network: true,
networkName: "bridge",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "with network name",
args: []string{"run", "dir", "--image", "foo:bar", "--network", "--network-name", "foo"},
path: "dir",
network: true,
networkName: "foo",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "custom kind",
args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h"},
path: "dir",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {g: h}
kind: Foo
apiVersion: v1
`,
},
{
name: "custom kind '=' in data",
args: []string{"run", "dir", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
path: "dir",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {g: h, i: j=k}
kind: Foo
apiVersion: v1
`,
},
{
name: "function paths",
args: []string{"run", "dir", "--fn-path", "path1", "--fn-path", "path2"},
path: "dir",
functionPaths: []string{"path1", "path2"},
},
{
name: "custom kind with function paths",
args: []string{
"run", "dir", "--fn-path", "path", "--image", "foo:bar", "--", "Foo", "g=h", "i=j=k"},
path: "dir",
functionPaths: []string{"path"},
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {g: h, i: j=k}
kind: Foo
apiVersion: v1
`,
},
{
name: "config map multi args",
args: []string{"run", "dir", "dir2", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
err: "0 or 1 arguments supported",
},
{
name: "config map not image",
args: []string{"run", "dir", "--", "a=b", "c=d", "e=f"},
err: "must specify --image",
},
{
name: "config map bad data",
args: []string{"run", "dir", "--image", "foo:bar", "--", "a=b", "c", "e=f"},
err: "must have keys and values separated by",
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
r := GetRunFnRunner("kustomize")
// Don't run the actual command
r.Command.Run = nil
r.Command.RunE = func(cmd *cobra.Command, args []string) error { return nil }
r.Command.SilenceErrors = true
r.Command.SilenceUsage = true
// hack due to https://github.com/spf13/cobra/issues/42
root := &cobra.Command{Use: "root"}
root.AddCommand(r.Command)
root.SetArgs(tt.args)
// error case
err := r.Command.Execute()
if tt.err != "" {
if !assert.Error(t, err) {
t.FailNow()
}
if !assert.Contains(t, err.Error(), tt.err) {
t.FailNow()
}
// don't check anything else in error case
return
}
// non-error case
if !assert.NoError(t, err) {
t.FailNow()
}
// check if Input was set
if !assert.Equal(t, tt.input, r.RunFns.Input) {
t.FailNow()
}
// check if Output was set
if !assert.Equal(t, tt.output, r.RunFns.Output) {
t.FailNow()
}
// check if Path was set
if !assert.Equal(t, tt.path, r.RunFns.Path) {
t.FailNow()
}
// check if Network was set
if tt.network {
if !assert.Equal(t, tt.network, r.RunFns.Network) {
t.FailNow()
}
if !assert.Equal(t, tt.networkName, r.RunFns.NetworkName) {
t.FailNow()
}
} else {
if !assert.Equal(t, false, r.RunFns.Network) {
t.FailNow()
}
}
// check if FunctionPaths were set
if tt.functionPaths == nil {
// make Equal work against flag default
tt.functionPaths = []string{}
}
if !assert.Equal(t, tt.functionPaths, r.RunFns.FunctionPaths) {
t.FailNow()
}
// check if Functions were set
if tt.expected != "" {
if !assert.Len(t, r.RunFns.Functions, 1) {
t.FailNow()
}
actual := strings.TrimSpace(r.RunFns.Functions[0].MustString())
if !assert.Equal(t, strings.TrimSpace(tt.expected), actual) {
t.FailNow()
}
}
})
}
}

View File

@@ -1,53 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)
// GetSinkRunner returns a command for Sink.
func GetSinkRunner(name string) *SinkRunner {
r := &SinkRunner{}
c := &cobra.Command{
Use: "sink DIR",
Short: commands.SinkShort,
Long: commands.SinkLong,
Example: commands.SinkExamples,
RunE: r.runE,
Args: cobra.MaximumNArgs(1),
}
fixDocs(name, c)
r.Command = c
return r
}
func SinkCommand(name string) *cobra.Command {
return GetSinkRunner(name).Command
}
// SinkRunner contains the run function
type SinkRunner struct {
Command *cobra.Command
}
func (r *SinkRunner) runE(c *cobra.Command, args []string) error {
var outputs []kio.Writer
if len(args) == 1 {
outputs = []kio.Writer{&kio.LocalPackageWriter{PackagePath: args[0]}}
} else {
outputs = []kio.Writer{&kio.ByteWriter{
Writer: c.OutOrStdout(),
ClearAnnotations: []string{kioutil.PathAnnotation}},
}
}
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.ByteReader{Reader: c.InOrStdin()}},
Outputs: outputs}.Execute()
return handleError(c, err)
}

View File

@@ -1,251 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestSinkCommand(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(d)
r := commands.GetSinkRunner("")
r.Command.SetIn(bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- 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
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
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'
spec:
replicas: 3
`))
r.Command.SetArgs([]string{d})
if !assert.NoError(t, r.Command.Execute()) {
t.FailNow()
}
actual, err := ioutil.ReadFile(filepath.Join(d, "f1.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected := `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
`
if !assert.Equal(t, expected, string(actual)) {
t.FailNow()
}
actual, err = ioutil.ReadFile(filepath.Join(d, "f2.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected = `apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`
if !assert.Equal(t, expected, string(actual)) {
t.FailNow()
}
}
func TestSinkCommand_Stdout(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(d)
// fmt the files
out := &bytes.Buffer{}
r := commands.GetSinkRunner("")
r.Command.SetIn(bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- 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
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
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'
spec:
replicas: 3
`))
r.Command.SetOut(out)
r.Command.SetArgs([]string{})
if !assert.NoError(t, r.Command.Execute()) {
t.FailNow()
}
expected := `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
---
apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`
if !assert.Equal(t, expected, out.String()) {
t.FailNow()
}
}

View File

@@ -1,82 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"fmt"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// GetSourceRunner returns a command for Source.
func GetSourceRunner(name string) *SourceRunner {
r := &SourceRunner{}
c := &cobra.Command{
Use: "source DIR",
Short: commands.SourceShort,
Long: commands.SourceLong,
Example: commands.SourceExamples,
RunE: r.runE,
}
fixDocs(name, c)
c.Flags().StringVar(&r.WrapKind, "wrap-kind", kio.ResourceListKind,
"output using this format.")
c.Flags().StringVar(&r.WrapApiVersion, "wrap-version", kio.ResourceListAPIVersion,
"output using this format.")
c.Flags().StringVar(&r.FunctionConfig, "function-config", "",
"path to function config.")
r.Command = c
_ = c.MarkFlagFilename("function-config", "yaml", "json", "yml")
return r
}
func SourceCommand(name string) *cobra.Command {
return GetSourceRunner(name).Command
}
// SourceRunner contains the run function
type SourceRunner struct {
WrapKind string
WrapApiVersion string
FunctionConfig string
Command *cobra.Command
}
func (r *SourceRunner) runE(c *cobra.Command, args []string) error {
// if there is a function-config specified, emit it
var functionConfig *yaml.RNode
if r.FunctionConfig != "" {
configs, err := kio.LocalPackageReader{PackagePath: r.FunctionConfig}.Read()
if err != nil {
return err
}
if len(configs) != 1 {
return fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs))
}
functionConfig = configs[0]
}
var outputs []kio.Writer
outputs = append(outputs, kio.ByteWriter{
Writer: c.OutOrStdout(),
KeepReaderAnnotations: true,
WrappingKind: r.WrapKind,
WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig,
})
var inputs []kio.Reader
for _, a := range args {
inputs = append(inputs, kio.LocalPackageReader{PackagePath: a})
}
if len(inputs) == 0 {
inputs = []kio.Reader{&kio.ByteReader{Reader: c.InOrStdin()}}
}
err := kio.Pipeline{Inputs: inputs, Outputs: outputs}.Execute()
return handleError(c, err)
}

View File

@@ -1,200 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestSourceCommand(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
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) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetSourceRunner("")
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- 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
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
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'
spec:
replicas: 3
`, b.String()) {
return
}
}
func TestSourceCommand_Stdin(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
in := bytes.NewBufferString(`
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
`)
out := &bytes.Buffer{}
r := commands.GetSourceRunner("")
r.Command.SetArgs([]string{})
r.Command.SetIn(in)
r.Command.SetOut(out)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/index: '0'
spec:
replicas: 1
- kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/index: '1'
spec:
selector:
app: nginx
`, out.String()) {
return
}
}

View File

@@ -104,6 +104,7 @@ metadata:
namespace: default namespace: default
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
replicas: 1 replicas: 1
@@ -117,6 +118,7 @@ metadata:
namespace: default namespace: default
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
replicas: 1 replicas: 1
@@ -130,6 +132,7 @@ metadata:
namespace: default namespace: default
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
replicas: 1 replicas: 1
@@ -143,6 +146,7 @@ metadata:
namespace: default2 namespace: default2
annotations: annotations:
app: nginx2 app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
replicas: 1 replicas: 1
@@ -156,6 +160,7 @@ metadata:
namespace: default namespace: default
annotations: annotations:
app: nginx3 app: nginx3
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
replicas: 1 replicas: 1
@@ -166,7 +171,8 @@ metadata:
app: nginx app: nginx
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/path: bar-package/f2.yaml config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
name: bar name: bar
spec: spec:
replicas: 3 replicas: 3
@@ -177,6 +183,7 @@ metadata:
namespace: default namespace: default
annotations: annotations:
app: nginx app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml config.kubernetes.io/path: f1.yaml
spec: spec:
selector: selector:

View File

@@ -4,6 +4,288 @@
// Code generated by "mdtogo"; DO NOT EDIT. // Code generated by "mdtogo"; DO NOT EDIT.
package api package api
var ConfigFnLong = `# Configuration Functions API Semantics
Configuration Functions are functions packaged as executables in containers which enable
**shift-left practices**. They configure applications and infrastructure through
Kubernetes style Resource Configuration, but run locally pre-commit.
Configuration functions enable shift-left practices (client-side) through:
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side *"CRD"* for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with ` + "`" + `small` + "`" + `, ` + "`" + `medium` + "`" + `, ` + "`" + `large` + "`" + `
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject ` + "`" + `init` + "`" + ` and ` + "`" + `side-car` + "`" + ` containers into Resources based off of Resource
Type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource -- e.g. ensure the ` + "`" + `Service.selector` + "`" + ` and ` + "`" + `Deployment.spec.template` + "`" + ` labels
match.
- MutatingWebHooks are scoped to a single Resource instance at a time.
- Low-level tweaks to the output of high-level abstractions -- e.g. add an ` + "`" + `init container` + "`" + `
to a client *"CRD"* Resource after it was generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
Configuration Functions are implemented as executable programs published in containers which:
- Accept as input (stdin):
- A list of Resource Configuration
- A Function Configuration (to configure the function itself)
- Emit as output (stdout + exit):
- A list of Resource Configuration
- An exit code for success / failure
### Function Specification
- Functions **SHOULD** be published as container images containing a ` + "`" + `CMD` + "`" + ` invoking an executable.
- Functions **MUST** accept input on STDIN a ` + "`" + `ResourceList` + "`" + ` containing the Resources and
` + "`" + `functionConfig` + "`" + `.
- Functions **MUST** emit output on STDOUT a ` + "`" + `ResourceList` + "`" + ` containing the modified
Resources.
- Functions **MUST** exit non-0 on failure, and exit 0 on success.
- Functions **MAY** emit output on STDERR with error messaging.
- Functions performing validation **SHOULD** exit failure and emit error messaging
on a validation failure.
- Functions generating Resources **SHOULD** retain non-conflicting changes on the
generated Resources -- e.g. 1. the function generates a Deployment, but doesn't
specify ` + "`" + `cpu` + "`" + `, 2. the user sets the ` + "`" + `cpu` + "`" + ` on the generated Resource, 3. the
function should keep the ` + "`" + `cpu` + "`" + ` when regenerating the Resource a second time.
- Functions **SHOULD** be usable outside ` + "`" + `kustomize config run` + "`" + ` -- e.g. though pipeline
mechanisms such as Tekton.
#### Input Format
Functions must accept on STDIN:
` + "`" + `ResourceList` + "`" + `:
- contains ` + "`" + `items` + "`" + ` field, same as ` + "`" + `List.items` + "`" + `
- contains ` + "`" + `functionConfig` + "`" + ` field -- a single item with the configuration for the function itself
Example ` + "`" + `ResourceList` + "`" + ` Input:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
functionConfig:
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
spec:
replicas: 5
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 3
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Output Format
Functions must emit on STDOUT:
` + "`" + `ResourceList` + "`" + `:
- contains ` + "`" + `items` + "`" + ` field, same as ` + "`" + `List.items` + "`" + `
Example ` + "`" + `ResourceList` + "`" + ` Output:
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
spec:
replicas: 5
...
- apiVersion: v1
kind: Service
metadata:
name: my-instance
spec:
...
#### Container Environment
When run by ` + "`" + `kustomize config run` + "`" + `, functions are run in containers with the
following environment:
- Network: ` + "`" + `none` + "`" + `
- User: ` + "`" + `nobody` + "`" + `
- Security Options: ` + "`" + `no-new-privileges` + "`" + `
- Volumes: the volume containing the ` + "`" + `functionConfig` + "`" + ` yaml is mounted under ` + "`" + `/local` + "`" + ` as ` + "`" + `ro` + "`" + `
### Example Function Implementation
Following is an example for implementing an nginx abstraction using a config
function.
#### ` + "`" + `nginx-template.sh` + "`" + `
` + "`" + `nginx-template.sh` + "`" + ` is a simple bash script which uses a *heredoc* as a templating solution
for generating Resources from the functionConfig input fields.
The script wraps itself using ` + "`" + `config run wrap -- $0` + "`" + ` which will:
1. Parse the ` + "`" + `ResourceList.functionConfig` + "`" + ` (provided to the container stdin) into env vars
2. Merge the stdout into the original list of Resources
3. Defaults filenames for newly generated Resources (if they are not set as annotations)
to ` + "`" + `config/NAME_KIND.yaml` + "`" + `
4. Format the output
#!/bin/bash
# script must run wrapped by ` + "`" + `kustomize config run wrap` + "`" + `
# for parsing input the functionConfig into env vars
if [ -z ${WRAPPED} ]; then
export WRAPPED=true
config run wrap -- $0
exit $?
fi
cat <<End-of-message
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: ${NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
instance: ${NAME}
template:
metadata:
labels:
app: nginx
instance: ${NAME}
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
End-of-message
#### ` + "`" + `Dockerfile` + "`" + `
` + "`" + `Dockerfile` + "`" + ` installs ` + "`" + `kustomize config` + "`" + ` and copies the script into the container image.
FROM golang:1.13-stretch
RUN go get sigs.k8s.io/kustomize/cmd/config
RUN mv /go/bin/config /usr/bin/config
COPY nginx-template.sh /usr/bin/nginx-template.sh
CMD ["nginx-template.sh]
### Example Function Usage
Following is an example of running the ` + "`" + `kustomize config run` + "`" + ` using the preceding API.
#### ` + "`" + `nginx.yaml` + "`" + ` (Input)
` + "`" + `dir/nginx.yaml` + "`" + ` contains a reference to the Function. The contents of ` + "`" + `nginx.yaml` + "`" + `
are passed to the Function through the ` + "`" + `ResourceList.functionConfig` + "`" + ` field.
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
configFn:
container:
image: gcr.io/example-functions/nginx-template:v1.0.0
spec:
replicas: 5
- ` + "`" + `configFn.container.image` + "`" + `: the image to use for this API
- ` + "`" + `annotations[config.kubernetes.io/local-config]` + "`" + `: mark this as not a Resource that should
be applied
#### ` + "`" + `kustomize config run dir/` + "`" + ` (Output)
` + "`" + `dir/my-instance_deployment.yaml` + "`" + ` contains the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
replicas: 5
selector:
matchLabels:
app: nginx
instance: my-instance
template:
metadata:
labels:
app: nginx
instance: my-instance
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
` + "`" + `dir/my-instance_service.yaml` + "`" + ` contains the Service:
apiVersion: v1
kind: Service
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: my-instance`
var ConfigIoLong = `# Configuration IO API Semantics var ConfigIoLong = `# Configuration IO API Semantics
Resource Configuration may be read / written from / to sources such as directories, Resource Configuration may be read / written from / to sources such as directories,
@@ -33,7 +315,7 @@ Example:
### ` + "`" + `config.kubernetes.io/index` + "`" + ` ### ` + "`" + `config.kubernetes.io/index` + "`" + `
Records the index of a Resource in file. In a multi-object YAML file, Resources are separated Records the index of a Resource in file. In a multi-object YAML file, Resources are separated
by three dashes (` + "`" + `---` + "`" + `), and the index represents the position of the Resource starting from zero. by three dashes (` + "`" + `---` + "`" + `), and the index represents the positon of the Resource starting from zero.
This annotation **SHOULD** be set when reading Resources from files. This annotation **SHOULD** be set when reading Resources from files.
It **SHOULD** be unset when writing Resources to files. It **SHOULD** be unset when writing Resources to files.
@@ -64,355 +346,6 @@ Example:
annotations: annotations:
config.kubernetes.io/local-config: "true"` config.kubernetes.io/local-config: "true"`
var FunctionsImplShort = `Following is an example for implementing an nginx abstraction using a configuration`
var FunctionsImplLong = `# Running Configuration Functions using kustomize CLI
Configuration functions can be implemented using any toolchain and invoked using any
container workflow orchestrator including Tekton, Cloud Build, or run directly using ` + "`" + `docker run` + "`" + `.
Run ` + "`" + `config help docs-fn-spec` + "`" + ` to see the Configuration Functions Specification.
` + "`" + `kustomize config run` + "`" + ` is an example orchestrator for invoking Configuration Functions. This
document describes how to implement and invoke an example function.
function.
### ` + "`" + `nginx-template.sh` + "`" + `
` + "`" + `nginx-template.sh` + "`" + ` is a simple bash script which uses a _heredoc_ as a templating solution
for generating Resources from the functionConfig input fields.
The script wraps itself using ` + "`" + `config run wrap -- $0` + "`" + ` which will:
1. Parse the ` + "`" + `ResourceList.functionConfig` + "`" + ` (provided to the container stdin) into env vars
2. Merge the stdout into the original list of Resources
3. Defaults filenames for newly generated Resources (if they are not set as annotations)
to ` + "`" + `config/NAME_KIND.yaml` + "`" + `
4. Format the output
#!/bin/bash
# script must run wrapped by "kustomize config run wrap"
# for parsing input the functionConfig into env vars
if [ -z ${WRAPPED} ]; then
export WRAPPED=true
config run wrap -- $0
exit $?
fi
cat <<End-of-message
apiVersion: v1
kind: Service
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: ${NAME}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${NAME}
labels:
app: nginx
instance: ${NAME}
spec:
replicas: ${REPLICAS}
selector:
matchLabels:
app: nginx
instance: ${NAME}
template:
metadata:
labels:
app: nginx
instance: ${NAME}
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
End-of-message
### Dockerfile
` + "`" + `Dockerfile` + "`" + ` installs ` + "`" + `kustomize config` + "`" + ` and copies the script into the container image.
FROM golang:1.13-stretch
RUN go get sigs.k8s.io/kustomize/cmd/config
RUN mv /go/bin/config /usr/bin/config
COPY nginx-template.sh /usr/bin/nginx-template.sh
CMD ["nginx-template.sh]
## Example Function Usage
Following is an example of running the ` + "`" + `kustomize config run` + "`" + ` using the preceding API.
When run by ` + "`" + `kustomize config run` + "`" + `, functions are run in containers with the
following environment:
- Network: ` + "`" + `none` + "`" + `
- User: ` + "`" + `nobody` + "`" + `
- Security Options: ` + "`" + `no-new-privileges` + "`" + `
- Volumes: the volume containing the ` + "`" + `functionConfig` + "`" + ` yaml is mounted under ` + "`" + `/local` + "`" + ` as ` + "`" + `ro` + "`" + `
### Input
` + "`" + `dir/nginx.yaml` + "`" + ` contains a reference to the Function. The contents of ` + "`" + `nginx.yaml` + "`" + `
are passed to the Function through the ` + "`" + `ResourceList.functionConfig` + "`" + ` field.
apiVersion: example.com/v1beta1
kind: Nginx
metadata:
name: my-instance
annotations:
config.kubernetes.io/local-config: "true"
config.k8s.io/function: |
container:
image: gcr.io/example-functions/nginx-template:v1.0.0
spec:
replicas: 5
- ` + "`" + `annotations[config.k8s.io/function].container.image` + "`" + `: the image to use for this API
- ` + "`" + `annotations[config.kubernetes.io/local-config]` + "`" + `: mark this as not a Resource that should
be applied
### Output
The function is invoked using byrunning ` + "`" + `kustomize config run dir/` + "`" + `.
` + "`" + `dir/my-instance_deployment.yaml` + "`" + ` contains the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
replicas: 5
selector:
matchLabels:
app: nginx
instance: my-instance
template:
metadata:
labels:
app: nginx
instance: my-instance
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
` + "`" + `dir/my-instance_service.yaml` + "`" + ` contains the Service:
apiVersion: v1
kind: Service
metadata:
name: my-instance
labels:
app: nginx
instance: my-instance
spec:
ports:
- port: 80
targetPort: 80
name: http
selector:
app: nginx
instance: my-instance`
var FunctionsSpecShort = `_Configuration functions_ enable shift-left practices (client-side) through:`
var FunctionsSpecLong = `# Configuration Functions Specification
This document specifies a standard for client-side functions that operate on
Kubernetes declarative configurations. This standard enables creating
small, interoperable, and language-independent executable programs packaged as
containers that can be chained together as part of a configuration management pipeline.
The end result of such a pipeline are fully rendered configurations that can then be
applied to a control plane (e.g. Using kubectl apply for Kubernetes control plane).
As such, although this document references Kubernetes Resource Model and API conventions,
it is completely decoupled from Kuberentes API machinery and does not depend on any
in-cluster components.
This document references terms described in [Kubernetes API Conventions][1].
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in [RFC 2119][2].
- Pre-commit / delivery validation and linting of configuration
- e.g. Fail if any containers don't have PodSecurityPolicy or CPU / Memory limits
- Implementation of abstractions as client actuated APIs (e.g. templating)
- e.g. Create a client-side _"CRD"_ for generating configuration checked into git
- Aspect Orient configuration / Injection of cross-cutting configuration
- e.g. T-Shirt size containers by annotating Resources with ` + "`" + `small` + "`" + `, ` + "`" + `medium` + "`" + `, ` + "`" + `large` + "`" + `
and inject the cpu and memory resources into containers accordingly.
- e.g. Inject ` + "`" + `init` + "`" + ` and ` + "`" + `side-car` + "`" + ` containers into Resources based off of Resource
Type, annotations, etc.
Performing these on the client rather than the server enables:
- Configuration to be reviewed prior to being sent to the API server
- Configuration to be validated as part of the CI?CD pipeline
- Configuration for Resources to validated holistically rather than individually
per-Resource
- e.g. ensure the ` + "`" + `Service.selector` + "`" + ` and ` + "`" + `Deployment.spec.template` + "`" + ` labels
match.
- e.g. MutatingWebHooks are scoped to a single Resource instance at a time.
- Low-level tweaks to the output of high-level abstractions
- e.g. add an ` + "`" + `init container` + "`" + ` to a client _"CRD"_ Resource after it was generated.
- Composition and layering of multiple functions together
- Compose generation, injection, validation together
## Spec
### Input Type
A function MUST accept as input a single [Kubernetes List type][3].
The ` + "`" + `items` + "`" + ` field in the input will contain a sequence of [Object types][3].
A function MAY not support [Simple types][3] and List types.
An example using ` + "`" + `v1/ConfigMapList` + "`" + ` as input:
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: config1
data:
p1: v1
p2: v2
- apiVersion: v1
kind: ConfigMap
metadata:
name: config2
An example using ` + "`" + `v1/List` + "`" + ` as input:
apiVersion: v1
kind: List
items:
spec:
- apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
address: "100 Main St."
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
In addition, a function MUST accept as input a List of kind ` + "`" + `ResourceList` + "`" + ` where the
` + "`" + `functionConfig` + "`" + ` field, if present, will contain the invocation-specific configuration passed to the function
by the orchestrator.
Functions MAY consider this field optional so that they can be triggered in an ad-hoc fashion.
An example using ` + "`" + `config.kubernetes.io/v1beta1/ResourceList` + "`" + ` as input:
apiVersion: config.kubernetes.io/v1beta1
kind: ResourceList
functionConfig:
apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
name: staging
metadata:
annotations:
config.k8s.io/function: |
container:
image: gcr.io/example/foo:v1.0.0
spec:
address: "100 Main St."
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- resources:
- namespaces
apiGroups:
- ""
verbs:
- get
- watch
- list
Here ` + "`" + `FulfillmentCenter` + "`" + ` kind with name ` + "`" + `staging` + "`" + ` is passed as the invocation-specific configuration
to the function.
### Output Type
A functions output MUST be the same as the input specification above
-- i.e. ` + "`" + `ResourceList` + "`" + ` or ` + "`" + `List` + "`" + `.
This is necessary to enable chaining two or more functions together in a pipeline.
The serialization format of the output SHOULD match that of its input on each invocation
-- e.g. if the input was a ` + "`" + `ResourceList` + "`" + `, the output should also be a ` + "`" + `ResourceList` + "`" + `.
### Serialization Format
A function MUST support YAML as a serialization format for the input and output.
A function MUST use utf8 encoding (as YAML is a superset of JSON, JSON will also be supported
by any conforming function).
### Operations
A function MAY Create, Update, or Delete any number of items in the ` + "`" + `items` + "`" + ` field and output the
resultant list.
A function MAY modify annotations with prefix ` + "`" + `config.kubernetes.io` + "`" + `, but must be careful about
doing so since theyre used for orchestration purposes and will likely impact subsequent functions
in the pipeline.
A function SHOULD preserve comments when input serialization format is YAML.
This allows for human authoring of configuration to coexist with changes made by functions.
### Containerization
A function MUST be implemented as a container.
A function container MUST be capable of running as a non-root user if it does not require
access to host filesystem or makes network calls.
### stdin/stdout/stderr and Exit Codes
A function MUST accept input from stdin and emit output to stdout.
Any error messages MUST be emitted to stderr.
An exit code of zero indicates function execution was successful.
A non-zero exit code indicates a failure.
[1]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
[2]: https://tools.ietf.org/html/rfc2119
[3]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#types-kinds`
var Merge2Long = `# Merge (2-way) var Merge2Long = `# Merge (2-way)
2-way merges fields from a source to a destination, overriding the destination fields 2-way merges fields from a source to a destination, overriding the destination fields

View File

@@ -4,20 +4,6 @@
// Code generated by "mdtogo"; DO NOT EDIT. // Code generated by "mdtogo"; DO NOT EDIT.
package commands 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 CatShort = `[Alpha] Print Resource Config from a local directory.`
var CatLong = ` var CatLong = `
[Alpha] Print Resource Config from a local directory. [Alpha] Print Resource Config from a local directory.
@@ -156,25 +142,6 @@ var GrepExamples = `
# look for Resources matching a specific container image # look for Resources matching a specific container image
kustomize config grep "spec.template.spec.containers[name=nginx].image=nginx:1\.7\.9" my-dir/ | kustomize config tree` kustomize config grep "spec.template.spec.containers[name=nginx].image=nginx:1\.7\.9" my-dir/ | kustomize config tree`
var ListSettersShort = `[Alpha] List setters for Resources.`
var ListSettersLong = `
List setters for Resources.
DIR
A directory containing Resource configuration.
NAME
Optional. The name of the setter to display.
`
var ListSettersExamples = `
Show setters:
$ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY
name-prefix '' PREFIX string 2`
var MergeShort = `[Alpha] Merge Resource configuration files` var MergeShort = `[Alpha] Merge Resource configuration files`
var MergeLong = ` var MergeLong = `
[Alpha] Merge Resource configuration files [Alpha] Merge Resource configuration files
@@ -234,8 +201,8 @@ order they appear in the file).
#### Config Functions: #### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function] Config functions are specified as Kubernetes types containing a metadata.configFn.container.image
field specifying an image for the container to run. This image tells run how to invoke the container. field. This field tells run how to invoke the container.
Example config function: Example config function:
@@ -243,17 +210,17 @@ order they appear in the file).
apiVersion: fn.example.com/v1beta1 apiVersion: fn.example.com/v1beta1
kind: ExampleFunctionKind kind: ExampleFunctionKind
metadata: metadata:
configFn:
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
annotations: annotations:
config.kubernetes.io/function: |
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
config.kubernetes.io/local-config: "true" # tools should ignore this config.kubernetes.io/local-config: "true" # tools should ignore this
spec: spec:
configField: configValue configField: configValue
In the preceding example, 'kustomize config run example/' would identify the function by In the preceding example, 'kustomize config run example/' would identify the function by
the metadata.annotations.[config.kubernetes.io/function] field. It would then write all Resources in the directory to the metadata.configFn field. It would then write all Resources in the directory to
a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It
would then write the container stdout back to example/, replacing the directory would then write the container stdout back to example/, replacing the directory
file contents. file contents.
@@ -319,19 +286,19 @@ var SetExamples = `
List setters: Show the possible setters List setters: Show the possible setters
$ config set DIR/ $ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY NAME DESCRIPTION VALUE TYPE COUNT OWNER
name-prefix '' PREFIX string 2 name-prefix '' PREFIX string 2
Perform set: set a new value, owner and description Perform substitution: set a new value, owner and description
$ kustomize config set DIR/ name-prefix "test" --description "test environment" --set-by "dev" $ kustomize config set DIR/ name-prefix "test" --description "test environment" --set-by "dev"
set 2 values performed 2 substitutions
List setters: Show the new values Show substitutions: Show the new values
$ config set DIR/ $ config set dir
NAME DESCRIPTION VALUE TYPE COUNT SETBY NAME DESCRIPTION VALUE TYPE COUNT SUBSTITUTED OWNER
name-prefix 'test environment' test string 2 dev prefix 'test environment' test string 2 true dev
New Resource YAML: New Resource YAML:
@@ -346,38 +313,6 @@ var SetExamples = `
name: test-app2 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}} name: test-app2 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}}
...` ...`
var SinkShort = `[Alpha] Implement a Sink by writing input to a local directory.`
var SinkLong = `
[Alpha] Implement a Sink by writing input to a local directory.
kustomize config sink [DIR]
DIR:
Path to local directory. If unspecified, sink will write to stdout as if it were a single file.
` + "`" + `sink` + "`" + ` writes its input to a directory
`
var SinkExamples = `
kustomize config source DIR/ | your-function | kustomize config sink DIR/`
var SourceShort = `[Alpha] Implement a Source by reading a local directory.`
var SourceLong = `
[Alpha] Implement a Source by reading a local directory.
kustomize config source DIR...
DIR:
One or more paths to local directories. Contents from directories will be concatenated.
If no directories are provided, source will read from stdin as if it were a single file.
` + "`" + `source` + "`" + ` emits configuration to act as input to a function
`
var SourceExamples = `
# emity configuration directory as input source to a function
kustomize config source DIR/
kustomize config source DIR/ | your-function | kustomize config sink DIR/`
var TreeShort = `[Alpha] Display Resource structure from a directory or stdin.` var TreeShort = `[Alpha] Display Resource structure from a directory or stdin.`
var TreeLong = ` var TreeLong = `
[Alpha] Display Resource structure from a directory or stdin. [Alpha] Display Resource structure from a directory or stdin.

View File

@@ -9,7 +9,6 @@ package main
import ( import (
"os" "os"
"sigs.k8s.io/kustomize/cmd/config/complete"
"sigs.k8s.io/kustomize/cmd/config/configcobra" "sigs.k8s.io/kustomize/cmd/config/configcobra"
"sigs.k8s.io/kustomize/kyaml/commandutil" "sigs.k8s.io/kustomize/kyaml/commandutil"
) )
@@ -17,10 +16,7 @@ import (
func main() { func main() {
// enable the config commands // enable the config commands
os.Setenv(commandutil.EnableAlphaCommmandsEnvName, "true") os.Setenv(commandutil.EnableAlphaCommmandsEnvName, "true")
cmd := configcobra.NewConfigCommand("") if err := configcobra.NewConfigCommand("").Execute(); err != nil {
complete.Complete(cmd).Complete("config")
if err := cmd.Execute(); err != nil {
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -3,21 +3,12 @@ module sigs.k8s.io/kustomize/cmd/kubectl
go 1.13 go 1.13
require ( require (
github.com/go-errors/errors v1.0.1
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/cli-runtime v0.17.0 k8s.io/cli-runtime v0.17.0
k8s.io/client-go v0.17.0 k8s.io/client-go v0.17.0
k8s.io/component-base v0.17.0 // indirect k8s.io/component-base v0.17.0 // indirect
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd
sigs.k8s.io/controller-runtime v0.4.0 sigs.k8s.io/kustomize/kyaml v0.0.0
sigs.k8s.io/kustomize/kstatus v0.0.1
sigs.k8s.io/kustomize/kyaml v0.0.2
) )
replace ( replace sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
sigs.k8s.io/kustomize/kstatus v0.0.1 => ../../kstatus
sigs.k8s.io/kustomize/kyaml v0.0.0 => ../../kyaml
)

Some files were not shown because too many files have changed in this diff Show More