Compare commits

..

1 Commits

Author SHA1 Message Date
Phillip Wittrock
5ba90fe5ef update go.mod for release 2019-12-17 13:56:44 -08:00
334 changed files with 5182 additions and 44431 deletions

View File

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

View File

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

View File

@@ -17,16 +17,6 @@ verify-kustomize: \
test-examples-kustomize-against-HEAD \
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.
# Without one Makefile to rule them all, the different makes
# cannot assume that golanci-lint is at the version they want
@@ -58,11 +48,6 @@ $(MYGOBIN)/goimports:
cd api; \
go install golang.org/x/tools/cmd/goimports
# Install resource from whatever is checked out.
$(MYGOBIN)/resource:
cd cmd/resource; \
go install .
# To pin pluginator, use this recipe instead:
# cd api;
# go install sigs.k8s.io/kustomize/pluginator/v2
@@ -208,16 +193,6 @@ test-unit-kustomize-all: \
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
./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:
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
( \
@@ -262,16 +237,6 @@ $(MYGOBIN)/helm:
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
clean: kustomize-external-go-plugin-clean
go clean --cache

View File

@@ -52,7 +52,7 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
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

View File

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

View File

@@ -2,15 +2,11 @@ package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/crawler"
"sigs.k8s.io/kustomize/api/internal/crawl/crawler/github"
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
@@ -25,76 +21,35 @@ const (
redisCacheURL = "REDIS_CACHE_URL"
redisKeyURL = "REDIS_KEY_URL"
retryCount = 3
githubUserEnv = "GITHUB_USER"
githubRepoEnv = "GITHUB_REPO"
)
type CrawlMode int
const (
CrawlUnknown CrawlMode = iota
// Crawl all the kustomization files in all the repositories of a Github user
CrawlUser
// Crawl all the kustomization files in a Github repo
CrawlRepo
// Crawl all the documents in the index
CrawlIndex
// Crawl all the kustomization files on Github
CrawlGithub
// Crawl all the documents in the index and crawling all the kustomization files on Github
CrawlIndexAndGithub
)
func NewCrawlMode(s string) CrawlMode {
switch s {
case "github-user":
return CrawlUser
case "github-repo":
return CrawlRepo
case "index+github":
return CrawlIndexAndGithub
case "index":
return CrawlIndex
case "github":
return CrawlGithub
default:
return CrawlUnknown
}
}
func main() {
indexNamePtr := flag.String(
"index", "kustomize", "The name of the ElasticSearch index.")
modePtr := flag.String("mode", "index+github",
`The crawling mode, which can be one of [github-user, github-repo, index, github, index+github].
* github-user: crawl all the kustomization files in all the repositories of a Github user (--github-user must be specified for this mode).
* github-repo: crawl all the kustomization files in a Github repository (--github-repo must be specified for this mode).
* index: crawl all the documents in the index.
* gihub: crawl all the kustomization files on Github.
* index+github: crawl all the documents in the index and crawling all the kustomization files on Github.`)
githubUserPtr := flag.String("github-user", "",
"A github user name (e.g., kubernetes-sigs). This flag is required for the `github-user` mode.")
githubRepoPtr := flag.String("github-repo", "",
"A github repository name (e.g., kubernetes-sigs/kustomize). This flag is required for the `github-repo` mode.")
flag.Parse()
githubUser := os.Getenv(githubUserEnv)
githubRepo := os.Getenv(githubRepoEnv)
githubToken := os.Getenv(githubAccessTokenVar)
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)
return
}
ctx := context.Background()
idx, err := index.NewKustomizeIndex(ctx, *indexNamePtr)
idx, err := index.NewKustomizeIndex(ctx)
if err != nil {
log.Printf("Could not create an index: %v\n", err)
fmt.Printf("Could not create an index: %v\n", err)
return
}
seedDocs := make(crawler.CrawlSeed, 0)
cacheURL := os.Getenv(redisCacheURL)
cache, err := redis.DialURL(cacheURL)
clientCache := &http.Client{}
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 {
clientCache = httpclient.NewClient(cache)
}
@@ -110,106 +65,58 @@ func main() {
}
// Index updates the value in the index.
indexFunc := func(cdoc crawler.CrawledDocument, mode index.Mode) error {
index := func(cdoc crawler.CrawledDocument, crwlr crawler.Crawler) error {
switch d := cdoc.(type) {
case *doc.KustomizationDocument:
switch mode {
case index.Delete:
log.Printf("Deleting: %v", d)
return idx.Delete(d.ID())
default:
log.Printf("Inserting: %v", d)
return idx.Put(d.ID(), d)
}
fmt.Println("Inserting: ", d)
_, err := idx.Put(d.ID(), d)
return err
default:
return fmt.Errorf("type %T not supported", d)
}
}
// 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.
seen := utils.NewSeenMap()
seen := make(map[string]struct{})
mode := NewCrawlMode(*modePtr)
var ghCrawler crawler.Crawler
ghCrawlerConstructor := func(user, repo string) crawler.Crawler {
if user != "" {
return github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"),
github.Filename("kustomization"),
github.User(user)),
)
} else if repo != "" {
return github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"),
github.Filename("kustomization"),
github.Repo(repo)),
)
} else {
return github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"),
github.Filename("kustomization")),
)
}
}
if githubRepo != "" {
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"),
github.Repo(githubRepo)),
)
} else if githubUser != "" {
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml"),
github.User(githubUser)),
)
} else {
ghCrawler = github.NewCrawler(githubToken, retryCount, clientCache,
github.QueryWith(
github.Filename("kustomization.yaml"),
github.Filename("kustomization.yml")),
)
seedDocs := make(crawler.CrawlSeed, 0)
// get all the documents in the index
getSeedDocsFunc := func() {
// 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 {
seedDocs = append(seedDocs, hit.Document.Document.Copy())
seedDocs = append(seedDocs, hit.Document.Copy())
}
}
if err := it.Err(); err != nil {
log.Fatalf("getSeedDocsFunc Error iterating: %v\n", err)
fmt.Printf("Error iterating: %v\n", err)
}
}
switch mode {
case CrawlIndexAndGithub:
getSeedDocsFunc()
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, indexFunc, seen)
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlIndex:
getSeedDocsFunc()
crawlers := []crawler.Crawler{ghCrawlerConstructor("", "")}
crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, indexFunc, seen)
case CrawlGithub:
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)
case CrawlUser:
if *githubUserPtr == "" {
flag.Usage()
log.Fatalf("Please specify a github user with the github-user flag!")
}
crawlers := []crawler.Crawler{ghCrawlerConstructor(*githubUserPtr, "")}
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlRepo:
if *githubRepoPtr == "" {
flag.Usage()
log.Fatalf("Please specify a github repository with the github-repo flag!")
}
crawlers := []crawler.Crawler{ghCrawlerConstructor("", *githubRepoPtr)}
crawler.CrawlGithub(ctx, crawlers, docConverter, indexFunc, seen)
case CrawlUnknown:
flag.Usage()
log.Fatalf("The --mode flag must be one of [github-user, github-repo, index, github, index+github].")
}
crawlers := []crawler.Crawler{ghCrawler}
crawler.CrawlFromSeed(ctx, seedDocs, crawlers, docConverter, index, seen)
crawler.CrawlGithub(ctx, crawlers, docConverter, index, seen)
}

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
literals:
- es-url="http://esbasic-master:9200"
- kustomize-index-name="kustomize"
- plugin-index-name="plugin"

View File

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

View File

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

View File

@@ -1,53 +0,0 @@
The crawler job can run in one of the following mode:
# Crawling all the documents in the index and crawling all the kustomization files on Github
This is the default setting of the crawler job. The `command` and `args` field
of the container should be:
```
command: ["/crawler"]
```
Or
```
command: ["/crawler"]
args: ["--mode=index+github"]
```
# Crawling all the documents in the index
The `command` and `args` field of the container should be:
```
command: ["/crawler"]
args: ["--mode=index"]
```
# Crawling all the kustomization files on Github
The `command` and `args` field of the container should be:
```
command: ["/crawler"]
args: ["--mode=github"]
```
# Crawling all the kustomization files in a Github repo
The `command` and `args` field of the container should be like:
```
command: ["/crawler"]
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize"]
```
# Crawling all the kustomization files in all the repositories of a Github user
The `command` and `args` field of the container should be like:
```
command: ["/crawler"]
args: ["--github-user", "--github-user=kubernetes-sigs"]
```

View File

