Compare commits

..

62 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
49ebb3f155 Merge pull request #2236 from pwittrock/parse
Setters type support
2020-02-27 12:06:38 -08:00
Phillip Wittrock
fa507f782f Setters: support for explicit setter typing
- ensure OpenAPI definitions always uses strings for setter values
- allow the field type to be defined -- integer,boolean,string
- format values using yaml 1.1 compatibility
2020-02-27 11:51:18 -08:00
Phillip Wittrock
da548f65ea fixup go.sum 2020-02-27 11:48:24 -08:00
Kubernetes Prow Robot
8991b193c6 Merge pull request #2232 from pwittrock/setby
Setters: support for enums
2020-02-26 20:33:19 -08:00
Phillip Wittrock
3c776b3435 fix setter clear set-by test 2020-02-26 20:25:05 -08:00
Phillip Wittrock
cf61a360e0 Support for enum mappings in setters 2020-02-26 20:13:06 -08:00
Kubernetes Prow Robot
a588f498ea Merge pull request #2228 from pwittrock/setby
Setters: clear set-by if unspecified when setting a value
2020-02-26 14:23:19 -08:00
Phillip Wittrock
573d7b7234 Setters: clear set-by if unspecified when setting a value 2020-02-26 13:53:47 -08:00
Kubernetes Prow Robot
1d94ee86df Merge pull request #2219 from phanimarupaka/substitutions
Create setters for substitutions
2020-02-26 13:41:19 -08:00
Phani Teja Marupaka
275bf05ae2 Refactoring and Table tests 2020-02-26 11:37:14 -08:00
Phani Teja Marupaka
d70f3a7958 Update go files 2020-02-25 14:09:37 -08:00
Kubernetes Prow Robot
5707962df5 Merge pull request #2224 from monopole/disableTravis
Disable travis testing now that prow works.
2020-02-25 11:52:50 -08:00
Jeffrey Regan
63c06eeaf1 Disable travis testing now that prow works. 2020-02-25 11:38:36 -08:00
Kubernetes Prow Robot
b3ce3a7882 Merge pull request #2213 from vanou/correct_explanation_of_ApendAll_method_in_ResMap_interface
Correct explanation of ApendAll method in ResMap interface
2020-02-24 16:25:35 -08:00
Phani Teja Marupaka
a8b5ec2c61 Suggested Changes and Unit Tests 2020-02-24 12:35:14 -08:00
Kubernetes Prow Robot
d190e1c18a Merge pull request #2220 from kubernetes-sigs/monopole-patch-1
Add lint-kustomize to prow-presubmit-check
2020-02-24 10:42:50 -08:00
Jeff Regan
96ac25fff5 Add lint-kustomize to prow-presubmit-check
To do tool installs.
2020-02-24 10:17:52 -08:00
Phani Teja Marupaka
1d988a0fd8 Merge branch 'master' into substitutions 2020-02-24 09:39:22 -08:00
Phillip Wittrock
6f176b1507 Merge pull request #2203 from pwittrock/setter-wiring
Manually merging since the prow automation does not appear to be configured correctly
2020-02-24 09:23:00 -08:00
Phillip Wittrock
d2f0b1b345 Merge pull request #2218 from pwittrock/master
Update kyaml to v0.0.13
2020-02-21 14:35:25 -08:00
Phillip Wittrock
c95a40933b Update kyaml to v0.0.13 2020-02-21 10:18:52 -08:00
Phillip Wittrock
bc7b880ab1 Merge pull request #2211 from pwittrock/master
Make GetOpenAPIFile publicly settable
2020-02-21 10:16:21 -08:00
Phillip Wittrock
e004c31700 Make GetOpenAPIFile publicly settable 2020-02-21 08:09:48 -08:00
Phillip Wittrock
ca9aa62c26 Merge pull request #2216 from pwittrock/version
release cmd/config and kyaml 0.0.12
2020-02-21 08:09:08 -08:00
Phillip Wittrock
cee7cb6589 release cmd/config and kyaml 0.0.12 2020-02-19 15:01:14 -08:00
Phillip Wittrock
4f905c9cff Merge pull request #2203 from pwittrock/setter-wiring
Manually merging since the prow automation does not appear to be configured correctly
2020-02-19 14:55:09 -08:00
Phillip Wittrock
232c1c8ee9 write create-substitution into command 2020-02-19 14:25:38 -08:00
Phillip Wittrock
61cf3e6ec5 wire set 2.0 command 2020-02-19 14:25:38 -08:00
Phillip Wittrock
1ce469f1fd stop printing expected error message in fmt command test 2020-02-19 14:25:38 -08:00
Phillip Wittrock
a49c9de4a4 wire create-setter 2.0 into command 2020-02-19 14:25:38 -08:00
Phillip Wittrock
bada055cd3 wire list-setters 2.0 into command 2020-02-19 14:25:38 -08:00
Phillip Wittrock
d7e0b1ac31 setter utilities for simplifying commands 2020-02-19 14:25:38 -08:00
Phillip Wittrock
5549035b69 support for listing setters 2020-02-19 14:25:38 -08:00
Phillip Wittrock
025200cc12 support for adding setter substitution
- refactor add setter to include file updates
- support add substitution file updates
2020-02-19 14:25:38 -08:00
Phillip Wittrock
154939803f better support for reading / writing single resource yaml files 2020-02-19 08:16:07 -08:00
Phillip Wittrock
64c30a0678 fix nil dereference issue in fieldmeta 2020-02-19 08:13:26 -08:00
Phillip Wittrock
b7bef5dc44 openapi support for loading definitions from a file 2020-02-19 08:13:26 -08:00
vanou
87b680e1c0 Correct explanation of ApendAll method in ResMap interface
This commit corrects expalation of ApendAll method in ResMap
interface. AppendAll method should fail on CurId collision,
not OrgId collision.
2020-02-19 23:53:10 +09:00
Jeff Regan
0075d0a88c Add a prow target to the Makefile 2020-02-12 15:19:35 -08:00
Kubernetes Prow Robot
3fc359043a Merge pull request #2199 from phanimarupaka/Setters2ReadAndWrite
Setter Definitions read and write
2020-02-12 10:14:51 -08:00
Phani Teja Marupaka
6b6a74af19 Substitutions 2020-02-12 09:20:43 -08:00
Kubernetes Prow Robot
89fc3cbb94 Merge pull request #2200 from pwittrock/add
setters 2.0: add references
2020-02-11 16:26:08 -08:00
Phillip Wittrock
437be2831f setters 2.0: support for adding references to setters 2020-02-11 16:04:32 -08:00
Kubernetes Prow Robot
b05ab6e0e3 Merge pull request #2197 from pwittrock/setters
setters 2.0
2020-02-11 16:04:09 -08:00
Phillip Wittrock
7097013426 setters 2.0 2020-02-11 11:00:58 -08:00
Phani Teja Marupaka
29fbc564e3 Setter Definitions read and write 2020-02-11 10:45:39 -08:00
Jeff Regan
42abcbd516 Merge pull request #2195 from haiyanmeng/stats
Add notes on backup and restore
2020-02-11 09:02:32 -08:00
Haiyan Meng
b7b7a5a79f Fix typo 2020-02-10 15:44:51 -08:00
Kubernetes Prow Robot
ebcc49d064 Merge pull request #2192 from eddiezane/ez/1901-expose-network-as-function-option
Support exposing network as a function option
2020-02-10 09:31:54 -08:00
Haiyan Meng
807ca9c1e3 Add notes on backup and restore 2020-02-10 08:30:08 -08:00
Eddie Zaneski
6cdcb1f436 Support network in functions
Signed-off-by: Eddie Zaneski <eddiezane@gmail.com>
2020-02-07 17:31:33 -07:00
Jeff Regan
91da8525c1 Merge pull request #2183 from haiyanmeng/stats
Add curl commands for kustomize stats
2020-02-06 11:40:00 -08:00
Jeff Regan
b604f03740 Merge pull request #2189 from pwittrock/master
release cmd/config and kyaml
2020-02-06 11:39:08 -08:00
Jeff Regan
422ba21df0 Merge pull request #2186 from vanou/fix-typo-in-fields-explanation
Fix typo in kustomization file resources field explanation
2020-02-06 11:38:48 -08:00
Phillip Wittrock
20e13abbb4 release cmd/config and kyaml 2020-02-06 09:02:30 -08:00
vanou
5975761fbf Fix typo in kustomization file fields explanation
This commit fixes typo in explanation of 'resources' field
in kustomization file.
2020-02-06 22:43:45 +09:00
Haiyan Meng
baccf58ccf Avoid tracking the change in github_api_secret.txt
This helps prevent commiting your Github personal access token into
Github by accident.
2020-02-05 12:06:21 -08:00
Haiyan Meng
c7bdb3fbe4 Add cmds to process the kustomize-stats log 2020-02-05 11:04:59 -08:00
Haiyan Meng
967fe44e3f Add curl commands for kustomize stats 2020-02-05 11:04:59 -08:00
Haiyan Meng
d0602c732b Remove the usage of github access token from the kustomize-stats job 2020-02-05 11:04:59 -08:00
Haiyan Meng
a4179fa87f Use the silence mode of curl 2020-02-05 11:04:59 -08:00
Haiyan Meng
c9bce3fc0a Add comments on backup and restore 2020-02-05 11:04:59 -08:00
66 changed files with 6236 additions and 235 deletions

View File

@@ -1,43 +0,0 @@
os:
- linux
- osx
# TODO: Speed up the slowness of the osx travis runs
# Maybe cache brew installs?
#
# TODO: Uncomment when some gets the tests running on Windows.
# - windows
addons:
apt:
packages:
- tree
homebrew:
packages:
- tree
update: true
# Only clone the most recent commit.
git:
depth: 1
language: go
go:
- "1.13"
go_import_path: sigs.k8s.io/kustomize
before_install:
- source ./travis/consider-early-travis-exit.sh
# Skip the install process; let pre-commit.sh do it.
install: true
script:
- make verify-kustomize
- ./travis/kyaml-pre-commit.sh
- ./travis/check-go-mod.sh
# TBD. Suppressing for now.
notifications:
email: false

View File

@@ -17,6 +17,13 @@ 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

View File