@@ -8,10 +8,8 @@ spec:
restartPolicy: OnFailure
containers:
- name: crawler
image: gcr.io/haiyanmeng-gke-dev/crawler:v1
image: gcr.io/kustomize-search/crawler:latest
imagePullPolicy: Always
command: ["/crawler"]
args: ["--mode=github-repo", "--github-repo=kubernetes-sigs/kustomize", "--index=kustomize"]
env:
- name: GITHUB_ACCESS_TOKEN
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
kind: ESCluster
metadata:
@@ -9,13 +8,6 @@ spec:
- repository-gcs
- ingest-user-agent
- 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:
env:
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,10 +10,6 @@ import (
"os"
"sync"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/index"
_ "github.com/gomodule/redigo/redis"
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
@@ -31,15 +27,13 @@ type Crawler interface {
// Crawl returns when it is done processing. This method does not take
// ownership of the channel. The channel is write only, and it
// designates where the crawler should forward the documents.
Crawl(ctx context.Context, output chan<- CrawledDocument, seen utils.SeenMap) error
Crawl(ctx context.Context, output chan<- CrawledDocument) error
// Get the document data given the FilePath, Repo, and Ref/Tag/Branch.
FetchDocument(context.Context, *doc.Document) error
// Write to the document what the created time is.
SetCreated(context.Context, *doc.Document) error
SetDefaultBranch(*doc.Document)
Match(*doc.Document) bool
}
@@ -47,18 +41,13 @@ type CrawledDocument interface {
ID() string
GetDocument() *doc.Document
// Get all the Documents directly referred in a Document.
// For a Document representing a non-kustomization file, an empty slice will be returned.
// For a Document representing a kustomization file:
// the `includeResources` parameter determines whether the documents referred in the `resources` field are returned or not;
// the `includeTransformers` parameter determines whether the documents referred in the `transformers` field are returned or not;
// the `includeGenerators` parameter determines whether the documents referred in the `generators` field are returned or not.
GetResources(includeResources, includeTransformers, includeGenerators bool) ([]*doc.Document, error)
GetResources() ([]*doc.Document, error)
WasCached() bool
}
type CrawlSeed []*doc.Document
type IndexFunc func(CrawledDocument, index.Mode) error
type IndexFunc func(CrawledDocument, Crawler) error
type Converter func(*doc.Document) (CrawledDocument, error)
func logIfErr(err error) {
@@ -78,27 +67,24 @@ func findMatch(d *doc.Document, crawlers []Crawler) Crawler {
}
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)
match.SetDefaultBranch(cdoc.GetDocument())
seen[cdoc.ID()] = struct{}{}
// Insert into index
if err := indx(cdoc, index.InsertOrUpdate); err != nil {
logger.Printf("Failed to insert or update doc(%s): %v",
cdoc.GetDocument().Path(), err)
return
}
deps, err := cdoc.GetResources(true, true, true)
err := indx(cdoc, match)
logIfErr(err)
if err != nil {
logger.Println(err)
return
}
deps, err := cdoc.GetResources()
logIfErr(err)
if err != nil {
return
}
for _, dep := range deps {
if seen.Seen(dep.ID()) && seen.Value(dep.ID()) == dep.FileType {
if _, ok := seen[dep.ID()]; ok {
continue
}
*stack = append(*stack, dep)
@@ -106,18 +92,8 @@ func addBranches(cdoc CrawledDocument, match Crawler, indx IndexFunc,
}
func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv Converter, indx IndexFunc,
seen utils.SeenMap, stack *CrawlSeed, refreshDoc bool, updateFileType bool) {
UpdatedDocCount := 0
seenDocCount := 0
cachedDocCount := 0
findMatchErrCount := 0
FetchDocumentErrCount := 0
SetCreatedErrCount := 0
convErrCount := 0
deleteDocCount := 0
crawledDocCount := 0
seen map[string]struct{}, stack *CrawlSeed) {
docCount := 0
// During the execution of the for loop, more Documents may be added into (*docsPtr).
for len(*docsPtr) > 0 {
// get the last Document in (*docPtr), which will be crawled in this iteration.
@@ -126,97 +102,45 @@ func doCrawl(ctx context.Context, docsPtr *CrawlSeed, crawlers []Crawler, conv C
// remove the last Document in (*docPtr)
*docsPtr = (*docsPtr)[:(len(*docsPtr) - 1)]
crawledDocCount++
logger.Printf("Crawling doc %d: %s", crawledDocCount, tail.Path())
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() {
logger.Printf("doc(%s) is cached already", tail.Path())
cachedDocCount++
if _, ok := seen[tail.ID()]; ok {
continue
}
docCount++
match := findMatch(tail, crawlers)
if match == nil {
logIfErr(fmt.Errorf("%v could not match any crawler", tail))
findMatchErrCount++
logIfErr(fmt.Errorf(
"%v could not match any crawler", tail))
continue
}
if tail.User == "" {
tail.User = doc.UserName(tail.RepositoryURL)
}
// If the Document represents a kustomization root, FetchDcoument will change
// the `filePath` field of the Document by adding `kustomization.yaml` or
// `kustomization.yml` or `kustomization` into the the field.
// Therefore, it is necessary to add the ID of the Document into seen before
// calling FetchDocument. Otherwise, the binary may enter into an infinite loop
// if a kustomization file points to its kustmozation root in its `resources` or
// `bases` field.
seen.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
logger.Println("Crawling ", tail.RepositoryURL, tail.FilePath)
err := match.FetchDocument(ctx, tail)
logIfErr(err)
// If there was no change or there is an error, we don't have
// to branch out, since the dependencies are already in the
// index, or we cannot find the document.
if err != nil || tail.WasCached() {
if tail.WasCached() {
logger.Println(tail.RepositoryURL, tail.FilePath, "is cached already")
}
continue
}
if refreshDoc || tail.CreationTime == nil {
if err := match.SetCreated(ctx, tail); err != nil {
logger.Printf("SetCreated failed on doc(%s): %v", tail.Path(), err)
SetCreatedErrCount++
}
}
logIfErr(match.SetCreated(ctx, tail))
cdoc, err := conv(tail)
// If conv returns an error, cdoc can still be added into the index so that
// cdoc.Document can be searched.
if err != nil {
logger.Printf("conv failed on doc(%s): %v", tail.Path(), err)
convErrCount++
}
logIfErr(err)
UpdatedDocCount++
addBranches(cdoc, match, indx, seen, stack)
}
logger.Printf("Summary of doCrawl:\n")
logger.Printf("\t%d documents were updated\n", UpdatedDocCount)
logger.Printf("\t%d documents were seen by the crawler already and skipped\n", seenDocCount)
logger.Printf("\t%d documents were cached already and skipped\n", cachedDocCount)
logger.Printf("\t%d documents didn't have a matching crawler and skipped\n", findMatchErrCount)
logger.Printf("\t%d documents cannot be fetched, %d out of them are deleted\n",
FetchDocumentErrCount, deleteDocCount)
logger.Printf("\t%d documents cannot update its creation time but still were inserted or updated in the index\n", SetCreatedErrCount)
logger.Printf("\t%d documents cannot be converted but still were inserted or updated in the index\n", convErrCount)
logger.Printf("%d documents were crawled by doCrawl\n", docCount)
}
// CrawlFromSeed updates all the documents in seed, and crawls all the new
// documents referred in the seed.
func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
conv Converter, indx IndexFunc, seen utils.SeenMap) {
conv Converter, indx IndexFunc, seen map[string]struct{}) {
// stack tracks the documents directly referred in other documents.
stack := make(CrawlSeed, 0)
@@ -224,14 +148,14 @@ func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
// Exploit seed to update bulk of corpus.
logger.Printf("updating %d documents from seed\n", len(seed))
// 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.
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
// will be added into stack.
// 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
@@ -252,7 +176,7 @@ func CrawlFromSeed(ctx context.Context, seed CrawlSeed, crawlers []Crawler,
// from the seed will be processed before any other documents from the
// crawlers.
func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
crawlers []Crawler, seen utils.SeenMap) []error {
crawlers []Crawler) []error {
errs := make([]error, len(crawlers))
wg := sync.WaitGroup{}
@@ -286,7 +210,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
}
}()
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
}
@@ -296,7 +220,7 @@ func CrawlGithubRunner(ctx context.Context, output chan<- CrawledDocument,
// CrawlGithub crawls all the kustomization files on Github.
func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
indx IndexFunc, seen utils.SeenMap) {
indx IndexFunc, seen map[string]struct{}) {
// stack tracks the documents directly referred in other documents.
stack := make(CrawlSeed, 0)
@@ -308,14 +232,8 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
wg.Add(1)
go func() {
defer wg.Done()
docCount := 0
for cdoc := range ch {
docCount++
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")
if _, ok := seen[cdoc.ID()]; ok {
continue
}
match := findMatch(cdoc.GetDocument(), crawlers)
@@ -329,7 +247,7 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
}()
logger.Println("processing the documents found from crawling github")
if errs := CrawlGithubRunner(ctx, ch, crawlers, seen); errs != nil {
if errs := CrawlGithubRunner(ctx, ch, crawlers); errs != nil {
for _, err := range errs {
logIfErr(err)
}
@@ -340,5 +258,5 @@ func CrawlGithub(ctx context.Context, crawlers []Crawler, conv Converter,
// Handle deps of newly discovered documents.
logger.Printf("crawling the %d new documents referred by other documents",
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"
"errors"
"fmt"
"log"
"reflect"
"sort"
"strings"
@@ -12,10 +11,6 @@ import (
"testing"
"time"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/index"
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
"sigs.k8s.io/kustomize/api/konfig"
)
@@ -37,8 +32,6 @@ func (c testCrawler) Match(d *doc.Document) bool {
return d != nil
}
func (c testCrawler) SetDefaultBranch(d *doc.Document) {}
func (c testCrawler) FetchDocument(_ context.Context, d *doc.Document) error {
if i, ok := c.lukp[d.ID()]; ok {
d.DocumentData = c.docs[i].DocumentData
@@ -80,7 +73,7 @@ func newCrawler(matchPrefix string, err error,
// Crawl implements the Crawler interface for testing.
func (c testCrawler) Crawl(_ context.Context,
output chan<- CrawledDocument, _ utils.SeenMap) error {
output chan<- CrawledDocument) error {
for i, d := range c.docs {
isResource := true
@@ -115,7 +108,7 @@ func (s sortableDocs) Len() int {
}
func TestCrawlGithubRunner(t *testing.T) {
log.Println("testing CrawlGithubRunner")
fmt.Println("testing CrawlGithubRunner")
tests := []struct {
tc []Crawler
errs []error
@@ -186,9 +179,8 @@ func TestCrawlGithubRunner(t *testing.T) {
defer close(output)
defer wg.Done()
seen := utils.NewSeenMap()
errs := CrawlGithubRunner(context.Background(),
output, test.tc, seen)
output, test.tc)
// Check that errors are returned as they should be.
if !reflect.DeepEqual(errs, test.errs) {
@@ -221,7 +213,7 @@ func TestCrawlGithubRunner(t *testing.T) {
}
func TestCrawlFromSeed(t *testing.T) {
log.Println("testing CrawlFromSeed")
fmt.Println("testing CrawlFromSeed")
tests := []struct {
seed CrawlSeed
@@ -324,11 +316,11 @@ resources:
Document: *d,
}, nil
},
func(d CrawledDocument, mode index.Mode) error {
func(d CrawledDocument, cr Crawler) error {
visited[d.ID()]++
return nil
},
utils.NewSeenMap(),
make(map[string]struct{}),
)
if lv, lc := len(visited), len(tc.corpus); lv != lc {
t.Errorf("error: %d of %d documents visited.", lv, lc)

View File

@@ -16,8 +16,6 @@ import (
"strings"
"time"
"sigs.k8s.io/kustomize/api/internal/crawl/utils"
"sigs.k8s.io/kustomize/api/internal/crawl/crawler"
"sigs.k8s.io/kustomize/api/internal/crawl/doc"
"sigs.k8s.io/kustomize/api/internal/crawl/httpclient"
@@ -32,8 +30,6 @@ var logger = log.New(os.Stdout, "Github Crawler: ",
type githubCrawler struct {
client GhClient
query Query
// branchMap maps github repositories to their default branches
branchMap map[string]string
}
type GhClient struct {
@@ -55,60 +51,13 @@ func NewCrawler(accessToken string, retryCount uint64, client *http.Client,
},
accessToken: accessToken,
},
query: query,
branchMap: map[string]string{},
query: query,
}
}
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.
func (gc githubCrawler) Crawl(ctx context.Context,
output chan<- crawler.CrawledDocument, seen utils.SeenMap) 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)
func (gc githubCrawler) Crawl(
ctx context.Context, output chan<- crawler.CrawledDocument) error {
noETagClient := GhClient{
RequestConfig: gc.client.RequestConfig,
@@ -117,25 +66,12 @@ func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
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
// multiple queries that split the search space into chunks of at most
// 1000 files to get all of the data.
for i := 0; i < 5; i++ {
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)
}
}
ranges, err := FindRangesForRepoSearch(newCache(noETagClient, gc.query))
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)
}
@@ -143,30 +79,20 @@ func (gc githubCrawler) CrawlSingleRange(ctx context.Context,
// Query each range for files.
errs := make(multiError, 0)
queryResult := RangeQueryResult{}
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 {
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 {
return reProcessQueryRanges, errs
return errs
}
return reProcessQueryRanges, nil
return nil
}
// FetchDocument first tries to fetch the document with d.FilePath. If it fails,
// it will try to add each string in konfig.RecognizedKustomizationFileNames() to
// d.FilePath, and try to fetch the document again.
func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error {
repoURL := d.RepositoryURL + "/" + d.FilePath + "?ref=" + d.DefaultBranch
repoSpec, err := git.NewRepoSpecFromUrl(repoURL)
@@ -178,13 +104,9 @@ func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error
"/" + repoSpec.Ref + "/" + repoSpec.Path
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 {
d.IsSame = httpclient.FromCache(resp.Header)
defer CloseResponseBody(resp)
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
@@ -193,18 +115,18 @@ func (gc githubCrawler) FetchDocument(_ context.Context, d *doc.Document) error
d.FilePath = d.FilePath + path
return nil
}
return err
}
resp, errGetRawUserContent := gc.client.GetRawUserContent(url)
if err := handle(resp, errGetRawUserContent, ""); err == nil {
resp, err := gc.client.GetRawUserContent(url)
if err := handle(resp, err, ""); err == nil {
return nil
}
for _, file := range konfig.RecognizedKustomizationFileNames() {
resp, errGetRawUserContent = gc.client.GetRawUserContent(url + "/" + file)
if err = handle(resp, errGetRawUserContent, "/"+file); err == nil {
return nil
resp, err = gc.client.GetRawUserContent(url + "/" + file)
err := handle(resp, err, "/"+file)
if err != nil {
continue
}
}
return fmt.Errorf("file not found: %s, error: %v", url, err)
@@ -236,32 +158,10 @@ func (gc githubCrawler) Match(d *doc.Document) bool {
return strings.Contains(repoSpec.Host, "github.com")
}
type RangeQueryResult struct {
totalDocCnt uint64
seenDocCnt uint64
newDocCnt uint64
errorCnt uint64
}
func (r *RangeQueryResult) Add(other RangeQueryResult) {
r.totalDocCnt += other.totalDocCnt
r.newDocCnt += other.newDocCnt
r.seenDocCnt += other.seenDocCnt
r.errorCnt += other.errorCnt
}
func (r *RangeQueryResult) String() string {
return fmt.Sprintf("got %d files from API. "+
"%d have been seen before. %d are new and sent to the output channel."+
" %d have kustomizationResultAdapter errors.",
r.totalDocCnt, r.seenDocCnt, r.newDocCnt, r.errorCnt)
}
// processQuery follows all of the pages in a query, and updates/adds the
// documents from the crawl to the datastore/index.
func processQuery(ctx context.Context, gcl GhClient, query string,
output chan<- crawler.CrawledDocument, seen utils.SeenMap,
branchMap map[string]string) (bool, RangeQueryResult, error) {
output chan<- crawler.CrawledDocument) error {
queryPages := make(chan GhResponseInfo)
@@ -277,84 +177,57 @@ func processQuery(ctx context.Context, gcl GhClient, query string,
close(queryPages)
}()
reProcessQuery := false
errs := make(multiError, 0)
result := RangeQueryResult{}
pageID := 1
errorCnt := 0
totalCnt := 0
for page := range queryPages {
if page.Error != nil {
errs = append(errs, page.Error)
continue
}
pageResult := RangeQueryResult{}
for _, file := range page.Parsed.Items {
k, err := kustomizationResultAdapter(gcl, file, seen, branchMap)
k, err := kustomizationResultAdapter(gcl, file)
if err != nil {
logger.Printf("kustomizationResultAdapter failed: %v", err)
errs = append(errs, err)
pageResult.errorCnt++
errorCnt++
}
if k != nil {
pageResult.newDocCnt++
output <- k
} else {
pageResult.seenDocCnt++
}
pageResult.totalDocCnt++
totalCnt++
}
logger.Printf("processQuery [TotalCount %d - page %d]: %s",
page.Parsed.TotalCount, pageID, pageResult.String())
result.Add(pageResult)
pageID++
if page.Parsed.TotalCount > githubMaxResultsPerQuery {
reProcessQuery = true
}
logger.Printf("got %d files out of %d from API. %d of %d had errors\n",
totalCnt, page.Parsed.TotalCount, errorCnt, totalCnt)
}
logger.Printf("Summary of processQuery: %s", result.String())
return reProcessQuery, result, errs
return errs
}
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec, seen utils.SeenMap,
branchMap map[string]string) (crawler.CrawledDocument, error) {
url := gcl.ReposRequest(k.Repository.FullName)
defaultBranch, err := gcl.GetDefaultBranch(url, k.Repository.URL, branchMap)
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
}
func kustomizationResultAdapter(gcl GhClient, k GhFileSpec) (
crawler.CrawledDocument, error) {
data, err := gcl.GetFileData(k)
if err != nil {
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{
Document: doc.Document{
DocumentData: string(data),
FilePath: k.Path,
DefaultBranch: defaultBranch,
RepositoryURL: k.Repository.URL,
User: doc.UserName(k.Repository.URL),
},
}
creationTime, err := gcl.GetFileCreationTime(k)
@@ -379,7 +252,7 @@ func (gcl GhClient) ForwardPaginatedQuery(ctx context.Context, query string,
output chan<- GhResponseInfo) error {
logger.Println("querying: ", query)
response := gcl.parseGithubResponseWithRetry(query)
response := gcl.parseGithubResponse(query)
if response.Error != nil {
return response.Error
@@ -392,7 +265,7 @@ func (gcl GhClient) ForwardPaginatedQuery(ctx context.Context, query string,
case <-ctx.Done():
return nil
default:
response = gcl.parseGithubResponseWithRetry(response.NextURL)
response = gcl.parseGithubResponse(response.NextURL)
if response.Error != nil {
return response.Error
}
@@ -420,10 +293,7 @@ func (gcl GhClient) GetFileData(k GhFileSpec) ([]byte, error) {
return nil, fmt.Errorf("%+v: could not read '%s' metadata: %v",
k, url, err)
}
if err := resp.Body.Close(); err != nil {
return nil, err
}
resp.Body.Close()
type githubContentRawURL struct {
DownloadURL string `json:"download_url,omitempty"`
@@ -442,32 +312,18 @@ func (gcl GhClient) GetFileData(k GhFileSpec) ([]byte, error) {
k, rawURL.DownloadURL, err)
}
defer CloseResponseBody(resp)
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
return data, err
}
func CloseResponseBody(resp *http.Response) {
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
}
func (gcl GhClient) GetDefaultBranch(url string) (string, error) {
resp, err := gcl.GetReposData(url)
if err != nil {
return "", fmt.Errorf(
"'%s' could not get default_branch: %v", url, err)
}
defer CloseResponseBody(resp)
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf(
@@ -519,7 +375,7 @@ func (gcl GhClient) GetFileCreationTime(
}
}
defer CloseResponseBody(resp)
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return defaultTime, fmt.Errorf(
@@ -587,8 +443,6 @@ type githubResponse struct {
// This is the number of files that match the query.
TotalCount uint64 `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
// Github representation of a file.
Items []GhFileSpec `json:"items,omitempty"`
}
@@ -631,17 +485,6 @@ func parseGithubLinkFormat(links string) (string, string) {
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 {
resp, err := gcl.SearchGithubAPI(getRequest)
requestInfo := GhResponseInfo{
@@ -655,7 +498,7 @@ func (gcl GhClient) parseGithubResponse(getRequest string) GhResponseInfo {
}
var data []byte
defer CloseResponseBody(resp)
defer resp.Body.Close()
data, requestInfo.Error = ioutil.ReadAll(resp.Body)
if requestInfo.Error != nil {
return requestInfo
@@ -716,15 +559,7 @@ func (gcl GhClient) Do(query string) (*http.Response, error) {
return nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("token %s", gcl.accessToken))
// gcl.client.Do: a non-2xx status code doesn't cause an error.
// See https://golang.org/pkg/net/http/#Client.Do for more info.
resp, err := gcl.client.Do(req)
if resp != nil && resp.StatusCode != http.StatusOK {
err = fmt.Errorf("GhClient.Do(%s) failed with response code: %d",
query, resp.StatusCode)
}
return resp, err
return gcl.client.Do(req)
}
func (gcl GhClient) getWithRetry(
@@ -734,10 +569,13 @@ func (gcl GhClient) getWithRetry(
retryCount := gcl.retryCount
for resp != nil && resp.StatusCode == http.StatusForbidden && retryCount > 0 {
for err == nil &&
resp.StatusCode == http.StatusForbidden &&
retryCount > 0 {
retryTime := resp.Header.Get("Retry-After")
i, errAtoi := strconv.Atoi(retryTime)
if errAtoi != nil {
i, err := strconv.Atoi(retryTime)
if err != nil {
return resp, fmt.Errorf(
"query '%s' forbidden without 'Retry-After'", query)
}

View File

@@ -116,11 +116,9 @@ type RequestConfig struct {
// the URL method to get the string value of the URL. See request.CopyWith, to
// understand why the request object is useful.
func (rc RequestConfig) CodeSearchRequestWith(query Query) request {
vals := url.Values{
"sort": []string{"indexed"},
"order": []string{"desc"},
}
req := rc.makeRequest("search/code", query, vals)
req := rc.makeRequest("search/code", query)
req.vals.Set("sort", "indexed")
req.vals.Set("order", "desc")
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.
func (rc RequestConfig) ContentsRequest(fullRepoName, path string) string {
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 {
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
// for the Github API to find the commits that affect this file.
func (rc RequestConfig) CommitsRequest(fullRepoName, path string) string {
uri := fmt.Sprintf("repos/%s/commits", fullRepoName)
vals := url.Values{
"path": []string{path},
}
return rc.makeRequest(uri, Query{}, vals).URL()
return rc.makeRequest(uri, Query{Path(escapeSpace(path))}).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))
return request{
@@ -166,7 +166,7 @@ type request struct {
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.
func (r request) CopyWith(queryParams ...queryField) request {
cpy := r

View File

@@ -101,7 +101,7 @@ func TestGithubSearchQuery(t *testing.T) {
"examples/helloWorld/kustomization.yaml?per_page=100",
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{
@@ -121,7 +121,7 @@ func TestGithubSearchQuery(t *testing.T) {
"examples%201/helloWorld/kustomization.yaml?per_page=100",
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 * 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
// search API), than it does to perform these range searches.
import (
"fmt"
"math/bits"
"strconv"
"strings"
)
// Files cannot be more than 2^19 bytes, according to
@@ -114,7 +112,7 @@ const (
// Interface instead of struct for testing purposes.
// Not expecting to have multiple implementations.
type cachedSearch interface {
CountResults(uint64, uint64) (uint64, error)
CountResults(uint64) (uint64, error)
RequestString(filesize rangeFormatter) string
}
@@ -141,7 +139,7 @@ type cachedSearch interface {
// problematic). The current cache implementation looks at the
// 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
// 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
// predecessor in constant time. Ultimately since the search is rate
// 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]
if cached {
return count, nil
}
sizeRange := RangeWithin{lowerBound, upperBound}
sizeRange := RangeWithin{0, upperBound}
rangeRequest := c.RequestString(sizeRange)
result := c.gcl.parseGithubResponseWithRetry(rangeRequest)
result := c.gcl.parseGithubResponse(rangeRequest)
if result.Error != nil {
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",
c.cache[prev], result.Parsed.TotalCount)
result = c.gcl.parseGithubResponseWithRetry(rangeRequest)
result = c.gcl.parseGithubResponse(rangeRequest)
if result.Error != nil {
return count, result.Error
}
@@ -221,8 +219,8 @@ func (c githubCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64,
}
count = result.Parsed.TotalCount
logger.Printf("Caching new query %s, with count %d (incomplete_results: %v)\n",
sizeRange.RangeString(), count, result.Parsed.IncompleteResults)
logger.Printf("Caching new query %s, with count %d\n",
sizeRange.RangeString(), count)
c.cache[upperBound] = count
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
// are sorted by last indexed, and retrieved on regular intervals, it should be
// sufficient to get most if not all documents.
func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64) ([]string, error) {
totalFiles, err := cache.CountResults(lowerBound, upperBound)
func FindRangesForRepoSearch(cache cachedSearch) ([]string, error) {
totalFiles, err := cache.CountResults(githubMaxFileSize)
if err != nil {
return nil, err
}
@@ -249,7 +247,7 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
if githubMaxResultsPerQuery >= totalFiles {
return []string{
cache.RequestString(RangeWithin{lowerBound, upperBound}),
cache.RequestString(RangeWithin{0, githubMaxFileSize}),
}, nil
}
@@ -277,7 +275,6 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
// range.
filesAccessible := uint64(0)
sizes := make([]uint64, 0)
sizes = append(sizes, lowerBound)
for filesAccessible < totalFiles {
target := filesAccessible + githubMaxResultsPerQuery
if target >= totalFiles {
@@ -287,22 +284,22 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
logger.Printf("%d accessible files, next target = %d\n",
filesAccessible, target)
size, err := FindFileSize(cache, target, lowerBound, upperBound)
cur, err := lowerBoundFileCount(cache, target)
if err != nil {
return nil, err
}
// If there are more than 1000 files in the next bucket, we must
// advance anyway and lose out on some files :(.
if l := len(sizes); l > 0 && sizes[l-1] == size {
size++
if l := len(sizes); l > 0 && sizes[l-1] == cur {
cur++
}
nextAccessible, err := cache.CountResults(lowerBound, size)
nextAccessible, err := cache.CountResults(cur)
if err != nil {
return nil, fmt.Errorf(
"cache should be populated at %d already, got %v",
size, err)
cur, err)
}
if nextAccessible < filesAccessible {
return nil, fmt.Errorf(
@@ -312,31 +309,31 @@ func FindRangesForRepoSearch(cache cachedSearch, lowerBound, upperBound uint64)
filesAccessible = nextAccessible
if nextAccessible < totalFiles {
sizes = append(sizes, size)
sizes = append(sizes, cur)
}
}
sizes = append(sizes, upperBound)
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
// 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
// results. It is left to the caller to handle this bit of logic and guarantee
// forward progession in this case.
func FindFileSize(
cache cachedSearch, targetFileCount, lowerBound, upperBound uint64) (uint64, error) {
func lowerBoundFileCount(
cache cachedSearch, targetFileCount uint64) (uint64, error) {
// Binary search for file sizes that make up the next <=1000 element
// chunk.
cur := lowerBound
increase := (upperBound - lowerBound) / 2
cur := uint64(0)
increase := githubMaxFileSize / 2
for increase > 0 {
mid := cur + increase
count, err := cache.CountResults(lowerBound, mid)
count, err := cache.CountResults(mid)
if err != nil {
return count, err
}
@@ -356,24 +353,26 @@ func FindFileSize(
}
func formatFilesizeRanges(cache cachedSearch, sizes []uint64) []string {
n := len(sizes)
if n < 2 {
return []string{}
ranges := make([]string, 0, len(sizes)+1)
if len(sizes) > 0 {
ranges = append(ranges, cache.RequestString(
RangeLessThan{sizes[0] + 1},
))
}
ranges := make([]string, 0, n-1)
ranges = append(ranges, cache.RequestString(RangeWithin{sizes[0], sizes[1]}))
for i := 1; i < n-1; i++ {
ranges = append(ranges, cache.RequestString(RangeWithin{sizes[i] + 1, sizes[i+1]}))
for i := 0; i < len(sizes)-1; 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
}
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 (
"fmt"
"log"
"reflect"
"testing"
)
@@ -11,8 +10,8 @@ type testCachedSearch struct {
cache map[uint64]uint64
}
func (c testCachedSearch) CountResults(lowerBound, upperBound uint64) (uint64, error) {
log.Printf("CountResults(%05x)\n", upperBound)
func (c testCachedSearch) CountResults(upperBound uint64) (uint64, error) {
fmt.Printf("CountResults(%05x)\n", upperBound)
count, ok := c.cache[upperBound]
if !ok {
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 {
t.Errorf("Error while finding ranges: %v", err)
}
expected := []string{
"0..106", // cache.RequestString(RangeWithin{0x00, 0x6a}),
"107..128", // cache.RequestString(RangeWithin{0x6b, 0x80}),
"129..256", // cache.RequestString(RangeWithin{0x81, 0x100}),
"257..4095", // cache.RequestString(RangeWithin{0x101, 0xfff}),
"4096..524288", // cache.RequestString(RangeWithin{0x1000, 0x80000}),
"<107", // cache.RequestString(RangeLessThan{0x6b}),
"107..128", // cache.RequestString(RangeWithin{0x6b, 0x80}),
"129..256", // cache.RequestString(RangeWithin{0x81, 0x100}),
"257..4095", // cache.RequestString(RangeWithin{0x101, 0xfff}),
">4095", // cache.RequestString(RangeGreaterThan{0xfff}),
}
if !reflect.DeepEqual(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,9 +2,6 @@ package doc
import (
"fmt"
"log"
"path/filepath"
"sort"
"strings"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
@@ -46,36 +43,21 @@ type KustomizationDocument 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 {
return fmt.Sprintf("%s %s %s %v %v %v len(identifiers):%v len(values):%v",
doc.RepositoryURL, doc.FilePath, doc.DefaultBranch, doc.CreationTime,
doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values))
}
// IsKustomizationFile determines whether a file path is a kustomization file
func IsKustomizationFile(path string) bool {
basename := filepath.Base(path)
for _, name := range konfig.RecognizedKustomizationFileNames() {
if basename == name {
return true
}
}
return false
doc.IsSame, doc.Kinds, len(doc.Identifiers), len(doc.Values))
}
// Implements the CrawlerDocument interface.
func (doc *KustomizationDocument) GetResources(
includeResources, includeTransformers, includeGenerators bool) ([]*Document, error) {
if !IsKustomizationFile(doc.FilePath) {
func (doc *KustomizationDocument) GetResources() ([]*Document, error) {
isResource := true
for _, suffix := range konfig.RecognizedKustomizationFileNames() {
if strings.HasSuffix(doc.FilePath, "/"+suffix) {
isResource = false
}
}
if isResource {
return []*Document{}, nil
}
@@ -93,46 +75,19 @@ func (doc *KustomizationDocument) GetResources(
}
k.FixKustomizationPostUnmarshalling()
res := make([]*Document, 0)
if includeResources {
resourceDocs := doc.CollectDocuments(k.Resources, "resource")
res = append(res, resourceDocs...)
}
if includeGenerators {
generatorDocs := doc.CollectDocuments(k.Generators, "generator")
res = append(res, generatorDocs...)
}
if includeTransformers {
transformerDocs := doc.CollectDocuments(k.Transformers, "transformer")
res = append(res, transformerDocs...)
res := make([]*Document, 0, len(k.Resources))
for _, r := range k.Resources {
next, err := doc.Document.FromRelativePath(r)
if err != nil {
fmt.Printf("GetResources error: %v\n", err)
continue
}
res = append(res, &next)
}
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) {
data := []byte(doc.DocumentData)
@@ -161,8 +116,6 @@ func (doc *KustomizationDocument) readBytes() ([]map[string]interface{}, error)
return configs, nil
}
// ParseYAML parses doc.Document and sets the following fields of doc:
// Kinds, Values, Identifiers.
func (doc *KustomizationDocument) ParseYAML() error {
doc.Identifiers = make([]string, 0)
doc.Values = make([]string, 0)
@@ -206,13 +159,6 @@ func (doc *KustomizationDocument) ParseYAML() error {
doc.Identifiers = append(doc.Identifiers, key)
}
// Without sorting these fields, every time when the string order in these fields changes,
// the document in the index will be updated.
// Sorting these fields are necessary to avoid a document being updated unnecessarily.
sort.Strings(doc.Kinds)
sort.Strings(doc.Values)
sort.Strings(doc.Identifiers)
return nil
}

View File

@@ -189,13 +189,11 @@ metadata:
}
}
type TestStructForGetResources struct {
doc KustomizationDocument
resources []*Document
}
func TestGetResources(t *testing.T) {
tests := []TestStructForGetResources{
tests := []struct {
doc KustomizationDocument
resources []*Document
}{
{
doc: KustomizationDocument{
Document: Document{
@@ -215,27 +213,19 @@ resources:
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/base",
FileType: "resource",
User: "sigs.k8s.io",
},
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/otherbase",
FileType: "resource",
User: "sigs.k8s.io",
},
{
RepositoryURL: "sigs.k8s.io/kustomize",
FilePath: "some/path/to/kdir/file.yaml",
FileType: "resource",
User: "sigs.k8s.io",
},
{
RepositoryURL: "https://github.com/kubernetes-sigs/kustomize",
FilePath: "examples/helloWorld",
DefaultBranch: "v3.1.0",
FileType: "resource",
User: "kubernetes-sigs",
},
},
},
@@ -258,12 +248,9 @@ resources:
resources: []*Document{},
},
}
runTest(t, tests, true, false, false)
}
func runTest(t *testing.T, tests []TestStructForGetResources, includeResources, includeTransformers, includeGenerators bool) {
for _, test := range tests {
res, err := test.doc.GetResources(includeResources, includeTransformers, includeGenerators)
res, err := test.doc.GetResources()
if err != nil {
t.Errorf("Unexpected error: %v\n", err)
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 {
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"`
RepositoryURL string `json:"repositoryUrl,omitempty"`
FilePath string `json:"filePath,omitempty"`
DefaultBranch string `json:"defaultBranch,omitempty"`
DocumentData string `json:"document,omitempty"`
CreationTime *time.Time `json:"creationTime,omitempty"`
IsSame bool `json:"-"`
// FileType can be one of the following:
// "generator", "transformer", "resource", "".
FileType string `json:"fileType,omitempty"`
}
// Implements the CrawlerDocument interface.
@@ -33,21 +27,14 @@ func (doc *Document) GetDocument() *Document {
func (doc *Document) Copy() *Document {
return &Document{
RepositoryURL: doc.RepositoryURL,
User: doc.User,
FilePath: doc.FilePath,
DefaultBranch: doc.DefaultBranch,
DocumentData: doc.DocumentData,
CreationTime: doc.CreationTime,
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.
func (doc *Document) WasCached() bool {
return doc.IsSame
@@ -60,7 +47,6 @@ func (doc *Document) FromRelativePath(newFile string) (Document, error) {
RepositoryURL: repoSpec.Host + path.Clean(repoSpec.OrgRepo),
FilePath: path.Clean(repoSpec.Path),
DefaultBranch: repoSpec.Ref,
User: UserName(repoSpec.Host + path.Clean(repoSpec.OrgRepo)),
}, nil
}
// else document is probably relative path.
@@ -68,7 +54,6 @@ func (doc *Document) FromRelativePath(newFile string) (Document, error) {
ret := Document{
RepositoryURL: doc.RepositoryURL,
DefaultBranch: doc.DefaultBranch,
User: UserName(doc.RepositoryURL),
}
ogDir, _ := path.Split(doc.FilePath)
@@ -93,32 +78,11 @@ func (doc *Document) ID() string {
}
func (doc *Document) RepositoryFullName() string {
url := TrimUrl(doc.RepositoryURL)
sections := strings.Split(url, "/")
doc.RepositoryURL = strings.TrimRight(doc.RepositoryURL, "/")
sections := strings.Split(doc.RepositoryURL, "/")
l := len(sections)
if l < 2 {
return url
return doc.RepositoryURL
}
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",
FilePath: "path/to/other/file/resource.yaml",
DefaultBranch: "master",
User: "example.com",
},
},
{
@@ -37,7 +36,6 @@ func TestFromRelativePath(t *testing.T) {
RepositoryURL: "example.com/repo",
FilePath: "path/to/other/file/patch.yaml",
DefaultBranch: "master",
User: "example.com",
},
},
{
@@ -46,7 +44,6 @@ func TestFromRelativePath(t *testing.T) {
RepositoryURL: "example.com/repo",
FilePath: "path/to/file/service.yaml",
DefaultBranch: "master",
User: "example.com",
},
},
},
@@ -68,7 +65,7 @@ func TestFromRelativePath(t *testing.T) {
func TestDocument_RepositoryFullName(t *testing.T) {
testCases := []struct {
doc Document
doc Document
expectedRepositoryFullName string
}{
{
@@ -95,12 +92,6 @@ func TestDocument_RepositoryFullName(t *testing.T) {
},
expectedRepositoryFullName: "",
},
{
doc: Document{
RepositoryURL: "git@github.com:user/repo",
},
expectedRepositoryFullName: "user/repo",
},
}
for _, tc := range testCases {
@@ -111,40 +102,4 @@ func TestDocument_RepositoryFullName(t *testing.T) {
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

@@ -1,13 +1,13 @@
module sigs.k8s.io/kustomize/api/internal/crawl
go 1.12
go 1.13
require (
github.com/elastic/go-elasticsearch/v6 v6.8.5
github.com/elastic/go-elasticsearch/v6 v6.8.2
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/mux v1.7.3
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
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
)

View File

@@ -2,10 +2,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -40,10 +44,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elastic/go-elasticsearch/v6 v6.8.2 h1:rp5DGrd63V5c6nHLjF6QEXUpZSvs0+QM3ld7m9VhV2g=
github.com/elastic/go-elasticsearch/v6 v6.8.2/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -87,11 +95,13 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -105,9 +115,12 @@ github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvL
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.19.1/go.mod h1:2CEc4Fxx3vxDv7g8DyXkHCBF73AOzAymcJAprs2vCps=
github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
@@ -119,8 +132,9 @@ github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -130,11 +144,18 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
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/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/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=
@@ -145,6 +166,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -178,7 +200,9 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matoous/godox v0.0.0-20190910121045-032ad8106c86/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
@@ -193,6 +217,8 @@ 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 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/monopole/mdrip v1.0.0/go.mod h1:N1/ppRG9CaPeUKAUHZ3dUlfOT81lTpKZLkyhCvTETwM=
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/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=
@@ -225,15 +251,19 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/securego/gosec v0.0.0-20190912120752-140048b2a218/go.mod h1:q6oYAujd2qyeU4cJqIri4LBIgdHXGvxWHZ1E29HNFRE=
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@@ -258,11 +288,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.3/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@@ -283,6 +315,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -305,8 +338,9 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc=
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -330,7 +364,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM=
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s=
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -339,6 +374,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -357,6 +393,8 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911230505-6bfd74cf029c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -380,6 +418,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/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@@ -390,11 +429,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@@ -403,12 +439,17 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
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/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.1/go.mod h1:A+ATnlHqzictQfQC1q3KB/T6MSr0UWQsrrLxMWkge2E=
sigs.k8s.io/kustomize/api v0.2.0 h1:e++6JpysnnlUbHmFrv6jvfF5rFlgQ103bS1DO7r5bWA=
sigs.k8s.io/kustomize/api v0.2.0/go.mod h1:zVtMg179jW1gr74jo9fc2Ac9dLYLTZZThc3DDb9lDW4=
sigs.k8s.io/kustomize/api v0.3.0/go.mod h1:4jaPCtRzxfQLFdYq4gYo40dBGW1hyPp/f4AuiZB5dAQ=
sigs.k8s.io/kustomize/pluginator/v2 v2.0.0/go.mod h1:zrXhTv8BAKt0egmZX/8AtMOSFUSWM9YuoHvvqz8/eHE=
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0 h1:otg4dLFc03c3gzl+2CV8GPGcd1kk8wjXwD+UhhcCn5I=
sigs.k8s.io/kustomize/pseudo/k8s v0.1.0/go.mod h1:bl/gVJgYYhJZCZdYU2BfnaKYAlqFkgbJEkpl302jEss=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"time"
es "github.com/elastic/go-elasticsearch/v6"
@@ -20,18 +21,12 @@ const IndexConfig = `
"repositoryUrl": {
"type": "keyword"
},
"user": {
"type": "keyword"
},
"filePath": {
"type": "keyword"
},
"defaultBranch": {
"type": "keyword"
},
"fileType": {
"type": "keyword"
},
"document": {
"type": "text"
},
@@ -93,7 +88,7 @@ func (idx *index) responseErrorOrNil(info string, res *esapi.Response,
defer res.Body.Close()
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 {
@@ -184,47 +179,44 @@ func (idx *index) DeleteIndex() error {
}
// Insert or update the document by ID.
func (idx *index) Put(uniqueID string, doc interface{}) error {
exists, err := idx.Exists(uniqueID)
func (idx *index) Put(uniqueID string, doc interface{}) (string, error) {
body, err := json.Marshal(doc)
if err != nil {
return err
return "", err
}
if exists {
docBytes, err := json.Marshal(doc)
if err != nil {
return err
}
body := byteJoin(`{"doc":`, docBytes, `}`)
req := esapi.IndexRequest{
Index: idx.name,
Body: bytes.NewReader(body),
DocumentID: uniqueID,
}
res, err := req.Do(idx.ctx, idx.client)
// For a document with a given id, every call of IndexRequest.Do will increase the version of a document.
// To avoid increasing the document version unnecessarily, use UpdateRequest here.
req := esapi.UpdateRequest{
Index: idx.name,
Body: bytes.NewReader(body),
DocumentID: uniqueID,
var id string
readId := func(reader io.Reader) error {
type InsertResult struct {
ID string `json:"_id,omitempty"`
}
res, err := req.Do(idx.ctx, idx.client)
err = idx.responseErrorOrNil("could not update document",
res, err, ignoreResponseBody)
} else {
body, err := json.Marshal(doc)
var ir InsertResult
data, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
req := esapi.IndexRequest{
Index: idx.name,
Body: bytes.NewReader(body),
DocumentID: uniqueID,
err = json.Unmarshal(data, &ir)
if err != nil {
return err
}
res, err := req.Do(idx.ctx, idx.client)
id = ir.ID
err = idx.responseErrorOrNil("could not insert document",
res, err, ignoreResponseBody)
return nil
}
return err
// populates the id field.
err = idx.responseErrorOrNil("could not insert document",
res, err, readId)
return id, err
}
type scrollUpdater func(string, readerFunc) error
@@ -304,24 +296,3 @@ func (idx *index) Delete(id string) error {
fmt.Sprintf("could not delete id(%s) from index(%s)", id, idx.name),
res, err, ignoreResponseBody)
}
// Check whether a given document id is in the index
func (idx *index) Exists(id string) (bool, error) {
op := idx.client.Exists
res, err := op(
idx.name,
id,
op.WithContext(idx.ctx),
op.WithPretty(),
)
if res != nil && !res.IsError() {
return true, nil
} else if res != nil && res.StatusCode == 404 {
return false, nil
} else {
return false, idx.responseErrorOrNil(
fmt.Sprintf("could not check the existence of id(%s) from index(%s)", id, idx.name),
res, err, ignoreResponseBody)
}
}

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"strings"
"time"
@@ -17,13 +16,6 @@ const (
AggregationKeyword = "aggs"
)
type Mode int
const (
InsertOrUpdate = iota
Delete
)
// Redefinition of Hits structure. Must match the json string of
// KustomizeResult.Hits.Hits. Declared as a convenience for iteration.
type KustomizeHits []struct {
@@ -99,14 +91,14 @@ type KustomizeIndex struct {
}
// Create index reference to the index containing the kustomize documents.
func NewKustomizeIndex(ctx context.Context, indexName string) (*KustomizeIndex, error) {
idx, err := newIndex(ctx, indexName)
func NewKustomizeIndex(ctx context.Context) (*KustomizeIndex, error) {
idx, err := newIndex(ctx, "kustomize")
if err != nil {
return nil, err
}
indicesExistsOp := idx.client.Indices.Exists
resp, err := indicesExistsOp([]string{indexName},
resp, err := indicesExistsOp([]string{"kustomize"},
indicesExistsOp.WithContext(idx.ctx),
indicesExistsOp.WithPretty())
if err != nil {
@@ -114,9 +106,9 @@ func NewKustomizeIndex(ctx context.Context, indexName string) (*KustomizeIndex,
}
if resp.StatusCode == 200 {
log.Printf("The %s index already exists", indexName)
fmt.Printf("The kustomize index already exists\n")
} else {
log.Printf("Creating the %s index\n", indexName)
fmt.Printf("Creating the kustomize index\n")
if err := idx.CreateIndex([]byte(IndexConfig)); err != nil {
return nil, err
}
@@ -254,7 +246,7 @@ func (it *KustomizeIterator) Next() bool {
}
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)
}
@@ -270,7 +262,7 @@ func (it *KustomizeIterator) Value() KustomizeResult {
return it.scrollImpl
}
// Check if any errors have occurred.
// Check if any errors have occured.
func (it *KustomizeIterator) Err() error {
return it.err
}
@@ -301,13 +293,12 @@ func (ki *KustomizeIndex) IterateQuery(query []byte, batchSize int,
}
// type specific Put for inserting structured kustomization documents.
func (ki *KustomizeIndex) Put(id string, doc *doc.KustomizationDocument) error {
return ki.index.Put(id, doc)
}
// Delete a document with a given id from the kustomize index.
func (ki *KustomizeIndex) Delete(id string) error {
return ki.index.Delete(id)
func (ki *KustomizeIndex) Put(id string, doc *doc.KustomizationDocument) (string, error) {
id, err := ki.index.Put(id, doc)
if err != nil {
return id, fmt.Errorf("could not insert in elastic: %v", err)
}
return id, nil
}
// Kustomize search options: What metrics should be returned? Kind Aggregation,
@@ -343,7 +334,7 @@ func (ki *KustomizeIndex) Search(query string,
if err != nil {
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
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 {
return err
}
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd := exec.Command(
gitProgram,
"clone",
"--depth=1",
repoSpec.CloneSpec(),
"-b",
repoSpec.Ref,
"init",
repoSpec.Dir.String())
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err = cmd.Run()
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(
err,
"trouble cloning git repo %v in %s",
repoSpec.CloneSpec(), repoSpec.Dir.String())
"trouble initializing empty git repo in %s",
repoSpec.Dir.String())
}
cmd = exec.Command(
gitProgram,
"remote",
"add",
"origin",
repoSpec.CloneSpec())
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
log.Printf("Error setting git remote: %s", out.String())
return errors.Wrapf(
err,
"trouble adding remote %s",
repoSpec.CloneSpec())
}
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd = exec.Command(
gitProgram,
"fetch",
"--depth=1",
"origin",
repoSpec.Ref)
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
cmd = exec.Command(
gitProgram,
"pull",
"origin",
"master")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Dir = repoSpec.Dir.String()
err := cmd.Run()
if err != nil {
return errors.Wrapf(err, "trouble pulling %s", repoSpec.OrgRepo)
}
if repoSpec.Ref == "" {
repoSpec.Ref = "master"
}
cmd = exec.Command(gitProgram, "checkout", repoSpec.Ref)
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
return errors.Wrapf(
err, "trouble checking out href %s", repoSpec.Ref)
}
}
cmd = exec.Command(
gitProgram,
"reset",
"--hard",
"FETCH_HEAD")
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
log.Printf("Error performing git reset: %s", out.String())
return errors.Wrapf(
err, "trouble hard resetting empty repository to %s", repoSpec.Ref)
}
cmd = exec.Command(
@@ -58,11 +120,10 @@ func ClonerUsingGitExec(repoSpec *RepoSpec) error {
"--init",
"--recursive")
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Dir = repoSpec.Dir.String()
err = cmd.Run()
if err != nil {
return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.CloneSpec())
return errors.Wrapf(err, "trouble fetching submodules for %s", repoSpec.Ref)
}
return nil

View File

@@ -4,7 +4,7 @@
// SPDX-License-Identifier: Apache-2.0
// 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
// some better technique comes along.

View File

@@ -190,7 +190,5 @@ varReference:
kind: StatefulSet
- path: metadata/labels
- path: metadata/annotations
`
)

View File

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

View File

@@ -1157,8 +1157,6 @@ vars:
name: my-deployment
labels:
my-label: $(NAMESPACE)
annotations:
my-annotation: $(NAMESPACE)
spec:
template:
spec:
@@ -1178,8 +1176,6 @@ vars:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
my-annotation: my-namespace
labels:
my-label: my-namespace
name: my-deployment

View File

@@ -9,7 +9,6 @@ import (
"fmt"
"os"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
@@ -86,17 +85,11 @@ func (kvl *loader) keyValuesFromFileSources(sources []string) ([]types.Pair, err
if err != nil {
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
}
// 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) {
var kvs []types.Pair
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
- goimports
# - golint
# - gosec
- gosec
- gosimple
- govet
- ineffassign

View File

@@ -52,10 +52,6 @@ func Complete(cmd *cobra.Command) *complete.Command {
Flags: map[string]complete.Predictor{},
Sub: map[string]*complete.Command{},
}
if strings.Contains(cmd.Use, "DIR") {
// if usage contains directory, then use a file predictor
cc.Args = predict.Dirs("*")
}
// add completion for each subcommand
for i := range cmd.Commands() {

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
// be embedded into other go binaries as a way of packaging the "config" command as part
// of another binary.
@@ -93,24 +69,15 @@ func NewConfigCommand(name string) *cobra.Command {
name = strings.TrimSpace(name + " config")
commands.ExitOnError = true
root.AddCommand(commands.AnnotateCommand(name))
root.AddCommand(commands.GrepCommand(name))
root.AddCommand(commands.TreeCommand(name))
root.AddCommand(commands.CatCommand(name))
root.AddCommand(commands.FmtCommand(name))
root.AddCommand(commands.MergeCommand(name))
root.AddCommand(commands.Merge3Command(name))
root.AddCommand(commands.CountCommand(name))
root.AddCommand(commands.RunFnCommand(name))
root.AddCommand(commands.XArgsCommand())
root.AddCommand(commands.WrapCommand())
root.AddCommand(commands.SetCommand(name))
root.AddCommand(commands.ListSettersCommand(name))
root.AddCommand(commands.CreateSetterCommand(name))
root.AddCommand(commands.CreateSubstitutionCommand(name))
root.AddCommand(commands.SinkCommand(name))
root.AddCommand(commands.SourceCommand(name))
root.AddCommand(commands.SubCommand(name))
root.AddCommand(commands.SubSetCommand(name))
root.AddCommand(&cobra.Command{
Use: "docs-merge",
@@ -124,13 +91,8 @@ func NewConfigCommand(name string) *cobra.Command {
})
root.AddCommand(&cobra.Command{
Use: "docs-fn",
Short: "[Alpha] Documentation for developing and invoking Configuration Functions.",
Long: api.FunctionsImplLong,
})
root.AddCommand(&cobra.Command{
Use: "docs-fn-spec",
Short: "[Alpha] Documentation for Configuration Functions Specification.",
Long: api.FunctionsSpecLong,
Short: "[Alpha] Documentation for writing containerized functions run by run.",
Long: api.ConfigFnLong,
})
root.AddCommand(&cobra.Command{
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`
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.
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,93 +0,0 @@
## create-setter
[Alpha] Create a custom setter for a Resource field
### Synopsis
Create a custom setter for a Resource field by inlining OpenAPI as comments.
DIR
A directory containing Resource configuration.
NAME
The name of the setter to create.
VALUE
The current value of the field, or a substring within the field.
### Creating a Custom Setter
**Given the YAML:**
# resource.yaml
apiVersion: v1
kind: Service
metadata:
...
spec:
...
ports:
...
- name: http
port: 8080
...
**Create a new setter:**
# create a setter for ports
$ kustomize config set create DIR/ http-port 8080 --type "integer" --field "port"
Resources fields with a field name matching `--field` and field value matching `VALUE` will
have a line comment added marking this field as settable.
**Newly modified YAML:**
# resource.yaml
apiVersion: v1
kind: Service
metadata:
...
spec:
...
ports:
...
- name: http
port: 8080 # {"type":"integer","x-kustomize":{"partialFieldSetters":[{"name":"http-port","value":"8080"}]}}
...
Setters may also be defined directly by editing the yaml and adding the comment.
Users may not set the field value using the `set` command:
# change the http-port value to 8081
$ kustomize config set DIR/ http-port 8081
### Using default values
The default values for a setter may be:
- valid field values (e.g. `8080` or `008080` for a port)
- invalid values that adhere to the schema (e.g. `0000` for a port)
- values that do not adhere to the schema (e.g. `[PORT]` for port)
A setter may be for a substring of a full field:
$ kustomize config set create DIR/ image-tag v1.0.01 --type "string" --field "image"
image: gcr.io/example/app:v1.0.1 # # {"type":"string","x-kustomize":{"partialFieldSetters":[{"name":"image-tag","value":"v1.0.1"}]}}
A single field value may have multiple setters applied to it for different parts of the field.
### Examples
# create a setter for port fields matching "8080"
kustomize config create-setter DIR/ port 8080 --type "integer" --field port \
--description "default port used by the app"
# create a setter for a substring of a field rather than the full field -- e.g. only the
# image tag, not the full image
kustomize config create-setter DIR/ image-tag v1.0.1 --type "string" \
--field image --description "current stable release"

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

@@ -1,23 +0,0 @@
## merge3
[Alpha] Merge diff of Resource configuration files into a destination (3-way)
### Synopsis
[Alpha] Merge diff of Resource configuration files into a destination (3-way)
Merge3 performs a 3-way merge by applying the diff between 2 sets of Resources to a 3rd set.
Merge3 may be for rebasing changes to a forked set of configuration -- e.g. compute the difference between the original
set of Resources that was forked and an updated set of those Resources, then apply that difference to the fork.
If a field value differs between the ORIGINAL_DIR and UPDATED_DIR, the value from the UPDATED_DIR is taken and applied
to the Resource in the DEST_DIR.
For information on merge rules, run:
kustomize config docs-merge3
### Examples
kustomize config merge3 --ancestor a/ --from b/ --to c/

View File

@@ -22,8 +22,8 @@ order they appear in the file).
#### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function]
field specifying an image for the container to run. This image tells run how to invoke the container.
Config functions are specified as Kubernetes types containing a metadata.configFn.container.image
field. This field tells run how to invoke the container.
Example config function:
@@ -31,17 +31,17 @@ order they appear in the file).
apiVersion: fn.example.com/v1beta1
kind: ExampleFunctionKind
metadata:
configFn:
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
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
spec:
configField: configValue
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
would then write the container stdout back to example/, replacing the directory
file contents.

View File

@@ -1,87 +0,0 @@
## set
[Alpha] Set values on Resources fields values.
### Synopsis
Set values on Resources fields. May set either the complete or partial field value.
`set` identifies setters using field metadata published as OpenAPI extensions.
`set` parses both the Kubernetes OpenAPI, as well OpenAPI published inline in
the configuration as comments.
`set` maybe be used to:
- edit configuration programmatically from the cli
- create reusable bundles of configuration with custom setters
DIR
A directory containing Resource configuration.
NAME
Optional. The name of the setter to perform or display.
VALUE
Optional. The value to set on the field.
To print the possible setters for the Resources in a directory, run `set` on
a directory -- e.g. `kustomize config set DIR/`.
#### Tips
- A description of the value may be specified with `--description`.
- The last setter for the field's value may be defined with `--set-by`.
- Create custom setters on Resources, Kustomization.yaml's, patches, etc
The description and setBy fields are left unmodified unless specified with flags.
To create a custom setter for a field see: `kustomize help config create-setter`
### Examples
Resource YAML: Name Prefix Setter
# DIR/resources.yaml
...
metadata:
name: PREFIX-app1 # {"type":"string","x-kustomize":{"partialFieldSetters":[{"name":"name-prefix","value":"PREFIX"}]}}
...
---
...
metadata:
name: PREFIX-app2 # {"type":"string","x-kustomize":{"partialFieldSetters":[{"name":"name-prefix","value":"PREFIX"}]}}
...
List setters: Show the possible setters
$ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY
name-prefix '' PREFIX string 2
Perform set: set a new value, owner and description
$ kustomize config set DIR/ name-prefix "test" --description "test environment" --set-by "dev"
set 2 values
List setters: Show the new values
$ config set DIR/
NAME DESCRIPTION VALUE TYPE COUNT SETBY
name-prefix 'test environment' test string 2 dev
New Resource YAML:
# DIR/resources.yaml
...
metadata:
name: test-app1 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}}
...
---
...
metadata:
name: test-app2 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}}
...

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

@@ -0,0 +1,98 @@
## set
[Alpha] Set values on Resources fields by substituting values.
### Synopsis
Set values on Resources fields by substituting predefined markers for new values.
`set` looks for markers specified on Resource fields and substitute a new user defined
value for the existing value.
`set` maybe be used to:
- edit configuration programmatically from the cli or scripts
- create reusable bundles of configuration
DIR
A directory containing Resource configuration.
NAME
Optional. The name of the substitution to perform or display.
VALUE
Optional. The new value to substitute into the field.
To print the possible substitutions for the Resources in a directory, run `set` on
a directory -- e.g. `kustomize config set DIR/`.
#### Tips
- A description of the value may be specified with `--description`.
- An owner for the field's value may be defined with `--owned-by`.
- Prevent overriding previous substitutions with `--override=false`.
- Revert previous substitutions with `--revert`.
- Create substitutions on Kustomization.yaml's, patches, etc
When overriding or reverting previous substitutions, the description and owner are left
unmodified unless specified with flags.
To create a substitution for a field see: `kustomize help config set create`
### Examples
Resource YAML: Name substitution
# dir/resources.yaml
...
metadata:
name: PREFIX-app1 # {"substitutions":[{"name":"prefix","marker":"PREFIX-"}]}
...
---
...
metadata:
name: PREFIX-app2 # {"substitutions":[{"name":"prefix","marker":"PREFIX-"}]}
...
Show substitutions: Show the possible substitutions
$ config set dir
NAME DESCRIPTION VALUE TYPE COUNT SUBSTITUTED OWNER
prefix '' PREFIX- string 2 false
Perform substitution: set a new value, owner and description
$ config set dir prefix "test-" --description "test environment" --owned-by "dev"
performed 2 substitutions
Show substitutions: Show the new values
$ config set dir
NAME DESCRIPTION VALUE TYPE COUNT SUBSTITUTED OWNER
prefix 'test environment' test- string 2 true dev
New Resource YAML:
# dir/resources.yaml
...
metadata:
name: test-app1 # {"substitutions":[{"name":"prefix","marker":"PREFIX-","value":"test-"}],"ownedBy":"dev","description":"test environment"}
...
---
...
metadata:
name: test-app2 # {"substitutions":[{"name":"prefix","marker":"PREFIX-","value":"test-"}],"ownedBy":"dev","description":"test environment"}
...
Revert substitution:
config set dir prefix --revert
performed 2 substitutions
config set dir
NAME DESCRIPTION VALUE TYPE COUNT SUBSTITUTED OWNER
prefix 'test environment' PREFIX- string 2 false dev

View File

@@ -0,0 +1,167 @@
## sub-set-marker
[Alpha] Create a new substitution for a Resource field
### Synopsis
Create a new substitution for a Resource field -- recognized by `kustomize config set`.
DIR
A directory containing Resource configuration.
NAME
The name of the substitution to create.
VALUE
The current value of the field, or a substring of the field.
#### Tips: Picking Good Marker
Substitutions may be defined by directly editing yaml **or** by running `kustomize config set create`
to create a new substitution.
Given the YAML:
# resource.yaml
apiVersion: v1
kind: Service
metadata:
...
spec:
...
ports:
...
- name: http
port: 8080
...
Create a new set marker:
# create a substitution for ports
$ kustomize config set create dir/ http-port 8080 --type "int" --field "port"
Modified YAML:
# resource.yaml
apiVersion: v1
kind: Service
metadata:
...
spec:
...
ports:
...
- name: http
port: 8080 # {"substitutions":[{"name":"port","marker":"[MARKER]"}],"type":"int"}
...
Change the value using the `set` command:
# change the http-port value to 8081
$ kustomize config set dir/ http-port 8081
Resources fields with a field name matching `--field` and field value matching `VALUE` will
have a line comment added marking this field as settable.
Substitution markers may be:
- valid field values (e.g. `8080` for a port)
- Note: `008080` would be preferred because it is more recognizable as a marker
- invalid values that adhere to the schema (e.g. `0000` for a port)
- values that do not adhere to the schema (e.g. `[PORT]` for port)
Markers **SHOULD be clearly identifiable as a marker and either**:
- **adhere to the field schema** -- e.g. use a valid value
port: 008080 # {"substitutions":[{"name":"port","marker":"008080"}],"type":"int"}
- **be pre-filled in with a value** -- e.g. set the value when setting the marker
port: 8080 # {"substitutions":[{"name":"port","marker":"[MARKER]","value":"8080""}],"type":"int"}
**Note:** The important thing is that in both cases the Resource configuration may be directly
applied to a cluster and validated by tools without the tool knowing about the substitution
marker.
The difference between the preceding examples is that:
- the former will be shown as `SUBSTITUTED=false` (`config sub dir/` exits non-0)
- the latter with show up as `SUBSTITUTED=true` (`config sub dir/` exits 0)
When choosing the which to use, consider that checks for unsubstituted values MAY be
configured as pre-commit checks -- if you want to these checks to fail if the value
hasn't been substituted, then don't specify a `value`.
Markers which are invalid field values MAY be chosen in cases where it is preferred to have
the create or update request fail rather than succeed if the substitution has not yet been
performed.
A substitution may be a substring of the full field:
$ kustomize config set create dir/ app-image-tag v1.0.01 --type "string" --field "image"
image: gcr.io/example/app:v1.0.1 # {"substitutions":[{"name":"app-image-tag","marker":"[MARKER]","value":"v1.0.1"}]}
A single field value may have multiple substitutions applied to it:
name: PREFIX-app-SUFFIX # {"substitutions":[{"name":"prefix","marker":"PREFIX-"},{"name":"suffix","marker":"-SUFFIX"}]}
#### Substitution Format
Substitutions are defined as json encoded FieldMeta comments on fields.
FieldMeta Schema read by `sub`:
{
"title": "FieldMeta",
"type": "object",
"properties": {
"substitutions": {
"type": "array",
"description": "Possible substitutions that may be performed against this field.",
"items": {
"type": "object",
"properties": {
"name": "Name of the substitution.",
"marker": "Marker for the value to be substituted.",
"value": "Current substituted value"
}
}
},
"type": {
"type": "string",
"description": "The value type. Defaults to string."
"enum": ["string", "int", "float", "bool"]
},
"description": {
"type": "string",
"description": "A description of the field's current value. Optional."
},
"ownedBy": {
"type": "string",
"description": "The current owner of the field. Optional."
},
}
}
### Examples
# set a substitution for port fields matching "8080"
kustomize config sub create dir/ port 8080 --type "int" --field port \
--description "default port used by the app"
# set a substitution for port fields matching "8080", using "0000" as a marker.
kustomize config sub dir/ port 8080 --marker "0000" --type "int" \
--field port --description "default port used by the app"
# substitute a substring of a field rather than the full field -- e.g. only the
# image tag, not the full image
kustomize config sub dir/ app-image-tag v1.0.1 --type "string" --substring \
--field port --description "current stable release"

View File

@@ -20,9 +20,8 @@ container names, etc.
kustomize config tree supports printing arbitrary fields using the '--field' flag.
By default, kustomize config tree uses Resource graph structure if any relationships between resources (ownerReferences)
are detected, as is typically the case when printing from a cluster. Otherwise, directory graph structure is used. The
graph structure can also be selected explicitly using the '--graph-structure' flag.
By default, kustomize config tree uses the directory structure for the tree structure, however when printing
from the cluster, the Resource graph structure may be used instead.
### Examples
@@ -43,7 +42,8 @@ graph structure can also be selected explicitly using the '--graph-structure' fl
--field="status.conditions[type=Completed].status"
# print live Resources from a cluster using owners for graph structure
kubectl get all -o yaml | kustomize config tree --replicas --name --image
kubectl get all -o yaml | kustomize config tree --replicas --name --image \
--graph-structure=owners
# print live Resources with status condition fields
kubectl get all -o yaml | kustomize config tree \

View File

@@ -39,7 +39,7 @@
#### 2. Modify the Generated Resources
- modify the generated Resources by adding an annotation, sidecar container, etc.
- modify the `local-resource/example-use.yaml` by changing the replicas
- modify the `local-resources/example-use.yaml` by changing the replicas
re-run `run`. this will apply the updated replicas to the generated Resources,
but keep the fields that you manually added to the generated Resource configuration.
@@ -86,7 +86,7 @@
#### 2. Modify the Generated Resources
- modify the generated Resources by adding an annotation, sidecar container, etc.
- modify the `local-resource/example-use.yaml` by changing the replicas
- modify the `local-resources/example-use.yaml` by changing the replicas
re-run `run`. this will apply the updated replicas to the generated Resources,
but keep the fields that you manually added to the generated Resource configuration.

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,6 +9,7 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
k8s.io/apimachinery v0.17.0
sigs.k8s.io/kustomize/kyaml v0.0.0
)

View File

@@ -1,11 +1,8 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -19,25 +16,16 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -55,6 +43,7 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -62,20 +51,21 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -91,7 +81,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -101,12 +93,12 @@ 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/go.mod h1:Rg3ijooqulo05aGLyGsHoLmIOUzHUVK19WVgrYBPU/E=
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/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
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/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -127,9 +119,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -139,12 +128,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

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
}
// 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
outputs = append(outputs, kio.ByteWriter{
Writer: out,
@@ -136,7 +129,6 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig,
Style: yaml.GetStyle(r.Styles...),
ClearAnnotations: clear,
})
return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute())

View File

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

View File

@@ -1,113 +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/ext"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/setters"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
)
// NewCreateSetterRunner returns a command runner.
func NewCreateSetterRunner(parent string) *CreateSetterRunner {
r := &CreateSetterRunner{}
set := &cobra.Command{
Use: "create-setter DIR NAME VALUE",
Args: cobra.ExactArgs(3),
Short: commands.CreateSetterShort,
Long: commands.CreateSetterLong,
Example: commands.CreateSetterExamples,
PreRunE: r.preRunE,
RunE: r.runE,
}
set.Flags().StringVar(&r.Set.SetPartialField.SetBy, "set-by", "",
"record who the field was default by.")
set.Flags().StringVar(&r.Set.SetPartialField.Description, "description", "",
"record a description for the current setter value.")
set.Flags().StringVar(&r.Set.SetPartialField.Field, "field", "",
"name of the field to set -- e.g. --field port. defaults to all fields match"+
"VALUE. maybe be the field name, field path, or partial field path (suffix)")
set.Flags().StringVar(&r.Set.ResourceMeta.Name, "name", "",
"name of the Resource on which to create the setter.")
set.Flags().MarkHidden("name")
set.Flags().StringVar(&r.Set.ResourceMeta.Kind, "kind", "",
"kind of the Resource on which to create the setter.")
set.Flags().MarkHidden("kind")
set.Flags().StringVar(&r.Set.SetPartialField.Type, "type", "",
"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)
r.Command = set
return r
}
func CreateSetterCommand(parent string) *cobra.Command {
return NewCreateSetterRunner(parent).Command
}
type CreateSetterRunner struct {
Command *cobra.Command
Set setters.CreateSetter
CreateSetter settersutil.SetterCreator
OpenAPIFile string
}
func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error {
return handleError(c, r.set(c, args))
}
func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error {
var err error
r.Set.SetPartialField.Setter.Name = args[1]
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
}
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]}
err := kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{&r.Set},
Outputs: []kio.Writer{rw}}.Execute()
if err != nil {
return err
}
return nil
}

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

@@ -1,180 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"fmt"
"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/kio"
"sigs.k8s.io/kustomize/kyaml/setters"
"sigs.k8s.io/kustomize/kyaml/setters2/settersutil"
)
// NewSetRunner returns a command runner.
func NewSetRunner(parent string) *SetRunner {
r := &SetRunner{}
c := &cobra.Command{
Use: "set DIR NAME [VALUE]",
Args: cobra.RangeArgs(1, 3),
Short: commands.SetShort,
Long: commands.SetLong,
Example: commands.SetExamples,
PreRunE: r.preRunE,
RunE: r.runE,
}
fixDocs(parent, 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
}
var setterVersion string
func SetCommand(parent string) *cobra.Command {
return NewSetRunner(parent).Command
}
type SetRunner struct {
Command *cobra.Command
Lookup setters.LookupSetters
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 {
if len(args) > 1 {
r.Perform.Name = args[1]
r.Lookup.Name = args[1]
}
if len(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
}
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 {
return handleError(c, r.perform(c, args))
}
return handleError(c, lookup(r.Lookup, c, args))
}
func lookup(l setters.LookupSetters, c *cobra.Command, args []string) error {
// lookup the setters
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}},
Filters: []kio.Filter{&l},
}.Execute()
if err != nil {
return err
}
table := tablewriter.NewWriter(c.OutOrStdout())
table.SetRowLine(false)
table.SetBorder(false)
table.SetHeaderLine(false)
table.SetColumnSeparator(" ")
table.SetCenterSeparator(" ")
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetHeader([]string{
"NAME", "DESCRIPTION", "VALUE", "TYPE", "COUNT", "SETBY",
})
for i := range l.SetterCounts {
s := l.SetterCounts[i]
v := s.Value
if s.Value == "" {
v = s.Value
}
table.Append([]string{
s.Name,
"'" + s.Description + "'",
v,
fmt.Sprintf("%v", s.Type),
fmt.Sprintf("%d", s.Count),
s.SetBy,
})
}
table.Render()
if len(l.SetterCounts) == 0 {
// exit non-0 if no matching setters are found
os.Exit(1)
}
return nil
}
// perform the setters
func (r *SetRunner) perform(c *cobra.Command, args []string) error {
rw := &kio.LocalPackageReadWriter{
PackagePath: args[0],
}
// perform the setters in the package
err := kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{&r.Perform},
Outputs: []kio.Writer{rw},
}.Execute()
if err != nil {
return err
}
fmt.Fprintf(c.OutOrStdout(), "set %d fields\n", r.Perform.Count)
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

@@ -0,0 +1,162 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
"os"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/cmd/config/internal/sub"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// NewSubRunner returns a command runner.
func NewSubRunner(parent string) *SubRunner {
r := &SubRunner{}
c := &cobra.Command{
Use: "set DIR [NAME] [VALUE]",
Args: cobra.RangeArgs(1, 3),
Short: commands.SubShort,
Long: commands.SubLong,
Example: commands.SubExamples,
Aliases: []string{"sub"},
PreRunE: r.preRunE,
RunE: r.runE,
}
c.Flags().BoolVar(&r.Perform.Override, "override", true,
"override previously substituted values.")
c.Flags().BoolVar(&r.Perform.Revert, "revert", false,
"override previously substituted values.")
fixDocs(parent, c)
r.Command = c
c.AddCommand(SubSetCommand(parent))
return r
}
func SubCommand(parent string) *cobra.Command {
return NewSubRunner(parent).Command
}
type SubRunner struct {
Command *cobra.Command
Lookup sub.LookupSubstitutions
Perform sub.PerformSubstitutions
}
func (r *SubRunner) preRunE(c *cobra.Command, args []string) error {
if len(args) > 1 {
r.Perform.Name = args[1]
r.Lookup.Name = args[1]
}
if len(args) > 2 {
r.Perform.NewValue = args[2]
}
if len(args) < 2 && r.Perform.Revert {
return errors.Errorf("must specify NAME with --revert")
}
var mutex int
if r.Perform.Revert {
mutex++
}
if r.Perform.Override {
mutex++
}
if mutex > 1 {
return errors.Errorf("--revert, --override are mutually exclusive")
}
return nil
}
func (r *SubRunner) runE(c *cobra.Command, args []string) error {
if len(args) == 3 {
return handleError(c, r.perform(c, args))
}
if len(args) == 2 && r.Perform.Revert {
return handleError(c, r.perform(c, args))
}
return handleError(c, r.lookup(c, args))
}
func (r *SubRunner) lookup(c *cobra.Command, args []string) error {
// lookup the substitutions
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: args[0]}},
Filters: []kio.Filter{&r.Lookup},
}.Execute()
if err != nil {
return err
}
remaining := false
table := tablewriter.NewWriter(c.OutOrStdout())
table.SetRowLine(false)
table.SetBorder(false)
table.SetHeaderLine(false)
table.SetColumnSeparator(" ")
table.SetCenterSeparator(" ")
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetHeader([]string{
"NAME", "DESCRIPTION", "VALUE", "TYPE", "COUNT", "SUBSTITUTED", "OWNER",
})
for i := range r.Lookup.SubstitutionCounts {
s := r.Lookup.SubstitutionCounts[i]
remaining = remaining || s.Count > s.CountComplete
v := s.CurrentValue
if s.CurrentValue == "" {
v = s.Marker
}
table.Append([]string{
s.Name,
"'" + s.Description + "'",
v,
fmt.Sprintf("%v", s.Type),
fmt.Sprintf("%d", s.Count),
fmt.Sprintf("%v", s.Count == s.CountComplete),
s.OwnedBy,
})
}
table.Render()
if remaining {
os.Exit(1)
}
return nil
}
// perform the substitutions
func (r *SubRunner) perform(c *cobra.Command, args []string) error {
rw := &kio.LocalPackageReadWriter{
PackagePath: args[0],
}
// perform the substitutions in the package
err := kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{&r.Perform},
Outputs: []kio.Writer{rw},
}.Execute()
if err != nil {
return err
}
fmt.Fprintf(c.OutOrStdout(), "performed %d substitutions\n", r.Perform.Count)
return nil
}

View File

@@ -0,0 +1,93 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/cmd/config/internal/sub"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// NewSubSetRunner returns a command runner.
func NewSubSetRunner(parent string) *SubSetRunner {
r := &SubSetRunner{}
set := &cobra.Command{
Use: "create PKG_DIR NAME [VALUE]",
Args: cobra.ExactArgs(3),
Short: commands.SubsetShort,
Long: commands.SubsetLong,
Example: commands.SubsetExamples,
PreRunE: r.preRunE,
RunE: r.runE,
}
set.Flags().StringVar(&r.Set.Marker.OwnedBy, "owned-by", "",
"set this owner on for the current value.")
set.Flags().StringVar(&r.Set.Marker.Description, "description", "",
"set this description for the current value description.")
set.Flags().StringVar(&r.Set.Marker.Substitution.Marker, "marker", "[MARKER]",
"use this marker.")
set.Flags().StringVar(&r.Set.Marker.Field, "field", "",
"name of the field to set -- e.g. --field port")
set.Flags().StringVar(&r.Set.ResourceMeta.Name, "name", "",
"name of the Resource on which to set the substitution.")
set.Flags().StringVar(&r.Set.ResourceMeta.Kind, "kind", "",
"kind of the Resource on which to set substitution.")
set.Flags().StringVar(&r.Set.Marker.Type, "type", "",
"field type -- e.g. int,float,bool,string.")
set.Flags().BoolVar(&r.Set.Marker.PartialMatch, "substring", false,
"if true, the value may be a substring of the current value.")
fixDocs(parent, set)
set.MarkFlagRequired("type")
set.MarkFlagRequired("field")
r.Command = set
return r
}
func SubSetCommand(parent string) *cobra.Command {
return NewSubSetRunner(parent).Command
}
type SubSetRunner struct {
Command *cobra.Command
Set sub.SetSubstitutionMarker
}
func (r *SubSetRunner) runE(c *cobra.Command, args []string) error {
return handleError(c, r.set(c, args))
}
func (r *SubSetRunner) preRunE(c *cobra.Command, args []string) error {
r.Set.Marker.Substitution.Name = args[1]
r.Set.Marker.Substitution.Value = args[2]
return nil
}
// perform the substitutions
func (r *SubSetRunner) set(c *cobra.Command, args []string) error {
rw := &kio.LocalPackageReadWriter{
PackagePath: args[0],
}
// add the substitution marker to the Resource
err := kio.Pipeline{
Inputs: []kio.Reader{rw},
Filters: []kio.Filter{&r.Set},
Outputs: []kio.Writer{rw},
}.Execute()
if err != nil {
return err
}
return nil
}

View File

@@ -78,8 +78,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
@@ -109,8 +109,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test
@@ -133,8 +133,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
@@ -161,8 +161,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test
@@ -185,8 +185,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_deployment.yaml
spec:
replicas: 11
selector:
@@ -216,8 +216,8 @@ items:
name: test
app: nginx
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
config.kubernetes.io/index: "0"
config.kubernetes.io/path: config/test_service.yaml
spec:
selector:
name: test

View File

@@ -14,7 +14,7 @@ import (
func GetFmtRunner(name string) *FmtRunner {
r := &FmtRunner{}
c := &cobra.Command{
Use: "fmt DIR...",
Use: "fmt",
Short: commands.FmtShort,
Long: commands.FmtLong,
Example: commands.FmtExamples,
@@ -31,8 +31,6 @@ formatting substitution verbs {'%n': 'metadata.name', '%s': 'metadata.namespace'
`if true, keep index and filename annotations set on Resources.`)
c.Flags().BoolVar(&r.Override, "override", false,
`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
return r
}
@@ -48,7 +46,6 @@ type FmtRunner struct {
SetFilenames bool
KeepAnnotations bool
Override bool
UseSchema bool
}
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 {
f := []kio.Filter{filters.FormatFilter{
UseSchema: r.UseSchema,
}}
f := []kio.Filter{filters.FormatFilter{}}
// format with file names
if r.SetFilenames {

View File

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

View File

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

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