@@ -14,11 +14,6 @@ import (
"sigs.k8s.io/kustomize/api/internal/crawl/index"
)
const (
githubAccessTokenVar = "GITHUB_ACCESS_TOKEN"
retryCount = 3
)
// iterateArr adds each item in arr into countMap.
func iterateArr(arr []string, countMap map[string]int) {
for _, item := range arr {

View File

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

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

View File

@@ -11,13 +11,8 @@ spec:
image: gcr.io/haiyanmeng-gke-dev/kustomize_stats:v1
imagePullPolicy: Always
command: ["/kustomize_stats"]
args: ["--index=kustomize", "--kinds=50", "--identifiers=50", "--kustomize-features=50"]
args: ["--index=kustomize", "--kinds=51", "--identifiers=50", "--kustomize-features=50"]
env:
- name: GITHUB_ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: github-access-token
key: token
- name: ELASTICSEARCH_URL
valueFrom:
configMapKeyRef:

View File

@@ -1,6 +1,9 @@
# 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 and all the snapshots in the repository.
# Deleting `esbackup/kustomize-backbup` will NOT delete essnapshot and esrestore objects.
# 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:
@@ -8,7 +11,11 @@ metadata:
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

View File

@@ -1,3 +1,4 @@
# ESCluster is depended by ESBackup and ESRestore.
apiVersion: elasticsearch.cloud.google.com/v1alpha1
kind: ESCluster
metadata:

View File

@@ -1,6 +1,9 @@
# 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:

View File

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

@@ -1,15 +0,0 @@
# Creating `essnapshot/kustomize-snapshot` will create a snapshot named `kustomize-snapshot` in the `kustomize-backup` snapshot repository.
# Deleting `essnapshot/kustomize-snapshot` will delete the snapshot.
# Deleting `essnapshot/kustomize-snapshot` should happen before deleting `esbackup/kustomize-backup`.
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

@@ -1,6 +1,6 @@
Find out the largest value of the `creationTime` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"max_creationTime" : { "max" : { "field" : "creationTime" } }
@@ -11,7 +11,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Find out the smallest value of the `creationTime` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"min_creationTime" : { "min" : { "field" : "creationTime" } }
@@ -22,7 +22,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Find out the smallest value of the `creationTime` field of all the kustomization files:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -40,7 +40,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Find out the smallest value of the `creationTime` field of all kustomize resource files:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -61,7 +61,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Find out the smallest value of the `creationTime` field of all kustomize generator files:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -82,7 +82,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Find out the smallest value of the `creationTime` field of all kustomize transformer files:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -103,7 +103,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Query all the documents whose `creationTime` <= `2016-07-29T17:38:26.000Z`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"range": {
@@ -118,7 +118,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Query all the documents whose `creationTime` falls within the specific range:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"range": {
@@ -134,7 +134,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Query all the kustomization files whose `creationTime` falls within the specific range:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 20,
"query": {
@@ -158,7 +158,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Aggregate how many new kustomization files were added into Github each month:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -181,7 +181,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize resource files were added into Github each month:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -207,7 +207,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize generator files were added into Github each month:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -233,7 +233,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize transformer files were added into Github each month:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -259,7 +259,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomization files were added into Github each year:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -282,7 +282,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize resource files were added into Github each year:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -308,7 +308,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize generator files were added into Github each year:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -334,7 +334,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Aggregate how many new kustomize transformer files were added into Github each year:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -360,7 +360,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Find the generator files created within the given time range:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -386,7 +386,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Find the transformer files created within the given time range:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {

View File

@@ -1,6 +1,6 @@
Count distinct values of the `defaultBranch` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"defaultBranch_count" : {
@@ -17,7 +17,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
List all the github branches where kustomization files and kustomize resource files live,
and how many kustomization files and kustomize resource files live in each branch:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"defaultBranch" : {

View File

@@ -1,7 +1,7 @@
Count the documents whose `document` field is empty (The reason why the `document` field
of a document is empty is because of empty documents):
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -19,7 +19,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Find all the documents having the `creationTime` field set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
@@ -32,7 +32,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Find all the documents whose `creationTime` field is not set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {

View File

@@ -1,6 +1,6 @@
Find all the documents having the `fileType` field set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
@@ -13,7 +13,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Find all the documents whose `fileType` field is not set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -31,7 +31,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the documents whose `fileType` field is `resource`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -46,7 +46,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files whose `fileType` field is `resource`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -62,7 +62,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomize resource files:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -80,7 +80,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search all the kustomization files including a `generators` field:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -103,7 +103,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the documents whose `fileType` field is `generator`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -118,7 +118,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files whose `fileType` field is `generator`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -134,7 +134,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomize generator files:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -152,7 +152,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search all the kustomization files including a `transformers` field:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -175,7 +175,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the documents whose `fileType` field is `transformer`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -190,7 +190,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files whose `fileType` field is `transformer`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -206,7 +206,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomize transformer files:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -224,7 +224,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Count distinct values of the `fileType` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"fileType_count" : {
@@ -240,7 +240,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
List all the values of the `fileType` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"fileType" : {
@@ -257,7 +257,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
For all the kustomization files in the index, list all the values of the
`fileType` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -280,7 +280,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
For all the non-kustomization files in the index, list all the values of the
`fileType` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {

View File

@@ -1,7 +1,7 @@
Find all the generator files whose `kinds` field includes `ChartRenderer`, and
only output certain fields of each document:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 200,
"_source": {

View File

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

View File

@@ -1,7 +1,7 @@
Count the documents in the index whose `repositoryUrl` field starts with
`https://github.com/`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -17,7 +17,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Count the documents in the index whose `repositoryUrl` field does not start with
`https://github.com/`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -33,7 +33,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search all the documents matching the given `repositoryUrl` and `filePath`, and return
a version for each search hit:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"version": true,
@@ -52,7 +52,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search all the documents whose filePath ends with one of these following three filenames:
`kustomization.yaml`, `kustomization.yml`, `kustomization`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -68,7 +68,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search all the documents whose filePath does not end with any of these following
three filenames: `kustomization.yaml`, `kustomization.yml`, `kustomization`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {

View File

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

View File

@@ -1,6 +1,6 @@
Count distinct values of the `repositoryUrl` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"repositoryUrl_count" : {
@@ -16,7 +16,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count how many Github repositories include kustomization files:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -39,7 +39,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize resource files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -65,7 +65,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize generator files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -91,7 +91,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize transformer files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -117,7 +117,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize resource dirs in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -141,7 +141,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize generator dirs in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -165,7 +165,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
Count distinct values of the `repositoryUrl` field for all the kustomize transformer dirs in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -191,7 +191,7 @@ List all the github repositories including kustomization files and kustomize res
and how many kustomization files and kustomize resource files each github repository includes
(the github repository including the most kustomization files is listed first):
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"repositoryUrl" : {
@@ -207,7 +207,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
List the top 20 Github repositories including the most amount of kustomization files:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -230,7 +230,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
List the top 20 Github repositories including the most amount of kustomize resource files:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {

View File

@@ -1,29 +1,29 @@
Retrieve information about all registered snapshot repositories:
```
curl -X GET "${ElasticSearchURL}:9200/_snapshot?pretty"
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot?pretty"
```
Retrieve information about a given snapshot repository, `kustomize-backup`:
```
curl -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup?pretty"
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup?pretty"
```
Verify a snapshot repository, `kustomize-backup`, manually:
```
curl -X POST "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/_verify?pretty"
curl -s -X POST "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/_verify?pretty"
```
List all the snapshots in a given snapshot repository:
```
curl -X GET "${ElasticSearchURL}:9200/_cat/snapshots/kustomize-backup?v&s=id&pretty"
curl -s -X GET "${ElasticSearchURL}:9200/_cat/snapshots/kustomize-backup?v&s=id&pretty"
```
Retrieve a summary information about a given snapshot:
```
curl -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/kustomize-snapshot?pretty"
curl -s -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/kustomize-snapshot?pretty"
```
Retrieve a detailed information about a given snapshot:
```
curl -X GET "${ElasticSearchURL}:9200/_snapshot/kustomize-backup/kustomize-snapshot/_status?pretty"
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,6 +1,6 @@
Search for all the kustomize resource files including a Deployment object:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {
@@ -16,7 +16,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomize resource files including a Deployment object, but only
including the `kinds` field in the result:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"_source": {
"includes": ["kinds"]
@@ -35,7 +35,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomize resource files including both a Deployment object and
a Service object:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {
@@ -52,7 +52,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Count the number of documents including Deployment and the number of documents
including Service:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs" : {
@@ -71,7 +71,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files involving CRDs:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -87,7 +87,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files defining configMapGenerator:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -103,7 +103,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the documents having a `kind` field:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -118,7 +118,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kuostmization files having a `kind` field:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -134,7 +134,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the kustomization files defining the `generatorOptions:disableNameSuffixHash` feature:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match" : {

View File

@@ -1,7 +1,7 @@
Find all the trasnformer files whose `kinds` field includes `HelmValues`, and
only output certain fields of each document:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 200,
"_source": {

View File

@@ -1,6 +1,6 @@
Find all the documents having the `user` field set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"exists": {
@@ -13,7 +13,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Find all the documents whose `user` field is not set:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 10000,
"query": {
@@ -31,7 +31,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Search for all the documents whose `user` field is `kubernetes-sigs`:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -46,7 +46,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?pretty" -H 'Content-T
Count distinct values of the `user` field:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"user_count" : {
@@ -62,7 +62,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
List all the values of the `user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"aggs" : {
"user" : {
@@ -78,7 +78,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomization files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -102,7 +102,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomization files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -125,7 +125,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomize resource files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -152,7 +152,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomize resource files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -178,7 +178,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomize generator files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -205,7 +205,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomize generator files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -231,7 +231,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomize transformer files in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -258,7 +258,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomize transformer files in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -284,7 +284,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomize generator dirs in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -309,7 +309,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomize generator dirs in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -333,7 +333,7 @@ curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Co
Count distinct values of the `user` field for all the kustomize transformer dirs in the index:
```
curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
@@ -358,7 +358,7 @@ curl -X POST "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'C
For all the kustomize transformer dirs in the index, list all the values of the
`user` field and the frequency of each value:
```
curl -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
curl -s -X GET "${ElasticSearchURL}:9200/${INDEXNAME}/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {

View File

@@ -114,7 +114,7 @@ type ResMap interface {
Append(*resource.Resource) error
// AppendAll appends another ResMap to self,
// failing on any OrgId collision.
// failing on any CurId collision.
AppendAll(ResMap) error
// AbsorbAll appends, replaces or merges the contents

View File

@@ -45,22 +45,23 @@ Advanced Documentation Topics:
// Export commands publicly for composition
var (
Annotate = commands.AnnotateCommand
Cat = commands.CatCommand
Count = commands.CountCommand
CreateSetter = commands.CreateSetterCommand
Fmt = commands.FmtCommand
Grep = commands.GrepCommand
ListSetters = commands.ListSettersCommand
Merge = commands.MergeCommand
Merge3 = commands.Merge3Command
RunFn = commands.RunFnCommand
Set = commands.SetCommand
Sink = commands.SinkCommand
Source = commands.SourceCommand
Tree = commands.TreeCommand
Wrap = commands.WrapCommand
XArgs = commands.XArgsCommand
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
@@ -107,6 +108,7 @@ func NewConfigCommand(name string) *cobra.Command {
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))

12
cmd/config/ext/ext.go Normal file
View File

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

@@ -156,8 +156,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

View File

@@ -5,9 +5,11 @@ 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.
@@ -23,21 +25,27 @@ func NewCreateSetterRunner(parent string) *CreateSetterRunner {
RunE: r.runE,
}
set.Flags().StringVar(&r.Set.SetPartialField.SetBy, "set-by", "",
"set the setBy annotation.")
"record who the field was default by.")
set.Flags().StringVar(&r.Set.SetPartialField.Description, "description", "",
"set the description of the field value.")
"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")
"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.")
"OpenAPI field type for the setter -- e.g. integer,boolean,string.")
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)
set.MarkFlagRequired("type")
r.Command = set
return r
}
@@ -47,8 +55,10 @@ func CreateSetterCommand(parent string) *cobra.Command {
}
type CreateSetterRunner struct {
Command *cobra.Command
Set setters.CreateSetter
Command *cobra.Command
Set setters.CreateSetter
CreateSetter settersutil.SetterCreator
OpenAPIFile string
}
func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error {
@@ -56,12 +66,41 @@ func (r *CreateSetterRunner) runE(c *cobra.Command, args []string) error {
}
func (r *CreateSetterRunner) preRunE(c *cobra.Command, args []string) error {
var err error
r.Set.SetPartialField.Setter.Name = args[1]
r.Set.SetPartialField.Setter.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
r.CreateSetter.Type = r.Set.SetPartialField.Type
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},

View File

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

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

@@ -0,0 +1,240 @@
// 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
`,
},
{
name: "substitution and create setters 1",
args: []string{
"image", "something/nginx::1.7.9/nginxotherthing", "--pattern", "something/IMAGE::TAG/nginxotherthing",
"--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: something/nginx::1.7.9/nginxotherthing
- name: sidecar
image: sidecar:1.7.9
`,
inputOpenAPI: `
apiVersion: v1alpha1
kind: Example
`,
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: something/IMAGE::TAG/nginxotherthing
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: something/nginx::1.7.9/nginxotherthing # {"$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

@@ -4,9 +4,16 @@
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.
@@ -33,15 +40,57 @@ func ListSettersCommand(parent string) *cobra.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

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

@@ -9,16 +9,18 @@ import (
"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]",
Use: "set DIR NAME [VALUE]",
Args: cobra.RangeArgs(1, 3),
Short: commands.SetShort,
Long: commands.SetLong,
@@ -32,18 +34,44 @@ func NewSetRunner(parent string) *SetRunner {
"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
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 {
@@ -55,15 +83,37 @@ func (r *SetRunner) preRunE(c *cobra.Command, args []string) error {
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))
}

View File

@@ -0,0 +1,277 @@
// 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"
`,
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

@@ -143,6 +143,8 @@ 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

@@ -40,6 +40,10 @@ func GetRunFnRunner(name string) *RunFnRunner {
r.Command.Flags().StringVar(
&r.Image, "image", "",
"run this image as a function instead of discovering them.")
r.Command.Flags().BoolVar(
&r.Network, "network", false, "enable network access for functions that declare it")
r.Command.Flags().StringVar(
&r.NetworkName, "network-name", "bridge", "the docker network to run the container in")
return r
}
@@ -56,6 +60,8 @@ type RunFnRunner struct {
FnPaths []string
Image string
RunFns runfn.RunFns
Network bool
NetworkName string
}
func (r *RunFnRunner) runE(c *cobra.Command, args []string) error {
@@ -188,6 +194,8 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
Output: output,
Input: input,
Path: path,
Network: r.Network,
NetworkName: r.NetworkName,
}
// don't consider args for the function

View File

@@ -25,6 +25,8 @@ func TestRunFnCommand_preRunE(t *testing.T) {
input io.Reader
output io.Writer
functionPaths []string
network bool
networkName string
}{
{
name: "config map",
@@ -86,6 +88,40 @@ metadata:
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "network enabled",
args: []string{"run", "dir", "--image", "foo:bar", "--network"},
path: "dir",
network: true,
networkName: "bridge",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
name: "with network name",
args: []string{"run", "dir", "--image", "foo:bar", "--network", "--network-name", "foo"},
path: "dir",
network: true,
networkName: "foo",
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {}
kind: ConfigMap
apiVersion: v1
`,
},
{
@@ -206,6 +242,20 @@ apiVersion: v1
t.FailNow()
}
// check if Network was set
if tt.network {
if !assert.Equal(t, tt.network, r.RunFns.Network) {
t.FailNow()
}
if !assert.Equal(t, tt.networkName, r.RunFns.NetworkName) {
t.FailNow()
}
} else {
if !assert.Equal(t, false, r.RunFns.Network) {
t.FailNow()
}
}
// check if FunctionPaths were set
if tt.functionPaths == nil {
// make Equal work against flag default

View File

@@ -206,7 +206,7 @@ _file_, or a path (or URL) refering to another
kustomization _directory_, e.g.
```
resource:
resources:
- myNamespace.yaml
- sub-dir/some-deployment.yaml
- ../../commonbase

View File

@@ -55,19 +55,36 @@ func (fm *FieldMeta) Read(n *yaml.RNode) error {
}
b, err := json.Marshal(fe)
if err != nil {
return err
return errors.Wrap(err)
}
return json.Unmarshal(b, &fm.Extensions)
}
return nil
}
func isExtensionEmpty(x XKustomize) bool {
if x.FieldSetter != nil {
return false
}
if x.SetBy != "" {
return false
}
if len(x.PartialFieldSetters) > 0 {
return false
}
return true
}
// Write writes the FieldMeta to a node
func (fm *FieldMeta) Write(n *yaml.RNode) error {
fm.Schema.VendorExtensible.AddExtension("x-kustomize", fm.Extensions)
if !isExtensionEmpty(fm.Extensions) {
fm.Schema.VendorExtensible.AddExtension("x-kustomize", fm.Extensions)
} else {
delete(fm.Schema.VendorExtensible.Extensions, "x-kustomize")
}
b, err := json.Marshal(fm.Schema)
if err != nil {
return err
return errors.Wrap(err)
}
n.YNode().LineComment = string(b)
return nil

View File

@@ -9,6 +9,7 @@ require (
github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca
gopkg.in/yaml.v2 v2.2.4
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
gopkg.in/yaml.v2 v2.2.7
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
)

View File

@@ -39,18 +39,20 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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=
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=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -408,6 +408,17 @@ const (
var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey}
// GetFunction parses the config function from the object if it is found
func GetFunction(n *yaml.RNode, meta yaml.ResourceMeta) (*yaml.RNode, error) {
for _, s := range functionAnnotationKeys {
fn := meta.Annotations[s]
if fn != "" {
return yaml.Parse(fn)
}
}
return n.Pipe(yaml.Lookup("metadata", "configFn"))
}
// GetContainerName returns the container image for an API if one exists
func GetContainerName(n *yaml.RNode) (string, string) {
meta, _ := n.GetMeta()
@@ -415,14 +426,10 @@ func GetContainerName(n *yaml.RNode) (string, string) {
// path to the function, this will be mounted into the container
path := meta.Annotations[kioutil.PathAnnotation]
// check previous keys for backwards compatibility
for _, s := range functionAnnotationKeys {
functionAnnotation := meta.Annotations[s]
if functionAnnotation != "" {
annotationContent, _ := yaml.Parse(functionAnnotation)
image, _ := annotationContent.Pipe(yaml.Lookup("container", "image"))
return image.YNode().Value, path
}
fn, _ := GetFunction(n, meta)
if fn != nil {
image, _ := fn.Pipe(yaml.Lookup("container", "image"))
return yaml.GetValue(image), path
}
container := meta.Annotations["config.kubernetes.io/container"]
@@ -434,5 +441,19 @@ func GetContainerName(n *yaml.RNode) (string, string) {
if err != nil || yaml.IsMissingOrNull(image) {
return "", path
}
return image.YNode().Value, path
return yaml.GetValue(image), path
}
// GetContainerNetworkRequired returns whether or not networking is required for the container
func GetContainerNetworkRequired(n *yaml.RNode) (bool, error) {
meta, err := n.GetMeta()
if err != nil {
return false, err
}
f, err := GetFunction(n, meta)
if err != nil {
return false, err
}
networkRequired, _ := f.Pipe(yaml.Lookup("container", "network", "required"))
return yaml.GetValue(networkRequired) == "true", nil
}

View File

@@ -309,6 +309,79 @@ metadata:
`, b.String())
}
func Test_GetFunction(t *testing.T) {
var tests = []struct {
name string
resource string
expectedFn string
missingFn bool
}{
// fn annotation
{
name: "fn annotation",
resource: `
apiVersion: v1beta1
kind: Example
metadata:
annotations:
config.kubernetes.io/function: |-
container: foo:v1.0.0
`,
expectedFn: `container: foo:v1.0.0`,
},
// legacy fn style
{name: "legacy fn meta",
resource: `
apiVersion: v1beta1
kind: Example
metadata:
configFn:
container: foo:v1.0.0
`,
expectedFn: `container: foo:v1.0.0`,
},
// no fn
{name: "no fn",
resource: `
apiVersion: v1beta1
kind: Example
metadata:
annotations: {}
`,
missingFn: true,
},
// test network, etc...
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
resource := yaml.MustParse(tt.resource)
meta, err := resource.GetMeta()
if !assert.NoError(t, err) {
t.FailNow()
}
fn, err := GetFunction(resource, meta)
if !assert.NoError(t, err) {
t.FailNow()
}
if tt.missingFn {
if !assert.Nil(t, fn) {
t.FailNow()
}
} else {
if !assert.Equal(t, strings.TrimSpace(fn.MustString()), strings.TrimSpace(tt.expectedFn)) {
t.FailNow()
}
}
})
}
}
func Test_GetContainerName(t *testing.T) {
// make sure gcr.io works
n, err := yaml.Parse(`apiVersion: v1beta1
@@ -364,6 +437,76 @@ metadata:
assert.Equal(t, "", c)
}
func Test_GetContainerNetworkRequired(t *testing.T) {
tests := []struct {
input string
required bool
}{
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo
configFn:
container:
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
network:
required: true
`,
required: true,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo
configFn:
container:
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
network:
required: false
`,
required: false,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo
configFn:
container:
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
`,
required: false,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
network:
required: true
`,
required: true,
},
}
for _, tc := range tests {
cfg, err := yaml.Parse(tc.input)
if !assert.NoError(t, err) {
return
}
required, err := GetContainerNetworkRequired(cfg)
assert.NoError(t, err)
assert.Equal(t, tc.required, required)
}
}
func TestFilter_Filter_defaultNaming(t *testing.T) {
cfg, err := yaml.Parse(`apiVersion: apps/v1
kind: Deployment

View File

@@ -6,6 +6,7 @@ package openapi
import (
"encoding/json"
"fmt"
"io/ioutil"
"sync"
"github.com/go-openapi/spec"
@@ -44,11 +45,76 @@ func SchemaForResourceType(t yaml.TypeMeta) *ResourceSchema {
return &ResourceSchema{Schema: rs}
}
// SupplementaryOpenAPIFieldName is the conventional field name (JSON/YAML) containing
// supplementary OpenAPI definitions.
const SupplementaryOpenAPIFieldName = "openAPI"
// AddSchemaFromFile reads the file at path and parses the OpenAPI definitions
// from the field "openAPI"
func AddSchemaFromFile(path string) error {
return AddSchemaFromFileUsingField(path, SupplementaryOpenAPIFieldName)
}
// AddSchemaFromFileUsingField reads the file at path and parses the OpenAPI definitions
// from the specified field. If field is the empty string, use the whole document as
// OpenAPI.
func AddSchemaFromFileUsingField(path, field string) error {
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
// parse the yaml file (json is a subset of yaml, so will also parse)
y, err := yaml.Parse(string(b))
if err != nil {
return err
}
if field != "" {
// get the field containing the openAPI
m := y.Field(field)
if yaml.IsFieldEmpty(m) {
// doesn't contain openAPI definitions
return nil
}
y = m.Value
}
oAPI, err := y.String()
if err != nil {
return err
}
// convert the yaml openAPI to a JSON string by unmarshalling it to an
// interface{} and the marshalling it to a string
var o interface{}
err = yaml.Unmarshal([]byte(oAPI), &o)
if err != nil {
return err
}
j, err := json.Marshal(o)
if err != nil {
return err
}
// add the json schema to the global schema
_, err = AddSchema(j)
if err != nil {
return err
}
return nil
}
// AddSchema parses s, and adds definitions from s to the global schema.
func AddSchema(s []byte) (*spec.Schema, error) {
return parse(s)
}
// ResetOpenAPI resets the openapi data to empty
func ResetOpenAPI() {
globalSchema = openapiData{}
}
// AddDefinitions adds the definitions to the global schema.
func AddDefinitions(definitions spec.Definitions) {
// initialize values if they have not yet been set
@@ -123,7 +189,7 @@ func GetSchema(s string) (*ResourceSchema, error) {
// schema as part of the global schema.
// Must be called before the schema is used.
func SuppressBuiltInSchemaUse() {
globalSchema.noUseBuiltInSchema = false
globalSchema.noUseBuiltInSchema = true
}
// Elements returns the Schema for the elements of an array.

View File

@@ -5,6 +5,7 @@ package openapi
import (
"fmt"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
@@ -40,7 +41,7 @@ func TestNoUseBuiltInSchema_AddSchema(t *testing.T) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.config.setters.replicas"}`)
if !assert.Greater(t, len(globalSchema.schema.Definitions), 1) {
if !assert.Equal(t, len(globalSchema.schema.Definitions), 1) {
t.FailNow()
}
if !assert.NoError(t, err) {
@@ -122,3 +123,117 @@ func TestSchemaForResourceType(t *testing.T) {
t.FailNow()
}
}
func TestAddSchemaFromFile(t *testing.T) {
ResetOpenAPI()
inputyaml := `
openAPI:
definitions:
io.k8s.cli.setters.image-name:
x-k8s-cli:
setter:
name: image-name
value: "nginx"
`
f, err := ioutil.TempFile("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
t.FailNow()
}
err = AddSchemaFromFile(f.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.setters.image-name"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Greater(t, len(globalSchema.schema.Definitions), 200) {
t.FailNow()
}
assert.Equal(t, `map[x-k8s-cli:map[setter:map[name:image-name value:nginx]]]`,
fmt.Sprintf("%v", s.Schema.Extensions))
}
func TestPopulateDefsInOpenAPI_Substitution(t *testing.T) {
ResetOpenAPI()
inputyaml := `
openAPI:
definitions:
io.k8s.cli.setters.image-name:
x-k8s-cli:
setter:
name: image-name
value: "nginx"
io.k8s.cli.setters.image-tag:
x-k8s-cli:
setter:
name: image-tag
value: "1.8.1"
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE_NAME:IMAGE_TAG
values:
- marker: "IMAGE_NAME"
ref: "#/definitions/io.k8s.cli.setters.image-name"
- marker: "IMAGE_TAG"
ref: "#/definitions/io.k8s.cli.setters.image-tag"
`
f, err := ioutil.TempFile("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
t.FailNow()
}
if !assert.NoError(t, AddSchemaFromFile(f.Name())) {
t.FailNow()
}
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.substitutions.image"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Greater(t, len(globalSchema.schema.Definitions), 200) {
t.FailNow()
}
assert.Equal(t,
`map[x-k8s-cli:map[substitution:map[name:image pattern:IMAGE_NAME:IMAGE_TAG`+
` values:[map[marker:IMAGE_NAME ref:#/definitions/io.k8s.cli.setters.image-name]`+
` map[marker:IMAGE_TAG ref:#/definitions/io.k8s.cli.setters.image-tag]]]]]`,
fmt.Sprintf("%v", s.Schema.Extensions))
}
func TestAddSchemaFromFile_empty(t *testing.T) {
ResetOpenAPI()
inputyaml := `
kind: Example
`
f, err := ioutil.TempFile("", "openapi-")
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
t.FailNow()
}
if !assert.NoError(t, AddSchemaFromFile(f.Name())) {
t.FailNow()
}
if !assert.Equal(t, len(globalSchema.schema.Definitions), 0) {
t.FailNow()
}
}

View File

@@ -44,6 +44,12 @@ type RunFns struct {
// Input can be set to read the Resources from Input rather than from a directory
Input io.Reader
// Network enables network access for functions that declare it
Network bool
// NetworkName is the name of the docker network to use for the container
NetworkName string
// Output can be set to write the result to Output rather than back to the directory
Output io.Writer
@@ -52,7 +58,7 @@ type RunFns struct {
NoFunctionsFromInput *bool
// for testing purposes only
containerFilterProvider func(string, string, *yaml.RNode) kio.Filter
containerFilterProvider func(string, string, string, *yaml.RNode) kio.Filter
}
// Execute runs the command
@@ -119,7 +125,10 @@ func (r RunFns) getFilters(nodes []*yaml.RNode) ([]kio.Filter, error) {
fltrs = append(fltrs, f...)
// explicit filters from a list of directories
f = r.getFunctionsFromFunctions()
f, err = r.getFunctionsFromFunctions()
if err != nil {
return nil, err
}
fltrs = append(fltrs, f...)
return fltrs, nil
@@ -160,8 +169,22 @@ func (r RunFns) getFunctionsFromInput(nodes []*yaml.RNode) ([]kio.Filter, error)
sortFns(buff)
for i := range buff.Nodes {
api := buff.Nodes[i]
network := ""
img, path := filters.GetContainerName(api)
fltrs = append(fltrs, r.containerFilterProvider(img, path, api))
required, err := filters.GetContainerNetworkRequired(api)
if err != nil {
return nil, err
}
if required {
if !r.Network {
// TODO(eddizane): Provide error info about which function needs the network
return fltrs, errors.Errorf("network required but not enabled with --network")
}
network = r.NetworkName
}
fltrs = append(fltrs, r.containerFilterProvider(img, path, network, api))
}
return fltrs, nil
}
@@ -182,8 +205,22 @@ func (r RunFns) getFunctionsFromFunctionPaths() ([]kio.Filter, error) {
}
for i := range buff.Nodes {
api := buff.Nodes[i]
network := ""
img, path := filters.GetContainerName(api)
c := r.containerFilterProvider(img, path, api)
required, err := filters.GetContainerNetworkRequired(api)
if err != nil {
return nil, err
}
if required {
if !r.Network {
// TODO(eddiezane): Provide error info about which function needs the network
return fltrs, errors.Errorf("network required but not enabled with --network")
}
network = r.NetworkName
}
c := r.containerFilterProvider(img, path, network, api)
cf, ok := c.(*filters.ContainerFilter)
if ok {
// functions provided by FunctionPaths are globally scoped
@@ -196,12 +233,26 @@ func (r RunFns) getFunctionsFromFunctionPaths() ([]kio.Filter, error) {
// getFunctionsFromFunctions returns the set of explicitly provided functions as
// Filters
func (r RunFns) getFunctionsFromFunctions() []kio.Filter {
func (r RunFns) getFunctionsFromFunctions() ([]kio.Filter, error) {
var fltrs []kio.Filter
for i := range r.Functions {
api := r.Functions[i]
network := ""
img, path := filters.GetContainerName(api)
c := r.containerFilterProvider(img, path, api)
required, err := filters.GetContainerNetworkRequired(api)
if err != nil {
return nil, err
}
if required {
if !r.Network {
// TODO(eddizane): Provide error info about which function needs the network
return fltrs, errors.Errorf("network required but not enabled with --network")
}
network = r.NetworkName
}
c := r.containerFilterProvider(img, path, network, api)
cf, ok := c.(*filters.ContainerFilter)
if ok {
// functions provided by Functions are globally scoped
@@ -209,7 +260,7 @@ func (r RunFns) getFunctionsFromFunctions() []kio.Filter {
}
fltrs = append(fltrs, c)
}
return fltrs
return fltrs, nil
}
// sortFns sorts functions so that functions with the longest paths come first
@@ -278,10 +329,11 @@ func (r *RunFns) init() {
// if containerFilterProvider hasn't been set, use the default
if r.containerFilterProvider == nil {
r.containerFilterProvider = func(image, path string, api *yaml.RNode) kio.Filter {
r.containerFilterProvider = func(image, path, network string, api *yaml.RNode) kio.Filter {
cf := &filters.ContainerFilter{
Image: image,
Config: api,
Network: network,
StorageMounts: r.StorageMounts,
GlobalScope: r.GlobalScope,
}

View File

@@ -50,7 +50,7 @@ kind:
if !assert.NoError(t, err) {
return
}
filter := instance.containerFilterProvider("example.com:version", "", api)
filter := instance.containerFilterProvider("example.com:version", "", "", api)
assert.Equal(t, &filters.ContainerFilter{Image: "example.com:version", Config: api}, filter)
}
@@ -69,7 +69,7 @@ kind:
if !assert.NoError(t, err) {
return
}
filter := instance.containerFilterProvider("example.com:version", "", api)
filter := instance.containerFilterProvider("example.com:version", "", "", api)
assert.Equal(t, &filters.ContainerFilter{
Image: "example.com:version", Config: api, GlobalScope: true}, filter)
}
@@ -659,8 +659,8 @@ func setupTest(t *testing.T) string {
// getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with
// a filter to s/kind: Deployment/kind: StatefulSet/g.
// this can be used to simulate running a filter.
func getFilterProvider(t *testing.T) func(string, string, *yaml.RNode) kio.Filter {
return func(s, _ string, node *yaml.RNode) kio.Filter {
func getFilterProvider(t *testing.T) func(string, string, string, *yaml.RNode) kio.Filter {
return func(s, _, _ string, node *yaml.RNode) kio.Filter {
// parse the filter from the input
filter := yaml.YFilter{}
b := &bytes.Buffer{}

220
kyaml/setters2/add.go Normal file
View File

@@ -0,0 +1,220 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"strings"
"github.com/go-openapi/spec"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Add creates or updates setter or substitution references from resource fields.
// Requires that at least one of FieldValue and FieldName have been set.
type Add struct {
// FieldValue if set will add the OpenAPI reference to fields if they have this value.
// Optional. If unspecified match all field values.
FieldValue string
// FieldName if set will add the OpenAPI reference to fields with this name or path
// FieldName may be the full name of the field, full path to the field, or the path suffix.
// e.g. all of the following would match spec.template.spec.containers.image --
// [image, containers.image, spec.containers.image, template.spec.containers.image,
// spec.template.spec.containers.image]
// Optional. If unspecified match all field names.
FieldName string
// Ref is the OpenAPI reference to set on the matching fields as a comment.
Ref string
}
// Filter implements yaml.Filter
func (a *Add) Filter(object *yaml.RNode) (*yaml.RNode, error) {
if a.FieldName == "" && a.FieldValue == "" {
return nil, errors.Errorf("must specify either fieldName or fieldValue")
}
if a.Ref == "" {
return nil, errors.Errorf("must specify ref")
}
return object, accept(a, object)
}
// visitScalar implements visitor
// visitScalar will set the field metadata on each scalar field whose name + value match
func (a *Add) visitScalar(object *yaml.RNode, p string) error {
// check if the field matches
if a.FieldName != "" && !strings.HasSuffix(p, a.FieldName) {
return nil
}
if a.FieldValue != "" && a.FieldValue != object.YNode().Value {
return nil
}
// read the field metadata
fm := fieldmeta.FieldMeta{}
if err := fm.Read(object); err != nil {
return err
}
// create the ref on the field metadata
r, err := spec.NewRef(a.Ref)
if err != nil {
return err
}
fm.Schema.Ref = r
// write the field metadata
if err := fm.Write(object); err != nil {
return err
}
return nil
}
const (
// CLIDefinitionsPrefix is the prefix for cli definition keys.
CLIDefinitionsPrefix = "io.k8s.cli."
// SetterDefinitionPrefix is the prefix for setter definition keys.
SetterDefinitionPrefix = CLIDefinitionsPrefix + "setters."
// SubstitutionDefinitionPrefix is the prefix for substitution definition keys.
SubstitutionDefinitionPrefix = CLIDefinitionsPrefix + "substitutions."
// DefinitionsPrefix is the prefix used to reference definitions in the OpenAPI
DefinitionsPrefix = "#/definitions/"
)
// SetterDefinition may be used to update a files OpenAPI definitions with a new setter.
type SetterDefinition struct {
// Name is the name of the setter to create or update.
Name string `yaml:"name"`
// Value is the value of the setter.
Value string `yaml:"value"`
// SetBy is the person or role that last set the value.
SetBy string `yaml:"setBy,omitempty"`
// Description is a description of the value.
Description string `yaml:"description,omitempty"`
// Count is the number of fields set by this setter.
Count int `yaml:"count,omitempty"`
// Type is the type of the setter value.
Type string `yaml:"type,omitempty"`
// EnumValues is a map of possible setter values to actual field values.
// If EnumValues is specified, then the value set the by user 1) MUST
// be present in the enumValues map as a key, and 2) the map entry value
// MUST be used as the value to set in the configuration (rather than the key)
// Example -- may be used for t-shirt sizing values by allowing cpu to be
// set to small, medium or large, and then mapping these values to cpu values -- 0.5, 2, 8
EnumValues map[string]string `yaml:"enumValues,omitempty"`
}
func (sd SetterDefinition) AddToFile(path string) error {
return yaml.UpdateFile(sd, path)
}
func (sd SetterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) {
key := SetterDefinitionPrefix + sd.Name
def, err := object.Pipe(yaml.LookupCreate(
yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions", key))
if err != nil {
return nil, err
}
if sd.Description != "" {
err = def.PipeE(yaml.FieldSetter{Name: "description", StringValue: sd.Description})
if err != nil {
return nil, err
}
// don't write the description to the extension
sd.Description = ""
}
if sd.Type != "" {
err = def.PipeE(yaml.FieldSetter{Name: "type", StringValue: sd.Type})
if err != nil {
return nil, err
}
// don't write the type to the extension
sd.Type = ""
}
ext, err := def.Pipe(yaml.LookupCreate(yaml.MappingNode, K8sCliExtensionKey))
if err != nil {
return nil, err
}
b, err := yaml.Marshal(sd)
if err != nil {
return nil, err
}
y, err := yaml.Parse(string(b))
if err != nil {
return nil, err
}
if err := ext.PipeE(yaml.SetField("setter", y)); err != nil {
return nil, err
}
return object, nil
}
// SetterDefinition may be used to update a files OpenAPI definitions with a new substitution.
type SubstitutionDefinition struct {
// Name is the name of the substitution to create or update
Name string `yaml:"name"`
// Pattern is the substitution pattern into which setter values are substituted
Pattern string `yaml:"pattern"`
// Values are setters which are substituted into pattern to produce a field value
Values []Value `yaml:"values"`
}
type Value struct {
// Marker is the string marker in pattern that is replace by the referenced setter.
Marker string `yaml:"marker"`
// Ref is a reference to a setter to pull the replacement value from.
Ref string `yaml:"ref"`
}
func (sd SubstitutionDefinition) AddToFile(path string) error {
return yaml.UpdateFile(sd, path)
}
func (sd SubstitutionDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) {
// create the substitution extension value by marshalling the SubstitutionDefinition itself
b, err := yaml.Marshal(sd)
if err != nil {
return nil, err
}
sub, err := yaml.Parse(string(b))
if err != nil {
return nil, err
}
// lookup or create the definition for the substitution
defKey := SubstitutionDefinitionPrefix + sd.Name
def, err := object.Pipe(yaml.LookupCreate(
yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions", defKey, "x-k8s-cli"))
if err != nil {
return nil, err
}
// set the substitution on the definition
if err := def.PipeE(yaml.SetField("substitution", sub)); err != nil {
return nil, err
}
return object, nil
}

358
kyaml/setters2/add_test.go Normal file
View File

@@ -0,0 +1,358 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestAdd_Filter(t *testing.T) {
var tests = []struct {
name string
add Add
input string
expected string
err string
}{
{
name: "add-replicas",
add: Add{
FieldValue: "3",
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "add-replicas-annotations",
add: Add{
FieldValue: "3",
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "add-replicas-name",
add: Add{
FieldValue: "3",
FieldName: "replicas",
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "add-replicas-2x",
add: Add{
FieldValue: "3",
FieldName: "replicas",
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
replicas: 3
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "add-replicas-1x",
add: Add{
FieldValue: "3",
FieldName: "spec.replicas",
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
replicas: 3
spec:
replicas: 3
`,
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
replicas: 3
spec:
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
`,
},
{
name: "add-replicas-error",
add: Add{
Ref: "#/definitions/io.k8s.cli.setters.replicas",
},
err: "must specify either fieldName or fieldValue",
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
`,
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// parse the input to be modified
r, err := yaml.Parse(test.input)
if !assert.NoError(t, err) {
t.FailNow()
}
// invoke add
result, err := test.add.Filter(r)
if test.err != "" {
if !assert.Equal(t, test.err, err.Error()) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
// compare the actual and expected output
actual, err := result.String()
if !assert.NoError(t, err) {
t.FailNow()
}
actual = strings.TrimSpace(actual)
expected := strings.TrimSpace(test.expected)
if !assert.Equal(t, expected, actual) {
t.FailNow()
}
})
}
}
var resourcefile = `apiVersion: resource.dev/v1alpha1
kind: resourcefile
metadata:
name: hello-world-set
upstream:
type: git
git:
commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe
directory: /package-examples/helloworld-set
ref: v0.1.0
packageMetadata:
shortDescription: example package using setters`
func TestAdd_Filter2(t *testing.T) {
path := filepath.Join(os.TempDir(), "resourcefile")
//write initial resourcefile to temp path
err := ioutil.WriteFile(path, []byte(resourcefile), 0666)
if !assert.NoError(t, err) {
t.FailNow()
}
//add a setter definition
sd := SetterDefinition{
Name: "image",
Value: "1",
}
err = sd.AddToFile(path)
if !assert.NoError(t, err) {
t.FailNow()
}
// update setter definition
sd2 := SetterDefinition{
Name: "image",
Value: "2",
}
err = sd2.AddToFile(path)
if !assert.NoError(t, err) {
t.FailNow()
}
b, err := ioutil.ReadFile(path)
if err != nil {
t.FailNow()
}
expected := `apiVersion: resource.dev/v1alpha1
kind: resourcefile
metadata:
name: hello-world-set
upstream:
type: git
git:
commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe
directory: /package-examples/helloworld-set
ref: v0.1.0
packageMetadata:
shortDescription: example package using setters
openAPI:
definitions:
io.k8s.cli.setters.image:
x-k8s-cli:
setter:
name: image
value: "2"
`
assert.Equal(t, expected, string(b))
}
func TestAddUpdateSubstitution(t *testing.T) {
path := filepath.Join(os.TempDir(), "resourcefile")
//write initial resourcefile to temp path
err := ioutil.WriteFile(path, []byte(resourcefile), 0666)
if !assert.NoError(t, err) {
t.FailNow()
}
value1 := Value{
Marker: "IMAGE_NAME",
Ref: "#/definitions/io.k8s.cli.setters.image-name",
}
value2 := Value{
Marker: "IMAGE_TAG",
Ref: "#/definitions/io.k8s.cli.setters.image-tag",
}
values := []Value{value1, value2}
//add a setter definition
subd := SubstitutionDefinition{
Name: "image",
Pattern: "IMAGE_NAME:IMAGE_TAG",
Values: values,
}
err = subd.AddToFile(path)
if !assert.NoError(t, err) {
t.FailNow()
}
// update setter definition
subd2 := SubstitutionDefinition{
Name: "image",
Pattern: "IMAGE_NAME:IMAGE_TAG2",
}
err = subd2.AddToFile(path)
if !assert.NoError(t, err) {
t.FailNow()
}
b, err := ioutil.ReadFile(path)
if err != nil {
t.FailNow()
}
expected := `apiVersion: resource.dev/v1alpha1
kind: resourcefile
metadata:
name: hello-world-set
upstream:
type: git
git:
commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe
directory: /package-examples/helloworld-set
ref: v0.1.0
packageMetadata:
shortDescription: example package using setters
openAPI:
definitions:
io.k8s.cli.substitutions.image:
x-k8s-cli:
substitution:
name: image
pattern: IMAGE_NAME:IMAGE_TAG2
values: []
`
assert.Equal(t, expected, string(b))
}

163
kyaml/setters2/doc.go Normal file
View File

@@ -0,0 +1,163 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
// Package setters2 contains libraries for setting resource field values from OpenAPI setter
// extensions.
//
// Setters
//
// Setters are used to programmatically set configuration field values -- e.g. through a cli or ui.
//
// Setters are defined through OpenAPI definitions using the x-k8s-cli extension.
// Note: additional OpenAPI definitions may be registered through openapi.AddSchema([]byte)
//
// Example OpenAPI schema containing a setter:
//
// {
// "definitions": {
// "io.k8s.cli.setters.replicas": {
// "x-k8s-cli": {
// "setter": {
// "name": "replicas",
// "value": "4"
// }
// }
// }
// }
// }
//
// Setter fields:
//
// x-k8s-cli.setter.name: name of the setter
// x-k8s-cli.setter.value: value of the setter that should be applied to fields
//
// The setter definition key must be of the form "io.k8s.cli.setters.NAME", where NAME matches the
// value of "x-k8s-cli.setter.name".
//
// When Set.Filter is called, the named setter will have its value applied to all resource
// fields referencing it.
//
// Fields may reference setters through a yaml comment containing the serialized JSON OpenAPI.
//
// Example Deployment resource with a "spec.replicas" field set by the "replicas" setter:
//
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// spec:
// replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
//
// If the OpenAPI io.k8s.cli.setters.replicas x-k8s-cli.setter.value was changed from "4" to "5",
// then calling Set{Name: "replicas"}.Filter(deployment) would update the Deployment spec.replicas
// value from 4 to 5.
//
// Updated OpenAPI:
//
// {
// "definitions": {
// "io.k8s.cli.setters.replicas": {
// "x-k8s-cli": {
// "setter": {
// "name": "replicas",
// "value": "5"
// }
// }
// }
// }
// }
//
// Updated Deployment Configuration:
//
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// spec:
// replicas: 5 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
//
// Substitutions
//
// Substitutions are used to programmatically set configuration field values using multiple
// setters which are substituted into a pattern string.
//
// Substitutions may be used when a field value does not cleanly map to a single setter, but
// instead matches some string pattern where setters may be substituted in.
//
// Fields may reference substitutions the same way they do setters, however substitutions
// reference setters from which they are derived.
//
// Example OpenAPI schema containing a substitution derived from 2 setters:
//
// {
// "definitions": {
// "io.k8s.cli.setters.image-name": {
// "x-k8s-cli": {
// "setter": {
// "name": "image-name",
// "value": "nginx"
// }
// }
// },
// "io.k8s.cli.setters.image-tag": {
// "x-k8s-cli": {
// "setter": {
// "name": "image-tag",
// "value": "1.8.1"
// }
// }
// },
// "io.k8s.cli.substitutions.image-name-tag": {
// "x-k8s-cli": {
// "substitution": {
// "name": "image-name-tag",
// "pattern": "IMAGE_NAME:IMAGE_TAG",
// "values": [
// {"marker": "IMAGE_NAME", "ref": "#/definitions/io.k8s.cli.setters.image-name"}
// {"marker": "IMAGE_TAG", "ref": "#/definitions/io.k8s.cli.setters.image-tag"}
// ]
// }
// }
// }
// }
// }
//
// Substitution Fields.
//
// x-k8s-cli.substitution.name: name of the substitution
// x-k8s-cli.substitution.pattern: string pattern to substitute markers into
// x-k8s-cli.substitution.values.marker: the marker substring within pattern to replace
// x-k8s-cli.substitution.values.ref: the setter ref containing the value to replace the marker with
//
// The substitution is composed of a "pattern" containing markers, and a list of setter "values"
// which are substituted into the markers.
//
// Example Deployment with substitution:
//
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// spec:
// template:
// spec:
// containers:
// - name: nginx
// image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-name-tag"}
//
// spec.template.spec.containers[name=nginx].image is set by the "image" substitution any time
// either "image-name" or "image-tag" is set. Whenever any setter referenced by a substitution
// is set, the substitution will be recalculated by substituting its values into its pattern.
//
//
// If the OpenAPI io.k8s.cli.setters.image-name x-k8s-cli.setter.value was changed from "1.8.1"
// to "1.8.2", then calling either Set{Name: "image-name"}.Filter(deployment) or
// Set{Name: "image-tag"}.Filter(deployment) would update the Deployment field
// spec.template.spec.container[name=nginx].image from "nginx:1.8.1" to "nginx:1.8.2".
//
// Adding Field References
//
// References to setters and substitutions may be added to fields using the Add Filter.
// Add will write a JSON OpenAPI string as a comment to any fields matching the specified
// FieldName add FieldValue.
package setters2

View File

@@ -0,0 +1,202 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"fmt"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// ExampleSet demonstrates using Set to replace the current field value in an object
func ExampleSet() {
openapi.ResetOpenAPI()
// OpenAPI definitions with setter extensions on definitions
schema := `
{
"definitions": {
"io.k8s.cli.setters.replicas": {
"x-k8s-cli": {
"setter": {
"name": "replicas",
"value": "4"
}
}
}
}
}
`
// Resource with field referencing OpenAPI definition
deployment := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`
_, err := openapi.AddSchema([]byte(schema)) // add the schema definitions
if err != nil {
panic(err)
}
object := yaml.MustParse(deployment) // parse the configuration
err = object.PipeE(&Set{Name: "replicas"}) // set replicas from the setter
if err != nil {
panic(err)
}
fmt.Println(object.MustString())
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// spec:
// replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
}
// ExampleSet_Substitution demonstrates using Set to substitute a value into the field of
// an object. Only part of the field value is modified.
func ExampleSet_substitution() {
openapi.ResetOpenAPI()
// set the version setter
schema := `
{
"definitions": {
"io.k8s.cli.setters.version": {
"x-k8s-cli": {
"setter": {
"name": "version",
"value": "1.8.1"
}
}
},
"io.k8s.cli.substitutions.image": {
"x-k8s-cli": {
"substitution": {
"name": "image",
"pattern": "nginx:VERSION",
"values": [
{"marker": "VERSION", "ref": "#/definitions/io.k8s.cli.setters.version"}
]
}
}
}
}
}`
deployment := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
template:
spec:
containers:
- name: nginx
image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
`
_, err := openapi.AddSchema([]byte(schema)) // add the schema definitions
if err != nil {
panic(err)
}
object := yaml.MustParse(deployment) // parse the configuration
err = object.PipeE(&Set{Name: "version"}) // set replicas from the setter
if err != nil {
panic(err)
}
// Print the object with the update value
fmt.Println(object.MustString())
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// spec:
// template:
// spec:
// containers:
// - name: nginx
// image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
}
// ExampleAdd demonstrates adding a setter reference to fields.
func ExampleAdd_fieldName() {
deployment := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3
spec:
replicas: 3
`
object := yaml.MustParse(deployment) // parse the configuration
err := object.PipeE(&Add{
Ref: "#/definitions/io.k8s.cli.setters.replicas",
FieldName: "replicas",
})
if err != nil {
panic(err)
}
// Print the object with the update value
fmt.Println(object.MustString())
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// annotations:
// something: 3
// spec:
// replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
}
// ExampleAdd demonstrates adding a setter reference to fields.
func ExampleAdd_fieldValue() {
deployment := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
something: 3
spec:
replicas: 3
`
object := yaml.MustParse(deployment) // parse the configuration
err := object.PipeE(&Add{
Ref: "#/definitions/io.k8s.cli.setters.replicas",
FieldValue: "3",
})
if err != nil {
panic(err)
}
// Print the object with the update value
fmt.Println(object.MustString())
// Output:
// apiVersion: apps/v1
// kind: Deployment
// metadata:
// name: nginx-deployment
// annotations:
// something: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
// spec:
// replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
}

117
kyaml/setters2/list.go Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"sort"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// List lists the setters specified in the OpenAPI
type List struct {
Name string
Setters []SetterDefinition
}
// List initializes l.Setters with the setters from the OpenAPI definitions in the file
func (l *List) List(openAPIPath, resourcePath string) error {
if err := openapi.AddSchemaFromFile(openAPIPath); err != nil {
return err
}
y, err := yaml.ReadFile(openAPIPath)
if err != nil {
return err
}
return l.list(y, resourcePath)
}
func (l *List) list(object *yaml.RNode, resourcePath string) error {
// read the OpenAPI definitions
def, err := object.Pipe(yaml.LookupCreate(yaml.MappingNode, "openAPI", "definitions"))
if err != nil {
return err
}
if yaml.IsEmpty(def) {
return nil
}
// iterate over definitions -- find those that are setters
err = def.VisitFields(func(node *yaml.MapNode) error {
setter := SetterDefinition{}
// the definition key -- contains the setter name
key := node.Key.YNode().Value
if !strings.HasPrefix(key, SetterDefinitionPrefix) {
// not a setter -- doesn't have the right prefix
return nil
}
setterNode, err := node.Value.Pipe(yaml.Lookup(K8sCliExtensionKey, "setter"))
if err != nil {
return err
}
if yaml.IsEmpty(setterNode) {
// has the setter prefix, but missing the setter extension
return errors.Errorf("missing x-k8s-cli.setter for %s", key)
}
// unmarshal the yaml for the setter extension into the definition struct
b, err := setterNode.String()
if err != nil {
return err
}
if err := yaml.Unmarshal([]byte(b), &setter); err != nil {
return err
}
if l.Name != "" && l.Name != setter.Name {
// not the setter that was requested by list
return nil
}
// the description is not part of the extension, and should be pulled out
// separately from the extension values.
description := node.Value.Field("description")
if description != nil {
setter.Description = description.Value.YNode().Value
}
// count the number of fields set by this setter
setter.Count, err = l.count(resourcePath, setter.Name)
if err != nil {
return err
}
l.Setters = append(l.Setters, setter)
return nil
})
if err != nil {
return err
}
// sort the setters by their name
sort.Slice(l.Setters, func(i, j int) bool {
return l.Setters[i].Name < l.Setters[j].Name
})
return nil
}
// count returns the number of fields set by the setter with name
func (l *List) count(path, name string) (int, error) {
s := &Set{Name: name}
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: path}},
Filters: []kio.Filter{kio.FilterAll(s)},
}.Execute()
return s.Count, err
}

289
kyaml/setters2/list_test.go Normal file
View File

@@ -0,0 +1,289 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/openapi"
)
func TestList(t *testing.T) {
var tests = []struct {
name string
setter string
openapi string
input string
expected []SetterDefinition
}{
{
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: []SetterDefinition{
{Name: "replicas", Value: "3", SetBy: "me", Description: "hello world", Count: 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: []SetterDefinition{
{Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 2},
{Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 1},
{Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 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: []SetterDefinition{
{Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3},
{Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 2},
{Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 2},
},
},
{
name: "list-name",
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
`,
setter: "image",
expected: []SetterDefinition{
{Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3},
},
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// reset the openAPI afterward
defer openapi.ResetOpenAPI()
initSchema(t, test.openapi)
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.openapi), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
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()
}
// invoke the setter
instance := &List{Name: test.setter}
err = instance.List(f.Name(), r.Name())
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, test.expected, instance.Setters) {
t.FailNow()
}
})
}
}

228
kyaml/setters2/set.go Normal file
View File

@@ -0,0 +1,228 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"strings"
"github.com/go-openapi/spec"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Set sets resource field values from an OpenAPI setter
type Set struct {
// Name is the name of the setter to set on the object. i.e. matches the x-k8s-cli.setter.name
// of the setter that should have its value applied to fields which reference it.
Name string
// Count is the number of fields that were updated by calling Filter
Count int
}
// Filter implements Set as a yaml.Filter
func (s *Set) Filter(object *yaml.RNode) (*yaml.RNode, error) {
return object, accept(s, object)
}
// visitScalar
func (s *Set) visitScalar(object *yaml.RNode, p string) error {
// get the openAPI for this field describing how to apply the setter
ext, sch, err := getExtFromComment(object)
if err != nil {
return err
}
if ext == nil {
return nil
}
// perform a direct set of the field if it matches
if s.set(object, ext, sch) {
s.Count++
return nil
}
// perform a substitution of the field if it matches
sub, err := s.substitute(object, ext, sch)
if err != nil {
return err
}
if sub {
s.Count++
}
return nil
}
// substitute updates the value of field from ext if ext contains a substitution that
// depends on a setter whose name matches s.Name.
func (s *Set) substitute(field *yaml.RNode, ext *cliExtension, _ *spec.Schema) (bool, error) {
nameMatch := false
// check partial setters to see if they contain the setter as part of a
// substitution
if ext.Substitution == nil {
return false, nil
}
p := ext.Substitution.Pattern
// substitute each setter into the pattern to get the new value
for _, v := range ext.Substitution.Values {
if v.Ref == "" {
return false, errors.Errorf(
"missing reference on substitution " + ext.Substitution.Name)
}
ref, err := spec.NewRef(v.Ref)
if err != nil {
return false, errors.Wrap(err)
}
setter, err := openapi.Resolve(&ref) // resolve the setter to its openAPI def
if err != nil {
return false, errors.Wrap(err)
}
subSetter, err := getExtFromSchema(setter) // parse the extension out of the openAPI
if err != nil {
return false, errors.Wrap(err)
}
if val, found := subSetter.Setter.EnumValues[subSetter.Setter.Value]; found {
// the setter has an enum-map. we should replace the marker with the
// enum value looked up from the map rather than the enum key
p = strings.ReplaceAll(p, v.Marker, val)
} else {
// substitute the setters current value into the substitution pattern
p = strings.ReplaceAll(p, v.Marker, subSetter.Setter.Value)
}
if subSetter.Setter.Name == s.Name {
// the substitution depends on the specified setter
nameMatch = true
}
}
if !nameMatch {
// doesn't depend on the setter, don't modify its value
return false, nil
}
// TODO(pwittrock): validate the field value
field.YNode().Value = p
// substitutions are always strings
field.YNode().Tag = "!!str"
return true, nil
}
// set applies the value from ext to field if its name matches s.Name
func (s *Set) set(field *yaml.RNode, ext *cliExtension, sch *spec.Schema) bool {
// check full setter
if ext.Setter == nil || ext.Setter.Name != s.Name {
return false
}
// TODO(pwittrock): validate the field value
if val, found := ext.Setter.EnumValues[ext.Setter.Value]; found {
// the setter has an enum-map. we should replace the marker with the
// enum value looked up from the map rather than the enum key
field.YNode().Value = val
return true
}
// this has a full setter, set its value
field.YNode().Value = ext.Setter.Value
// format the node so it is quoted if it is a string
yaml.FormatNonStringStyle(field.YNode(), *sch)
return true
}
// SetOpenAPI updates a setter value
type SetOpenAPI struct {
// Name is the name of the setter to add
Name string `yaml:"name"`
// Value is the current value of the setter
Value string `yaml:"value"`
Description string `yaml:"description"`
SetBy string `yaml:"setBy"`
}
// UpdateFile updates the OpenAPI definitions in a file with the given setter value.
func (s SetOpenAPI) UpdateFile(path string) error {
return yaml.UpdateFile(s, path)
}
func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) {
key := SetterDefinitionPrefix + s.Name
def, err := object.Pipe(yaml.Lookup(
"openAPI", "definitions", key, "x-k8s-cli", "setter"))
if err != nil {
return nil, err
}
if def == nil {
return nil, errors.Errorf("no setter %s found", s.Name)
}
// if the setter contains an enumValues map, then ensure the set value appears
// as a key in the map
if values, err := def.Pipe(yaml.Lookup("enumValues")); err != nil {
// error looking up the enumValues
return nil, err
} else if values != nil {
// contains enumValues map -- validate the set value against the map entries
// get the enumValues keys
fields, err := values.Fields()
if err != nil {
return nil, err
}
// search for the user provided value in the set of allowed values
var match bool
for i := range fields {
if fields[i] == s.Value {
// found a match, we are good
match = true
break
}
}
if !match {
// no match found -- provide an informative error to the user
return nil, errors.Errorf("%s does not match the possible values for %s: [%s]",
s.Value, s.Name, strings.Join(fields, ","))
}
}
v := yaml.NewScalarRNode(s.Value)
// values are always represented as strings the OpenAPI
// since the are unmarshalled into strings. Use double quote style to
// ensure this consistently.
v.YNode().Tag = "!!str"
v.YNode().Style = yaml.DoubleQuotedStyle
if err := def.PipeE(&yaml.FieldSetter{Name: "value", Value: v}); err != nil {
return nil, err
}
if err := def.PipeE(&yaml.FieldSetter{Name: "setBy", StringValue: s.SetBy}); err != nil {
return nil, err
}
if s.Description != "" {
d, err := object.Pipe(yaml.LookupCreate(
yaml.MappingNode, "openAPI", "definitions", key))
if err != nil {
return nil, err
}
if err := d.PipeE(&yaml.FieldSetter{Name: "description", StringValue: s.Description}); err != nil {
return nil, err
}
}
return object, nil
}

1043
kyaml/setters2/set_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package settersutil
import (
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
)
// FieldSetter sets the value for a field setter.
type FieldSetter struct {
// Name is the name of the setter to set
Name string
// Value is the value to set
Value string
Description string
SetBy string
}
// Set updates the OpenAPI definitions and resources with the new setter value
func (fs FieldSetter) Set(openAPIPath, resourcesPath string) (int, error) {
// Update the OpenAPI definitions
soa := setters2.SetOpenAPI{
Name: fs.Name, Value: fs.Value, Description: fs.Description, SetBy: fs.SetBy}
if err := soa.UpdateFile(openAPIPath); err != nil {
return 0, err
}
// Load the updated definitions
if err := openapi.AddSchemaFromFile(openAPIPath); err != nil {
return 0, err
}
// Update the resources with the new value
inout := &kio.LocalPackageReadWriter{PackagePath: resourcesPath}
s := &setters2.Set{Name: fs.Name}
err := kio.Pipeline{
Inputs: []kio.Reader{inout},
Filters: []kio.Filter{kio.FilterAll(s)},
Outputs: []kio.Writer{inout},
}.Execute()
return s.Count, err
}

View File

@@ -0,0 +1,64 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package settersutil
import (
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
)
// SetterCreator creates or updates a setter in the OpenAPI definitions, and inserts references
// to the setter from matching resource fields.
type SetterCreator struct {
// Name is the name of the setter to create or update.
Name string
Description string
SetBy string
Type string
// FieldName if set will add the OpenAPI reference to fields with this name or path
// FieldName may be the full name of the field, full path to the field, or the path suffix.
// e.g. all of the following would match spec.template.spec.containers.image --
// [image, containers.image, spec.containers.image, template.spec.containers.image,
// spec.template.spec.containers.image]
// Optional. If unspecified match all field names.
FieldName string
// FieldValue if set will add the OpenAPI reference to fields if they have this value.
// Optional. If unspecified match all field values.
FieldValue string
}
func (c SetterCreator) Create(openAPIPath, resourcesPath string) error {
// Update the OpenAPI definitions to hace the setter
sd := setters2.SetterDefinition{
Name: c.Name, Value: c.FieldValue, Description: c.Description, SetBy: c.SetBy,
Type: c.Type,
}
if err := sd.AddToFile(openAPIPath); err != nil {
return err
}
// Load the updated definitions
if err := openapi.AddSchemaFromFile(openAPIPath); err != nil {
return err
}
// Update the resources with the setter reference
inout := &kio.LocalPackageReadWriter{PackagePath: resourcesPath}
return kio.Pipeline{
Inputs: []kio.Reader{inout},
Filters: []kio.Filter{kio.FilterAll(
&setters2.Add{
FieldName: c.FieldName,
FieldValue: c.FieldValue,
Ref: setters2.DefinitionsPrefix + setters2.SetterDefinitionPrefix + c.Name,
})},
Outputs: []kio.Writer{inout},
}.Execute()
}

View File

@@ -0,0 +1,258 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package settersutil
import (
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/setters2"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SubstitutionCreator creates or updates a substitution in the OpenAPI definitions, and
// inserts references to the substitution from matching resource fields.
type SubstitutionCreator struct {
// Name is the name of the substitution to create
Name string
// Pattern is the substitution pattern
Pattern string
// Values are the substitution values for the pattern
Values []setters2.Value
// FieldName if set will add the OpenAPI reference to fields with this name or path
// FieldName may be the full name of the field, full path to the field, or the path suffix.
// e.g. all of the following would match spec.template.spec.containers.image --
// [image, containers.image, spec.containers.image, template.spec.containers.image,
// spec.template.spec.containers.image]
// Optional. If unspecified match all field names.
FieldName string
// FieldValue if set will add the OpenAPI reference to fields if they have this value.
// Optional. If unspecified match all field values.
FieldValue string
}
func (c SubstitutionCreator) Create(openAPIPath, resourcesPath string) error {
d := setters2.SubstitutionDefinition{
Name: c.Name,
Values: c.Values,
Pattern: c.Pattern,
}
err := c.CreateSettersForSubstitution(openAPIPath)
if err != nil {
return err
}
if err := d.AddToFile(openAPIPath); err != nil {
return err
}
// Load the updated definitions
if err := openapi.AddSchemaFromFile(openAPIPath); err != nil {
return err
}
// Update the resources with the setter reference
inout := &kio.LocalPackageReadWriter{PackagePath: resourcesPath}
return kio.Pipeline{
Inputs: []kio.Reader{inout},
Filters: []kio.Filter{kio.FilterAll(
&setters2.Add{
FieldName: c.FieldName,
FieldValue: c.FieldValue,
Ref: setters2.DefinitionsPrefix + setters2.SubstitutionDefinitionPrefix + c.Name,
})},
Outputs: []kio.Writer{inout},
}.Execute()
}
// CreateSettersForSubstitution creates the setters for all the references in the substitution
// values if they don't already exist in openAPIPath file.
func (c SubstitutionCreator) CreateSettersForSubstitution(openAPIPath string) error {
y, err := yaml.ReadFile(openAPIPath)
if err != nil {
return err
}
m, err := c.GetValuesForMarkers()
if err != nil {
return err
}
// for each ref in values, check if the setter already exists, if not create them
for _, value := range c.Values {
obj, err := y.Pipe(yaml.Lookup(
// get the setter key from ref. Ex: from #/definitions/io.k8s.cli.setters.image_setter
// extract io.k8s.cli.setters.image_setter
"openAPI", "definitions", strings.TrimPrefix(value.Ref, "#/definitions/")))
if err != nil {
return err
}
if obj == nil {
sd := setters2.SetterDefinition{
// get the setter name from ref. Ex: from #/definitions/io.k8s.cli.setters.image_setter
// extract image_setter
Name: strings.TrimPrefix(value.Ref, "#/definitions/io.k8s.cli.setters."),
Value: m[value.Marker],
}
err := sd.AddToFile(openAPIPath)
if err != nil {
return err
}
}
}
return nil
}
// GetValuesForMarkers parses the pattern and field value to derive values for the
// markers in the pattern string. Returns error if the marker values can't be derived
func (c SubstitutionCreator) GetValuesForMarkers() (map[string]string, error) {
m := make(map[string]string)
indices, err := c.GetStartIndices()
if err != nil {
return nil, err
}
fv := c.FieldValue
pattern := c.Pattern
fvInd := 0
patternInd := 0
// iterate fv, pattern with indices fvInd, patternInd respectively and when patternInd hits the index of a marker,
// freeze patternInd and iterate fvInd and capture string till we find the substring just after current marker
// and before next marker
// Ex: fv = "something/ubuntu:0.1.0", pattern = "something/IMAGE:VERSION", till patternInd reaches 10
// just proceed fvInd and patternInd and check if fv[fvInd]==pattern[patternInd] when patternInd is 10,
// freeze patternInd and move fvInd till it sees substring ':' which derives IMAGE = ubuntu and so on.
for fvInd < len(fv) && patternInd < len(pattern) {
// if we hit marker index, extract its corresponding value
if marker, ok := indices[patternInd]; ok {
// increment the patternInd to end of marker. This helps us to extract the substring before next marker.
patternInd += len(marker)
var value string
if value, fvInd, err = c.extractValueForMarker(fvInd, fv, patternInd, indices); err != nil {
return nil, err
}
// if marker is repeated in the pattern, make sure that the corresponding values
// are same and throw error if not.
if prevValue, ok := m[marker]; ok && prevValue != value {
return nil, errors.Errorf(
"marker %s is found to have different values %s and %s", marker, prevValue, value)
}
m[marker] = value
} else {
// Ex: fv = "samething/ubuntu:0.1.0" pattern = "something/IMAGE:VERSION". Error out at 'a' in fv.
if fv[fvInd] != pattern[patternInd] {
return nil, errors.Errorf(
"unable to derive values for markers, " +
"create setters for all markers and then try again")
}
fvInd++
patternInd++
}
}
// check if both strings are completely visited or throw error
if fvInd < len(fv) || patternInd < len(pattern) {
return nil, errors.Errorf(
"unable to derive values for markers, " +
"create setters for all markers and then try again")
}
return m, nil
}
// GetStartIndices returns the start indices of all the markers in the pattern
func (c SubstitutionCreator) GetStartIndices() (map[int]string, error) {
indices := make(map[int]string)
for _, value := range c.Values {
found := false
for i := range c.Pattern {
if strings.HasPrefix(c.Pattern[i:], value.Marker) {
indices[i] = value.Marker
found = true
}
}
if !found {
return nil, errors.Errorf("unable to find marker " + value.Marker + " in the pattern")
}
}
if err := validateMarkers(indices); err != nil {
return nil, err
}
return indices, nil
}
// validateMarkers takes the indices map, checks if any of 2 markers not have delimiters,
// checks if any marker is substring of other and returns error
func validateMarkers(indices map[int]string) error {
for k1, v1 := range indices {
for k2, v2 := range indices {
if k1 != k2 && k1+len(v1) == k2 {
return errors.Errorf(
"markers %s and %s are found to have no delimiters between them,"+
" pre-create setters and try again", v1, v2)
}
if v1 != v2 && strings.Contains(v1, v2) {
return errors.Errorf(
"markers %s is substring of %s,"+
" no marker should be substring of other", v2, v1)
}
}
}
return nil
}
// extractValueForMarker returns the value string for a marker and the incremented index
func (c SubstitutionCreator) extractValueForMarker(fvInd int, fv string, patternInd int, indices map[int]string) (string, int, error) {
nonMarkerStr := strTillNextMarker(indices, patternInd, c.Pattern)
// return the remaining string of fv till end if patternInd is at end of pattern
if patternInd == len(c.Pattern) {
return fv[fvInd:], len(fv), nil
}
// split remaining fv starting from fvInd with the non marker substring delimiter and get the first value
// In example fv = "something/ubuntu::0.1.0", pattern = "something/IMAGE::VERSION",
// split with "::" delimiter in fv which gives markerValue = ubuntu for marker IMAGE
// increment fvInd by length of extracted marker value and return fvInd
if markerValues := strings.Split(fv[fvInd:], nonMarkerStr); len(markerValues) > 0 {
return markerValues[0], fvInd + len(markerValues[0]), nil
}
return "", -1, errors.Errorf(
"unable to derive values for markers," +
" create setters for all markers and then try again")
}
// substrOfLen takes a string, start index and length and returns substring of given length
// or till end of string
func substrOfLen(str string, startInd int, length int) string {
return str[startInd:min(len(str), startInd+length)]
}
// strTillNextMarker takes in the indices map, a start index and returns the substring till
// start of next marker
func strTillNextMarker(indices map[int]string, startInd int, pattern string) string {
// initialize with max value which is length of pattern
nextMarkerStartInd := len(pattern)
for ind := range indices {
if ind > startInd {
nextMarkerStartInd = min(ind-startInd, nextMarkerStartInd)
}
}
return substrOfLen(pattern, startInd, nextMarkerStartInd)
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}

View File

@@ -0,0 +1,111 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package settersutil
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/setters2"
)
func TestGetValuesForMarkers(t *testing.T) {
var tests = []struct {
name string
pattern string
fieldValue string
markers []string
expectedError error
expectedOutput map[string]string
}{
{
name: "positive example",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGE::VERSION/otherthing/IMAGE::VERSION/",
fieldValue: "something/nginx::0.1.0/otherthing/nginx::0.1.0/",
expectedOutput: map[string]string{"IMAGE": "nginx", "VERSION": "0.1.0"},
},
{
name: "marker with different values",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGE:VERSION/IMAGE",
fieldValue: "something/nginx:0.1.0/ubuntu",
expectedError: errors.Errorf("marker IMAGE is found to have different values nginx and ubuntu"),
},
{
name: "unmatched pattern",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGE:VERSION",
fieldValue: "otherthing/nginx:0.1.0",
expectedError: errors.Errorf("unable to derive values for markers"),
},
{
name: "unmatched pattern at the end",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGE:VERSION/abc",
fieldValue: "something/nginx:0.1.0/abcd",
expectedError: errors.Errorf("unable to derive values for markers"),
},
{
name: "substring markers",
markers: []string{"IMAGE", "VERSION", "MAGE"},
pattern: "something/IMAGE:VERSION/abc/MAGE",
fieldValue: "something/nginx:0.1.0/abc/ubuntu",
expectedError: errors.Errorf("no marker should be substring of other"),
},
{
name: "markers with no delimiters",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGEVERSION/",
fieldValue: "something/nginx0.1.0/",
expectedError: errors.Errorf("no delimiters between them"),
},
{
name: "unmatched delimiter",
markers: []string{"IMAGE", "VERSION"},
pattern: "something/IMAGE:^VERSION/otherthing/IMAGE::VERSION/",
fieldValue: "something/nginx::0.1.0/otherthing/nginx::0.1.0/",
expectedError: errors.Errorf("unable to derive values for markers"),
},
}
for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
values := []setters2.Value{}
for _, marker := range test.markers {
value := setters2.Value{
Marker: marker,
}
values = append(values, value)
}
sc := SubstitutionCreator{
Pattern: test.pattern,
Values: values,
FieldValue: test.fieldValue,
}
m, err := sc.GetValuesForMarkers()
if test.expectedError == nil {
// fail if expectedError is nil but actual error is not
if !assert.NoError(t, err) {
t.FailNow()
}
// check if all the expected markers and values are present in actual map
for k, v := range test.expectedOutput {
if val, ok := m[k]; ok {
assert.Equal(t, v, val)
} else {
t.FailNow()
}
}
} else {
//if expectedError is not nil, check for correctness of error message
assert.Contains(t, err.Error(), test.expectedError.Error())
}
})
}
}

88
kyaml/setters2/types.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"encoding/json"
"github.com/go-openapi/spec"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type cliExtension struct {
Setter *setter `yaml:"setter,omitempty" json:"setter,omitempty"`
Substitution *substitution `yaml:"substitution,omitempty" json:"substitution,omitempty"`
}
type setter struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Value string `yaml:"value,omitempty" json:"value,omitempty"`
EnumValues map[string]string `yaml:"enumValues,omitempty" json:"enumValues,omitempty"`
}
type substitution struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Pattern string `yaml:"pattern,omitempty" json:"pattern,omitempty"`
Values []substitutionSetterReference `yaml:"values,omitempty" json:"values,omitempty"`
}
type substitutionSetterReference struct {
Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
Marker string `yaml:"marker,omitempty" json:"marker,omitempty"`
}
//K8sCliExtensionKey is the name of the OpenAPI field containing the setter extensions
const K8sCliExtensionKey = "x-k8s-cli"
// getExtFromSchema returns the cliExtension openAPI extension if it is present in schema
func getExtFromSchema(schema *spec.Schema) (*cliExtension, error) {
cep := schema.VendorExtensible.Extensions[K8sCliExtensionKey]
if cep == nil {
return nil, nil
}
b, err := json.Marshal(cep)
if err != nil {
return nil, err
}
val := &cliExtension{}
if err := json.Unmarshal(b, val); err != nil {
return nil, err
}
return val, nil
}
// getExtFromComment returns the cliExtension openAPI extension if it is present as
// a comment on the field.
func getExtFromComment(object *yaml.RNode) (*cliExtension, *spec.Schema, error) {
// TODO(pwittrock): also use path to the field to get openapi, not just comments
// parse comment containing the extended openapi for this field
fm := fieldmeta.FieldMeta{}
if err := fm.Read(object); err != nil {
return nil, nil, errors.Wrap(err)
}
if fm.Schema.Ref.String() == "" {
return nil, nil, nil
}
// resolve the comment reference to the extended openapi definitions
r, err := openapi.Resolve(&fm.Schema.Ref)
if err != nil {
return nil, nil, errors.Wrap(err)
}
if r == nil {
// no schema found
// TODO(pwittrock): should this be an error if it doesn't resolve?
return nil, nil, nil
}
// get the cli extension from the openapi (contains setter information)
ext, err := getExtFromSchema(r)
if err != nil {
return nil, nil, errors.Wrap(err)
}
return ext, r, nil
}

45
kyaml/setters2/walk.go Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// visitor is implemented by structs which need to walk the configuration.
// visitor is provided to accept to walk configuration
type visitor interface {
// visitScalar is called for each scalar field value on a resource
// node is the scalar field value
// path is the path to the field; path elements are separated by '.'
visitScalar(node *yaml.RNode, path string) error
}
// accept invokes the appropriate function on v for each field in object
func accept(v visitor, object *yaml.RNode) error {
return acceptImpl(v, object, "")
}
// acceptImpl implements accept using recursion
func acceptImpl(v visitor, object *yaml.RNode, p string) error {
switch object.YNode().Kind {
case yaml.DocumentNode:
// Traverse the child of the document
return accept(v, yaml.NewRNode(object.YNode()))
case yaml.MappingNode:
return object.VisitFields(func(node *yaml.MapNode) error {
// Traverse each field value
return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value)
})
case yaml.SequenceNode:
return object.VisitElements(func(node *yaml.RNode) error {
// Traverse each list element
return acceptImpl(v, node, p)
})
case yaml.ScalarNode:
// Visit the scalar field
return v.visitScalar(object, p)
}
return nil
}

View File

@@ -6,6 +6,7 @@ package yaml
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
"gopkg.in/yaml.v3"
@@ -99,6 +100,42 @@ func Parse(value string) (*RNode, error) {
return Parser{Value: value}.Filter(nil)
}
// ReadFile parses a single Resource from a yaml file
func ReadFile(path string) (*RNode, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return Parse(string(b))
}
// WriteFile writes a single Resource to a yaml file
func WriteFile(node *RNode, path string) error {
out, err := node.String()
if err != nil {
return err
}
return ioutil.WriteFile(path, []byte(out), 0600)
}
// UpdateFile reads the file at path, applies the filter to it, and write the result back.
// path must contain a exactly 1 resource (YAML).
func UpdateFile(filter Filter, path string) error {
// Read the yaml
y, err := ReadFile(path)
if err != nil {
return err
}
// Update the yaml
if err := y.PipeE(filter); err != nil {
return err
}
// Write the yaml
return WriteFile(y, path)
}
// TODO(pwittrock): test this
func GetStyle(styles ...string) Style {
var style Style

View File

@@ -6,7 +6,7 @@
# kyaml version
export kyaml_major=0
export kyaml_minor=0
export kyaml_patch=10
export kyaml_patch=13
# kstatus version
export kstatus_major=0
@@ -21,7 +21,7 @@ export api_patch=2
# cmd/config version
export cmd_config_major=0
export cmd_config_minor=0
export cmd_config_patch=10
export cmd_config_patch=13
# cmd/kubectl version
export cmd_kubectl_major=0