diff --git a/e2e/testdata/fn-eval/out-of-place-dir/.expected/diff.patch b/e2e/testdata/fn-eval/out-of-place-dir/.expected/diff.patch new file mode 100644 index 0000000000..e27541d57d --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-dir/.expected/diff.patch @@ -0,0 +1,34 @@ +diff --git a/out/resources.yaml b/out/resources.yaml +new file mode 100644 +index 0000000..254b9cd +--- /dev/null ++++ b/out/resources.yaml +@@ -0,0 +1,28 @@ ++# Copyright 2021 Google LLC ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++apiVersion: apps/v1 ++kind: Deployment ++metadata: ++ name: nginx-deployment ++ namespace: staging ++spec: ++ replicas: 3 ++--- ++apiVersion: custom.io/v1 ++kind: Custom ++metadata: ++ name: custom ++ namespace: staging ++spec: ++ image: nginx:1.2.3 \ No newline at end of file diff --git a/e2e/testdata/fn-eval/out-of-place-dir/.expected/exec.sh b/e2e/testdata/fn-eval/out-of-place-dir/.expected/exec.sh new file mode 100644 index 0000000000..3182035ccf --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-dir/.expected/exec.sh @@ -0,0 +1,18 @@ +#! /bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1.3 -o out -- namespace=staging diff --git a/e2e/testdata/fn-eval/out-of-place-dir/.krmignore b/e2e/testdata/fn-eval/out-of-place-dir/.krmignore new file mode 100644 index 0000000000..1f57855a2f --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-dir/.krmignore @@ -0,0 +1,2 @@ +.expected +out diff --git a/e2e/testdata/fn-eval/out-of-place/resources.yaml b/e2e/testdata/fn-eval/out-of-place-dir/resources.yaml similarity index 100% rename from e2e/testdata/fn-eval/out-of-place/resources.yaml rename to e2e/testdata/fn-eval/out-of-place-dir/resources.yaml diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/config.yaml b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/config.yaml new file mode 100644 index 0000000000..8d4bca56d2 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/config.yaml @@ -0,0 +1,52 @@ +stdOut: | + apiVersion: config.kubernetes.io/v1alpha1 + kind: ResourceList + items: + # Copyright 2021 Google LLC + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "0" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + replicas: 3 + selector: + matchLabels: + tier: backend + template: + metadata: + labels: + tier: backend + annotations: + foo: bar + - apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "1" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/exec.sh b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/exec.sh new file mode 100644 index 0000000000..1a9f09128c --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.expected/exec.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1.3 -o stdout -- namespace=staging \ +| kpt fn eval - --image gcr.io/kpt-fn/set-annotations:v0.1.3 -- foo=bar \ +| kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1.3 -- tier=backend diff --git a/e2e/testdata/fn-eval/out-of-place/.krmignore b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.krmignore similarity index 100% rename from e2e/testdata/fn-eval/out-of-place/.krmignore rename to e2e/testdata/fn-eval/out-of-place-fnchain-stdout/.krmignore diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/resources.yaml b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/resources.yaml new file mode 100644 index 0000000000..10cd18bf5c --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-stdout/resources.yaml @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/config.yaml b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/config.yaml new file mode 100644 index 0000000000..6b922b43d0 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/config.yaml @@ -0,0 +1,33 @@ +stdOut: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + labels: + tier: backend + annotations: + foo: bar + spec: + replicas: 3 + selector: + matchLabels: + tier: backend + template: + metadata: + labels: + tier: backend + annotations: + foo: bar + --- + apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + labels: + tier: backend + annotations: + foo: bar + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/exec.sh b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/exec.sh new file mode 100644 index 0000000000..7ca5394526 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.expected/exec.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1.3 -o stdout -- namespace=staging \ +| kpt fn eval - --image gcr.io/kpt-fn/set-annotations:v0.1.3 -- foo=bar \ +| kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1.3 -o unwrap -- tier=backend diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.krmignore b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/Kptfile b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/resources.yaml b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/resources.yaml new file mode 100644 index 0000000000..62f87440ab --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-fnchain-unwrap/resources.yaml @@ -0,0 +1,13 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/out-of-place-source-sink/.expected/diff.patch b/e2e/testdata/fn-eval/out-of-place-source-sink/.expected/diff.patch new file mode 100644 index 0000000000..e27541d57d --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-source-sink/.expected/diff.patch @@ -0,0 +1,34 @@ +diff --git a/out/resources.yaml b/out/resources.yaml +new file mode 100644 +index 0000000..254b9cd +--- /dev/null ++++ b/out/resources.yaml +@@ -0,0 +1,28 @@ ++# Copyright 2021 Google LLC ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++apiVersion: apps/v1 ++kind: Deployment ++metadata: ++ name: nginx-deployment ++ namespace: staging ++spec: ++ replicas: 3 ++--- ++apiVersion: custom.io/v1 ++kind: Custom ++metadata: ++ name: custom ++ namespace: staging ++spec: ++ image: nginx:1.2.3 \ No newline at end of file diff --git a/e2e/testdata/fn-eval/out-of-place/.expected/exec.sh b/e2e/testdata/fn-eval/out-of-place-source-sink/.expected/exec.sh similarity index 79% rename from e2e/testdata/fn-eval/out-of-place/.expected/exec.sh rename to e2e/testdata/fn-eval/out-of-place-source-sink/.expected/exec.sh index a1e340a833..3425bf1a82 100644 --- a/e2e/testdata/fn-eval/out-of-place/.expected/exec.sh +++ b/e2e/testdata/fn-eval/out-of-place-source-sink/.expected/exec.sh @@ -15,16 +15,8 @@ set -eo pipefail -# create a temporary directory -TEMP_DIR=$(mktemp -d) +rm -rf out; mkdir out kpt fn source \ | kpt fn eval - --image gcr.io/kpt-fn/set-namespace:v0.1.3 -- namespace=staging \ -| kpt fn sink $TEMP_DIR - -# copy back the resources -rm -r ./* -cp $TEMP_DIR/* . - -# remove temporary directory -rm -r $TEMP_DIR +| kpt fn sink out diff --git a/e2e/testdata/fn-eval/out-of-place-source-sink/.krmignore b/e2e/testdata/fn-eval/out-of-place-source-sink/.krmignore new file mode 100644 index 0000000000..1f57855a2f --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-source-sink/.krmignore @@ -0,0 +1,2 @@ +.expected +out diff --git a/e2e/testdata/fn-eval/out-of-place-source-sink/resources.yaml b/e2e/testdata/fn-eval/out-of-place-source-sink/resources.yaml new file mode 100644 index 0000000000..7a494c9ca7 --- /dev/null +++ b/e2e/testdata/fn-eval/out-of-place-source-sink/resources.yaml @@ -0,0 +1,26 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/out-of-place/.expected/diff.patch b/e2e/testdata/fn-eval/out-of-place/.expected/diff.patch deleted file mode 100644 index 63ccc2b389..0000000000 --- a/e2e/testdata/fn-eval/out-of-place/.expected/diff.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/resources.yaml b/resources.yaml -index 7a494c9..254b9cd 100644 ---- a/resources.yaml -+++ b/resources.yaml -@@ -15,6 +15,7 @@ apiVersion: apps/v1 - kind: Deployment - metadata: - name: nginx-deployment -+ namespace: staging - spec: - replicas: 3 - --- -@@ -22,5 +23,6 @@ apiVersion: custom.io/v1 - kind: Custom - metadata: - name: custom -+ namespace: staging - spec: - image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/output-to-stdout/.expected/config.yaml b/e2e/testdata/fn-eval/output-to-stdout/.expected/config.yaml index 1c59630d71..a83c39d5a0 100644 --- a/e2e/testdata/fn-eval/output-to-stdout/.expected/config.yaml +++ b/e2e/testdata/fn-eval/output-to-stdout/.expected/config.yaml @@ -13,35 +13,39 @@ # limitations under the License. stdOut: | - # Copyright 2021 Google LLC - # - # Licensed under the Apache License, Version 2.0 (the "License"); - # you may not use this file except in compliance with the License. - # You may obtain a copy of the License at - # - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, - # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - # See the License for the specific language governing permissions and - # limitations under the License. - apiVersion: apps/v1 - kind: Deployment - metadata: - name: nginx-deployment - namespace: staging - annotations: - config.kubernetes.io/path: resources.yaml - spec: - replicas: 3 - --- - apiVersion: custom.io/v1 - kind: Custom - metadata: - name: custom - namespace: staging - annotations: - config.kubernetes.io/path: resources.yaml - spec: - image: nginx:1.2.3 + apiVersion: config.kubernetes.io/v1alpha1 + kind: ResourceList + items: + - # Copyright 2021 Google LLC + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + annotations: + config.kubernetes.io/index: "0" + config.kubernetes.io/path: resources.yaml + spec: + replicas: 3 + - apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + annotations: + config.kubernetes.io/index: "1" + config.kubernetes.io/path: resources.yaml + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/diff.patch b/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/diff.patch new file mode 100644 index 0000000000..bb76ffe432 --- /dev/null +++ b/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/diff.patch @@ -0,0 +1,45 @@ +diff --git a/out/resources.yaml b/out/resources.yaml +new file mode 100644 +index 0000000..a9dd224 +--- /dev/null ++++ b/out/resources.yaml +@@ -0,0 +1,39 @@ ++# Copyright 2021 Google LLC ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++apiVersion: apps/v1 ++kind: Deployment ++metadata: ++ name: nginx-deployment ++ namespace: staging ++ labels: ++ tier: backend ++spec: ++ replicas: 3 ++ selector: ++ matchLabels: ++ tier: backend ++ template: ++ metadata: ++ labels: ++ tier: backend ++--- ++apiVersion: custom.io/v1 ++kind: Custom ++metadata: ++ name: custom ++ namespace: staging ++ labels: ++ tier: backend ++spec: ++ image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/exec.sh b/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/exec.sh new file mode 100644 index 0000000000..f01ec40f65 --- /dev/null +++ b/e2e/testdata/fn-render/basicpipeline-out-of-place/.expected/exec.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o out diff --git a/e2e/testdata/fn-render/basicpipeline-out-of-place/.krmignore b/e2e/testdata/fn-render/basicpipeline-out-of-place/.krmignore new file mode 100644 index 0000000000..1f57855a2f --- /dev/null +++ b/e2e/testdata/fn-render/basicpipeline-out-of-place/.krmignore @@ -0,0 +1,2 @@ +.expected +out diff --git a/e2e/testdata/fn-render/basicpipeline-out-of-place/Kptfile b/e2e/testdata/fn-render/basicpipeline-out-of-place/Kptfile new file mode 100644 index 0000000000..32bba40ae1 --- /dev/null +++ b/e2e/testdata/fn-render/basicpipeline-out-of-place/Kptfile @@ -0,0 +1,12 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging + - image: gcr.io/kpt-fn/set-labels:v0.1.4 + configMap: + tier: backend diff --git a/e2e/testdata/fn-render/basicpipeline-out-of-place/resources.yaml b/e2e/testdata/fn-render/basicpipeline-out-of-place/resources.yaml new file mode 100644 index 0000000000..7a494c9ca7 --- /dev/null +++ b/e2e/testdata/fn-render/basicpipeline-out-of-place/resources.yaml @@ -0,0 +1,26 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-dir/.expected/diff.patch b/e2e/testdata/fn-render/out-of-place-dir/.expected/diff.patch new file mode 100644 index 0000000000..e27541d57d --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-dir/.expected/diff.patch @@ -0,0 +1,34 @@ +diff --git a/out/resources.yaml b/out/resources.yaml +new file mode 100644 +index 0000000..254b9cd +--- /dev/null ++++ b/out/resources.yaml +@@ -0,0 +1,28 @@ ++# Copyright 2021 Google LLC ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++apiVersion: apps/v1 ++kind: Deployment ++metadata: ++ name: nginx-deployment ++ namespace: staging ++spec: ++ replicas: 3 ++--- ++apiVersion: custom.io/v1 ++kind: Custom ++metadata: ++ name: custom ++ namespace: staging ++spec: ++ image: nginx:1.2.3 \ No newline at end of file diff --git a/e2e/testdata/fn-render/out-of-place-dir/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-dir/.expected/exec.sh new file mode 100644 index 0000000000..f01ec40f65 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-dir/.expected/exec.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o out diff --git a/e2e/testdata/fn-render/out-of-place-dir/.krmignore b/e2e/testdata/fn-render/out-of-place-dir/.krmignore new file mode 100644 index 0000000000..1f57855a2f --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-dir/.krmignore @@ -0,0 +1,2 @@ +.expected +out diff --git a/e2e/testdata/fn-render/out-of-place-dir/Kptfile b/e2e/testdata/fn-render/out-of-place-dir/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-dir/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-dir/resources.yaml b/e2e/testdata/fn-render/out-of-place-dir/resources.yaml new file mode 100644 index 0000000000..10cd18bf5c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-dir/resources.yaml @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/config.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/config.yaml new file mode 100644 index 0000000000..8d4bca56d2 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/config.yaml @@ -0,0 +1,52 @@ +stdOut: | + apiVersion: config.kubernetes.io/v1alpha1 + kind: ResourceList + items: + # Copyright 2021 Google LLC + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "0" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + replicas: 3 + selector: + matchLabels: + tier: backend + template: + metadata: + labels: + tier: backend + annotations: + foo: bar + - apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "1" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/exec.sh new file mode 100644 index 0000000000..358a983c0e --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.expected/exec.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +set -eo pipefail + +# create a temporary directory for results +results=$(mktemp -d) + +kpt fn render -o stdout --results-dir $results \ +| kpt fn eval - --image gcr.io/kpt-fn/set-annotations:v0.1.3 --results-dir $results -- foo=bar \ +| kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1.3 --results-dir $results -- tier=backend + +# remove temporary directory +rm -r $results \ No newline at end of file diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.krmignore b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/Kptfile b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/resources.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/resources.yaml new file mode 100644 index 0000000000..10cd18bf5c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout-results/resources.yaml @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/config.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/config.yaml new file mode 100644 index 0000000000..8d4bca56d2 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/config.yaml @@ -0,0 +1,52 @@ +stdOut: | + apiVersion: config.kubernetes.io/v1alpha1 + kind: ResourceList + items: + # Copyright 2021 Google LLC + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "0" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + replicas: 3 + selector: + matchLabels: + tier: backend + template: + metadata: + labels: + tier: backend + annotations: + foo: bar + - apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + labels: + tier: backend + annotations: + config.kubernetes.io/index: "1" + config.kubernetes.io/path: resources.yaml + foo: bar + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/exec.sh new file mode 100644 index 0000000000..c444e2e3db --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.expected/exec.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o stdout \ +| kpt fn eval - --image gcr.io/kpt-fn/set-annotations:v0.1.3 -- foo=bar \ +| kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1.3 -- tier=backend diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.krmignore b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout/Kptfile b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-stdout/resources.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/resources.yaml new file mode 100644 index 0000000000..10cd18bf5c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-stdout/resources.yaml @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/config.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/config.yaml new file mode 100644 index 0000000000..6b922b43d0 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/config.yaml @@ -0,0 +1,33 @@ +stdOut: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + labels: + tier: backend + annotations: + foo: bar + spec: + replicas: 3 + selector: + matchLabels: + tier: backend + template: + metadata: + labels: + tier: backend + annotations: + foo: bar + --- + apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + labels: + tier: backend + annotations: + foo: bar + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/exec.sh new file mode 100644 index 0000000000..0bfe1376e5 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.expected/exec.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o stdout \ +| kpt fn eval - --image gcr.io/kpt-fn/set-annotations:v0.1.3 -- foo=bar \ +| kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1.3 -o unwrap -- tier=backend diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.krmignore b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/Kptfile b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/resources.yaml b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/resources.yaml new file mode 100644 index 0000000000..62f87440ab --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-fnchain-unwrap/resources.yaml @@ -0,0 +1,13 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-stdout/.expected/config.yaml b/e2e/testdata/fn-render/out-of-place-stdout/.expected/config.yaml new file mode 100644 index 0000000000..2309dc5d61 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-stdout/.expected/config.yaml @@ -0,0 +1,37 @@ +stdOut: | + apiVersion: config.kubernetes.io/v1alpha1 + kind: ResourceList + items: + # Copyright 2021 Google LLC + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + annotations: + config.kubernetes.io/index: "0" + config.kubernetes.io/path: resources.yaml + spec: + replicas: 3 + - apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + annotations: + config.kubernetes.io/index: "1" + config.kubernetes.io/path: resources.yaml + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-stdout/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-stdout/.expected/exec.sh new file mode 100644 index 0000000000..d5270f000c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-stdout/.expected/exec.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o stdout diff --git a/e2e/testdata/fn-render/out-of-place-stdout/.krmignore b/e2e/testdata/fn-render/out-of-place-stdout/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-stdout/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/out-of-place-stdout/Kptfile b/e2e/testdata/fn-render/out-of-place-stdout/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-stdout/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-stdout/resources.yaml b/e2e/testdata/fn-render/out-of-place-stdout/resources.yaml new file mode 100644 index 0000000000..10cd18bf5c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-stdout/resources.yaml @@ -0,0 +1,27 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-unwrap/.expected/config.yaml b/e2e/testdata/fn-render/out-of-place-unwrap/.expected/config.yaml new file mode 100644 index 0000000000..c47d24e265 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-unwrap/.expected/config.yaml @@ -0,0 +1,16 @@ +stdOut: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + namespace: staging + spec: + replicas: 3 + --- + apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom + namespace: staging + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/out-of-place-unwrap/.expected/exec.sh b/e2e/testdata/fn-render/out-of-place-unwrap/.expected/exec.sh new file mode 100644 index 0000000000..cb0d6daf0c --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-unwrap/.expected/exec.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +set -eo pipefail + +kpt fn render -o unwrap diff --git a/e2e/testdata/fn-render/out-of-place-unwrap/.krmignore b/e2e/testdata/fn-render/out-of-place-unwrap/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-unwrap/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/out-of-place-unwrap/Kptfile b/e2e/testdata/fn-render/out-of-place-unwrap/Kptfile new file mode 100644 index 0000000000..2d1b28a802 --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-unwrap/Kptfile @@ -0,0 +1,9 @@ +apiVersion: kpt.dev/v1alpha2 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging diff --git a/e2e/testdata/fn-render/out-of-place-unwrap/resources.yaml b/e2e/testdata/fn-render/out-of-place-unwrap/resources.yaml new file mode 100644 index 0000000000..62f87440ab --- /dev/null +++ b/e2e/testdata/fn-render/out-of-place-unwrap/resources.yaml @@ -0,0 +1,13 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/internal/cmdrender/cmd.go b/internal/cmdrender/cmd.go index 20f8b7ea91..1ac0c52491 100644 --- a/internal/cmdrender/cmd.go +++ b/internal/cmdrender/cmd.go @@ -16,8 +16,10 @@ package cmdrender import ( + "bytes" "context" "fmt" + "io" "os" docs "github.com/GoogleContainerTools/kpt/internal/docs/generated/fndocs" @@ -41,6 +43,8 @@ func NewRunner(ctx context.Context, parent string) *Runner { } c.Flags().StringVar(&r.resultsDirPath, "results-dir", "", "path to a directory to save function results") + c.Flags().StringVarP(&r.dest, "output", "o", "", + fmt.Sprintf("output resources are written to provided location. Allowed values: %s|%s|", cmdutil.Stdout, cmdutil.Unwrap)) c.Flags().StringVar(&r.imagePullPolicy, "image-pull-policy", "always", "pull image before running the container. It should be one of always, ifNotPresent and never.") cmdutil.FixDocs("kpt", parent, c) @@ -57,6 +61,7 @@ type Runner struct { pkgPath string resultsDirPath string imagePullPolicy string + dest string Command *cobra.Command ctx context.Context } @@ -89,14 +94,31 @@ func (r *Runner) runE(c *cobra.Command, _ []string) error { if err != nil { return err } + var output io.Writer + outContent := bytes.Buffer{} + if r.dest != "" { + // this means the output should be written to another destination + // capture the content to be written + output = &outContent + } executor := Executor{ PkgPath: r.pkgPath, ResultsDirPath: r.resultsDirPath, + Output: output, ImagePullPolicy: cmdutil.StringToImagePullPolicy(r.imagePullPolicy), } - return executor.Execute(r.ctx) + err = executor.Execute(r.ctx) + if err != nil { + return err + } + + return cmdutil.WriteFnOutput(r.dest, outContent.String(), false, c.OutOrStdout()) } func (r *Runner) postRun(_ *cobra.Command, _ []string) { + if r.dest != "" { + // do not format/modify resources in package if output should be written to other dest + return + } pkgutil.FormatPackage(r.pkgPath) } diff --git a/internal/cmdrender/executor.go b/internal/cmdrender/executor.go index 966beae926..a79ce01bad 100644 --- a/internal/cmdrender/executor.go +++ b/internal/cmdrender/executor.go @@ -17,6 +17,7 @@ package cmdrender import ( "context" "fmt" + "io" "os" "path" "path/filepath" @@ -41,6 +42,7 @@ import ( type Executor struct { PkgPath string ResultsDirPath string + Output io.Writer ImagePullPolicy fnruntime.ImagePullPolicy Printer printer.Printer @@ -49,6 +51,11 @@ type Executor struct { // Execute runs a pipeline. func (e *Executor) Execute(ctx context.Context) error { const op errors.Op = "fn.render" + disableCLIOutput := false + if e.Output != nil { + // disable output written to stdout in case of out-of-place hydration + disableCLIOutput = true + } pr := printer.FromContextOrDie(ctx) @@ -59,17 +66,19 @@ func (e *Executor) Execute(ctx context.Context) error { // initialize hydration context hctx := &hydrationContext{ - root: root, - pkgs: map[types.UniquePath]*pkgNode{}, - fnResults: fnresult.NewResultList(), - imagePullPolicy: e.ImagePullPolicy, + root: root, + pkgs: map[types.UniquePath]*pkgNode{}, + fnResults: fnresult.NewResultList(), + imagePullPolicy: e.ImagePullPolicy, + disableCLIOutput: disableCLIOutput, } resources, err := hydrate(ctx, root, hctx) if err != nil { // Note(droot): ignore the error in function result saving // to avoid masking the hydration error. - _ = e.saveFnResults(ctx, hctx.fnResults, true) + // don't disable the CLI output in case of error + _ = e.saveFnResults(ctx, hctx.fnResults, true, false) return errors.E(op, root.pkg.UniquePath, err) } @@ -77,34 +86,52 @@ func (e *Executor) Execute(ctx context.Context) error { return err } - pkgWriter := &kio.LocalPackageReadWriter{PackagePath: string(root.pkg.UniquePath)} - // format resources before writing _, err = filters.FormatFilter{UseSchema: true}.Filter(resources) if err != nil { return err } - err = pkgWriter.Write(resources) - if err != nil { - return fmt.Errorf("failed to save resources: %w", err) - } + if e.Output == nil { + // the intent of the user is to modify resources in-place + pkgWriter := &kio.LocalPackageReadWriter{PackagePath: string(root.pkg.UniquePath)} + err = pkgWriter.Write(resources) + if err != nil { + return fmt.Errorf("failed to save resources: %w", err) + } - if err = pruneResources(hctx); err != nil { - return err + if err = pruneResources(hctx); err != nil { + return err + } + pr.Printf("Successfully executed %d function(s) in %d package(s).\n", hctx.executedFunctionCnt, len(hctx.pkgs)) + } else { + // the intent of the user is to write the resources to either stdout|unwrapped| + // so, write the resources to provided e.Output which will be written to appropriate destination by cobra layer + writer := &kio.ByteWriter{ + Writer: e.Output, + KeepReaderAnnotations: true, + WrappingAPIVersion: kio.ResourceListAPIVersion, + WrappingKind: kio.ResourceListKind, + } + err = writer.Write(resources) + if err != nil { + return fmt.Errorf("failed to write resources: %w", err) + } } - pr.Printf("Successfully executed %d function(s) in %d package(s).\n", hctx.executedFunctionCnt, len(hctx.pkgs)) - - return e.saveFnResults(ctx, hctx.fnResults, false) + return e.saveFnResults(ctx, hctx.fnResults, false, disableCLIOutput) } -func (e *Executor) saveFnResults(ctx context.Context, fnResults *fnresult.ResultList, toStdErr bool) error { +func (e *Executor) saveFnResults(ctx context.Context, fnResults *fnresult.ResultList, toStdErr, disableCLIOutput bool) error { resultsFile, err := fnruntime.SaveResults(e.ResultsDirPath, fnResults) if err != nil { return fmt.Errorf("failed to save function results: %w", err) } + if disableCLIOutput { + return nil + } + printerutil.PrintFnResultInfo(ctx, resultsFile, false, toStdErr) return nil } @@ -138,6 +165,10 @@ type hydrationContext struct { // imagePullPolicy controls the image pulling behavior. imagePullPolicy fnruntime.ImagePullPolicy + + // disableCLIOutput disables the cli output written to stdout + // stderr is not affected by this + disableCLIOutput bool } // @@ -299,7 +330,9 @@ func (pn *pkgNode) runPipeline(ctx context.Context, hctx *hydrationContext, inpu // TODO: the DisplayPath is a relative file path. It cannot represent the // package structure. We should have function to get the relative package // path here. - pr.OptPrintf(printer.NewOpt().PkgDisplay(pn.pkg.DisplayPath), "\n") + if !hctx.disableCLIOutput { + pr.OptPrintf(printer.NewOpt().PkgDisplay(pn.pkg.DisplayPath), "\n") + } if len(input) == 0 { return nil, nil @@ -328,7 +361,9 @@ func (pn *pkgNode) runPipeline(ctx context.Context, hctx *hydrationContext, inpu return nil, errors.E(op, pn.pkg.UniquePath, err) } // print a new line after a pipeline running - pr.Printf("\n") + if !hctx.disableCLIOutput { + pr.Printf("\n") + } return mutatedResources, nil } @@ -389,7 +424,7 @@ func (pn *pkgNode) runValidators(ctx context.Context, hctx *hydrationContext, in for i := range pl.Validators { fn := pl.Validators[i] - validator, err := fnruntime.NewContainerRunner(ctx, &fn, pn.pkg.UniquePath, hctx.fnResults, hctx.imagePullPolicy) + validator, err := fnruntime.NewContainerRunner(ctx, &fn, pn.pkg.UniquePath, hctx.fnResults, hctx.imagePullPolicy, hctx.disableCLIOutput) if err != nil { return err } @@ -443,7 +478,7 @@ func fnChain(ctx context.Context, hctx *hydrationContext, pkgPath types.UniquePa var runners []kio.Filter for i := range fns { fn := fns[i] - r, err := fnruntime.NewContainerRunner(ctx, &fn, pkgPath, hctx.fnResults, hctx.imagePullPolicy) + r, err := fnruntime.NewContainerRunner(ctx, &fn, pkgPath, hctx.fnResults, hctx.imagePullPolicy, hctx.disableCLIOutput) if err != nil { return nil, err } diff --git a/internal/docs/generated/fndocs/docs.go b/internal/docs/generated/fndocs/docs.go index 8bb54fdae6..2f627444b2 100644 --- a/internal/docs/generated/fndocs/docs.go +++ b/internal/docs/generated/fndocs/docs.go @@ -37,9 +37,9 @@ Args: 1. Multi object YAML where resources are separated by ` + "`" + `---` + "`" + `. - 2. ` + "`" + `Function Specification` + "`" + ` wire format where resources are wrapped in an object + 2. KRM Function Specification wire format where resources are wrapped in an object of kind ResourceList. - + If the output is written to ` + "`" + `stdout` + "`" + `, resources are written in multi object YAML format where resources are separated by ` + "`" + `---` + "`" + `. @@ -57,7 +57,7 @@ Flags: --dry-run: If enabled, the resources are not written to local filesystem, instead they are written to stdout. By defaults it is disabled. - + --env, e: List of local environment variables to be exported to the container function. By default, none of local environment variables are made available to the @@ -69,7 +69,7 @@ Flags: only one function, so do not use ` + "`" + `--image` + "`" + ` flag with this flag. This is useful for testing function locally during development. It enables faster dev iterations by avoiding the function to be published as container image. - + --fn-config: Path to the file containing ` + "`" + `functionConfig` + "`" + ` for the function. @@ -101,6 +101,14 @@ Flags: If enabled, container functions are allowed to access network. By default it is disabled. + --output, o: + If specified, the output resources are written to provided location, + if not specified, resources are modified in-place. + Allowed values: stdout|unwrap| + 1. stdout: output resources are wrapped in ResourceList and written to stdout. + 2. unwrap: output resources are written to stdout, in multi-object yaml format. + 3. OUT_DIR_PATH: output resources are written to provided directory, the directory is created if it doesn't already exist. + --results-dir: Path to a directory to write structured results. Directory must exist. Structured results emitted by the functions are aggregated and saved @@ -110,14 +118,14 @@ Flags: var EvalExamples = ` # execute container my-fn on the resources in DIR directory and # write output back to DIR - $ kpt fn eval DIR --image gcr.io/example.com/my-fn + $ kpt fn eval DIR -i gcr.io/example.com/my-fn # execute container my-fn on the resources in DIR directory with # ` + "`" + `functionConfig` + "`" + ` my-fn-config - $ kpt fn eval DIR --image gcr.io/example.com/my-fn --fn-config my-fn-config + $ kpt fn eval DIR -i gcr.io/example.com/my-fn --fn-config my-fn-config # execute container my-fn with an input ConfigMap containing ` + "`" + `data: {foo: bar}` + "`" + ` - $ kpt fn eval DIR --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar + $ kpt fn eval DIR -i gcr.io/example.com/my-fn:v1.0.0 -- foo=bar # execute executable my-fn on the resources in DIR directory and # write output back to DIR @@ -125,27 +133,42 @@ var EvalExamples = ` # execute container my-fn on the resources in DIR directory, # save structured results in /tmp/my-results dir and write output back to DIR - $ kpt fn eval DIR --image gcr.io/example.com/my-fn --results-dir /tmp/my-results-dir + $ kpt fn eval DIR -i gcr.io/example.com/my-fn --results-dir /tmp/my-results-dir # execute container my-fn on the resources in DIR directory with network access enabled, # and write output back to DIR - $ kpt fn eval DIR --image gcr.io/example.com/my-fn --network + $ kpt fn eval DIR -i gcr.io/example.com/my-fn --network # execute container my-fn on the resource in DIR and export KUBECONFIG # and foo environment variable - $ kpt fn eval DIR --image gcr.io/example.com/my-fn --env KUBECONFIG -e foo=bar + $ kpt fn eval DIR -i gcr.io/example.com/my-fn --env KUBECONFIG -e foo=bar # execute kubeval function by mounting schema from a local directory on wordpress package - $ kpt fn eval --image gcr.io/kpt-fn/kubeval:v0.1 \ + $ kpt fn eval -i gcr.io/kpt-fn/kubeval:v0.1 \ --mount type=bind,src="/path/to/schema-dir",dst=/schema-dir \ --as-current-user wordpress -- additional_schema_locations=/schema-dir # chaining functions using the unix pipe to set namespace and set labels on # wordpress package $ kpt fn source wordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1 - -- label_name=color label_value=orange \ + | kpt fn eval - -i gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ + | kpt fn eval - -i gcr.io/kpt-fn/set-labels:v0.1 - -- label_name=color label_value=orange \ | kpt fn sink wordpress + + # execute container 'set-namespace' on the resources in current directory and write + # the output resources to another directory + $ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o path/to/dir -- namespace=mywordpress + + # execute container 'set-namespace' on the resources in current directory and write + # the output resources to stdout which are piped to 'kubectl apply' + $ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o unwrap -- namespace=mywordpress \ + | kubectl apply - + + # execute container 'set-namespace' on the resources in current directory and write + # the wrapped output resources to stdout which are passed to 'set-annotations' function + # and the output resources after setting namespace and annotation is written to another directory + $ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o stdout \ + | kpt fn eval - -i gcr.io/kpt-fn/set-annotations:v0.1.3 -o path/to/dir -- foo=bar ` var ExportShort = `Auto-generating function pipelines for different workflow orchestrators` @@ -195,6 +218,14 @@ Flags: to one of always, ifNotPresent, never. If unspecified, always will be the default. + --output, o: + If specified, the output resources are written to provided location, + if not specified, resources are modified in-place. + Allowed values: stdout|unwrap| + 1. stdout: output resources are wrapped in ResourceList and written to stdout. + 2. unwrap: output resources are written to stdout, in multi-object yaml format. + 3. OUT_DIR_PATH: output resources are written to provided directory, the directory is created if it doesn't already exist. + --results-dir: Path to a directory to write structured results. Directory must exist. Structured results emitted by the functions are aggregated and saved @@ -206,10 +237,23 @@ var RenderExamples = ` $ kpt fn render # Render the package in current directory and save results in my-results-dir - $ kpt fn render --results-dir my-results-dir + $ kpt fn render --results-dir my-results-dir # Render my-package-dir $ kpt fn render my-package-dir + + # Render the package in current directory and write output resources to another DIR + $ kpt fn render -o path/to/dir + + # Render resources in current directory and write unwrapped resources to stdout + # which can be piped to kubectl apply + $ kpt fn render -o unwrap | kubectl apply - + + # Render resources in current directory, write the wrapped resources + # to stdout which are piped to 'set-annotations' function, + # the transformed resources are written to another directory + $ kpt fn render -o stdout \ + | kpt fn eval - -i gcr.io/kpt-fn/set-annotations:v0.1.3 -o path/to/dir -- foo=bar ` var SinkShort = `Write resources to a local directory` @@ -223,7 +267,7 @@ var SinkExamples = ` # read resources from DIR directory, execute my-fn on them and write the # output to DIR directory. $ kpt fn source DIR | - kpt fn eval --image gcr.io/example.com/my-fn - | + kpt fn eval - --image gcr.io/example.com/my-fn - | kpt fn sink DIR ` @@ -250,6 +294,6 @@ var SourceExamples = ` # read resources from DIR directory, execute my-fn on them and write the # output to DIR directory. $ kpt fn source DIR | - kpt fn eval --image gcr.io/example.com/my-fn - | + kpt fn eval - --image gcr.io/example.com/my-fn - | kpt fn sink DIR ` diff --git a/internal/docs/generated/livedocs/docs.go b/internal/docs/generated/livedocs/docs.go index fb81188f28..a5542a9729 100644 --- a/internal/docs/generated/livedocs/docs.go +++ b/internal/docs/generated/livedocs/docs.go @@ -217,7 +217,7 @@ Flags: --output: Determines the output format for the status information. Must be one of the following: - + * events: The output will be a list of the status events as they become available. * json: The output will be a list of the status events as they become available, each formatted as a json object. @@ -225,20 +225,20 @@ Flags: as the status of resources become available. The default value is ‘events’. - + --poll-period: The frequency with which the cluster will be polled to determine the status of the applied resources. The default value is 2 seconds. --poll-until: When to stop polling for status and exist. Must be one of the following: - + * known: Exit when the status for all resources have been found. * current: Exit when the status for all resources have reached the Current status. * deleted: Exit when the status for all resources have reached the NotFound status, i.e. all the resources have been deleted from the live state. * forever: Keep polling for status until interrupted. - + The default value is ‘known’. --timeout: diff --git a/internal/docs/generated/overview/docs.go b/internal/docs/generated/overview/docs.go index fe8c9fb5a3..2c045b2be4 100644 --- a/internal/docs/generated/overview/docs.go +++ b/internal/docs/generated/overview/docs.go @@ -1,17 +1,17 @@ // Code generated by "mdtogo"; DO NOT EDIT. package overview -var ReferenceShort = `Overview of kpt commands` -var ReferenceLong = ` +var CliShort = `Overview of kpt commands` +var CliLong = ` All kpt commands follow this general synopsis: kpt [PKG_PATH] [flags] kpt functionality is divided into three command groups: -| Group | Description | -| --------| ------------------------------------------------------------------------| -| pkg | get, update, and describe packages with resources. | -| fn | generate, transform, validate packages using containerized functions. | -| live | deploy local configuration packages to a cluster. | +| Group | Description | +| ------ | --------------------------------------------------------------------- | +| [pkg] | get, update, and describe packages with resources. | +| [fn] | generate, transform, validate packages using containerized functions. | +| [live] | deploy local configuration packages to a cluster. | ` diff --git a/internal/fnruntime/runner.go b/internal/fnruntime/runner.go index 9f164ee3e6..ed1bf2ca1b 100644 --- a/internal/fnruntime/runner.go +++ b/internal/fnruntime/runner.go @@ -40,7 +40,7 @@ import ( func NewContainerRunner( ctx context.Context, f *kptfilev1alpha2.Function, pkgPath types.UniquePath, fnResults *fnresult.ResultList, - imagePullPolicy ImagePullPolicy) (kio.Filter, error) { + imagePullPolicy ImagePullPolicy, disableCLIOutput bool) (kio.Filter, error) { config, err := newFnConfig(f, pkgPath) if err != nil { return nil, err @@ -61,14 +61,14 @@ func NewContainerRunner( // Enable this once test harness supports filepath based assertions. // Pkg: string(pkgPath), } - return NewFunctionRunner(ctx, fltr, false, fnResult, fnResults) + return NewFunctionRunner(ctx, fltr, disableCLIOutput, fnResult, fnResults) } // NewFunctionRunner returns a kio.Filter given a specification of a function // and it's config. func NewFunctionRunner(ctx context.Context, fltr *runtimeutil.FunctionFilter, - disableOutput bool, + disableCLIOutput bool, fnResult *fnresult.Result, fnResults *fnresult.ResultList) (kio.Filter, error) { name := fnResult.Image @@ -76,29 +76,29 @@ func NewFunctionRunner(ctx context.Context, name = fnResult.ExecPath } return &FunctionRunner{ - ctx: ctx, - name: name, - filter: fltr, - disableOutput: disableOutput, - fnResult: fnResult, - fnResults: fnResults, + ctx: ctx, + name: name, + filter: fltr, + disableCLIOutput: disableCLIOutput, + fnResult: fnResult, + fnResults: fnResults, }, nil } // FunctionRunner wraps FunctionFilter and implements kio.Filter interface. type FunctionRunner struct { - ctx context.Context - name string - disableOutput bool - filter *runtimeutil.FunctionFilter - fnResult *fnresult.Result - fnResults *fnresult.ResultList + ctx context.Context + name string + disableCLIOutput bool + filter *runtimeutil.FunctionFilter + fnResult *fnresult.Result + fnResults *fnresult.ResultList } func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err error) { pr := printer.FromContextOrDie(fr.ctx) - if !fr.disableOutput { + if !fr.disableCLIOutput { pr.Printf("[RUNNING] %q\n", fr.name) } output, err = fr.do(input) @@ -113,7 +113,7 @@ func (fr *FunctionRunner) Filter(input []*yaml.RNode) (output []*yaml.RNode, err } return nil, err } - if !fr.disableOutput { + if !fr.disableCLIOutput { pr.Printf("[PASS] %q\n", fr.name) printFnResult(fr.ctx, fr.fnResult, printer.NewOpt()) } diff --git a/internal/util/cmdutil/cmdutil.go b/internal/util/cmdutil/cmdutil.go index aedb57d07c..65b8f4c2be 100644 --- a/internal/util/cmdutil/cmdutil.go +++ b/internal/util/cmdutil/cmdutil.go @@ -17,6 +17,7 @@ package cmdutil import ( "bytes" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -25,11 +26,15 @@ import ( "github.com/GoogleContainerTools/kpt/internal/errors" "github.com/GoogleContainerTools/kpt/internal/fnruntime" "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/kio/kioutil" ) const ( StackTraceOnErrors = "COBRA_STACK_TRACE_ON_ERRORS" trueString = "true" + Stdout = "stdout" + Unwrap = "unwrap" ) // FixDocs replaces instances of old with new in the docs for c @@ -113,3 +118,49 @@ func StringToImagePullPolicy(v string) fnruntime.ImagePullPolicy { return fnruntime.AlwaysPull } } + +// WriteFnOutput writes the output resources of function commands to provided destination +func WriteFnOutput(dest, content string, fromStdin bool, w io.Writer) error { + r := strings.NewReader(content) + switch dest { + case Stdout: + // if user specified dest is "stdout" directly write the content as it is already wrapped + _, err := w.Write([]byte(content)) + return err + case Unwrap: + // if user specified dest is "unwrap", write the unwrapped content to the provided writer + return WriteToOutput(r, w, "") + case "": + if fromStdin { + // if user didn't specify dest, and if input is from STDIN, write the wrapped content provided writer + // this is same as "stdout" input above + _, err := w.Write([]byte(content)) + return err + } + default: + // this means user specified a directory as dest, write the content to dest directory + return WriteToOutput(r, nil, dest) + } + return nil +} + +// WriteToOutput reads the input from r and writes the output to either w or outDir +func WriteToOutput(r io.Reader, w io.Writer, outDir string) error { + var outputs []kio.Writer + if outDir != "" { + err := os.MkdirAll(outDir, 0700) + if err != nil { + return fmt.Errorf("failed to create output directory %q: %q", outDir, err.Error()) + } + outputs = []kio.Writer{&kio.LocalPackageWriter{PackagePath: outDir}} + } else { + outputs = []kio.Writer{&kio.ByteWriter{ + Writer: w, + ClearAnnotations: []string{kioutil.IndexAnnotation, kioutil.PathAnnotation}}, + } + } + + return kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: r}}, + Outputs: outputs}.Execute() +} diff --git a/internal/util/cmdutil/cmdutil_test.go b/internal/util/cmdutil/cmdutil_test.go new file mode 100644 index 0000000000..14e6542c98 --- /dev/null +++ b/internal/util/cmdutil/cmdutil_test.go @@ -0,0 +1,292 @@ +package cmdutil + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/kio" +) + +func TestWriteFnOutput(t *testing.T) { + var tests = []struct { + name string + dest string + content string + fromStdin bool + writer bytes.Buffer + expectedStdout string + expectedPkg string + }{ + { + name: "wrapped output to stdout", + dest: "stdout", + writer: bytes.Buffer{}, + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedStdout: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + }, + { + name: "unwrapped output to stdout", + dest: "unwrap", + writer: bytes.Buffer{}, + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedStdout: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-svc +`, + }, + { + name: "output to another directory", + dest: "foo/bar", + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedPkg: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/path: 'foo/bar/deployment.yaml' +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-svc + annotations: + config.kubernetes.io/path: 'foo/bar/svc.yaml' +`, + }, + { + name: "wrapped output to stdout by default if input is from stdin", + fromStdin: true, + writer: bytes.Buffer{}, + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedStdout: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + }, + { + name: "unwrapped output to stdout for input from stdin", + fromStdin: true, + dest: "unwrap", + writer: bytes.Buffer{}, + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedStdout: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-svc +`, + }, + { + name: "output to directory for input from stdin", + fromStdin: true, + dest: "foo/bar", + content: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'deployment.yaml' + - apiVersion: v1 + kind: Service + metadata: + name: nginx-svc + annotations: + config.kubernetes.io/index: '0' + config.kubernetes.io/path: 'svc.yaml' +`, + expectedPkg: `apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + annotations: + config.kubernetes.io/path: 'foo/bar/deployment.yaml' +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-svc + annotations: + config.kubernetes.io/path: 'foo/bar/svc.yaml' +`, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + baseDir, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } + defer os.RemoveAll(baseDir) + + if test.dest != "" && test.dest != Stdout && test.dest != Unwrap { + test.dest = filepath.Join(baseDir, test.dest) + } + + // this method should create a directory and write the output if the dest is a directory path + err = WriteFnOutput(test.dest, test.content, test.fromStdin, &test.writer) + if !assert.NoError(t, err) { + t.FailNow() + } + + actualStdout := test.writer.String() + if !assert.Equal(t, test.expectedStdout, actualStdout) { + t.FailNow() + } + + // read the resources from output dir + in := &kio.LocalPackageReader{ + PackagePath: baseDir, + } + out := &bytes.Buffer{} + + err = kio.Pipeline{ + Inputs: []kio.Reader{in}, + Outputs: []kio.Writer{&kio.ByteWriter{Writer: out}}, + }.Execute() + + if !assert.NoError(t, err) { + t.FailNow() + } + + // verify that the resources in the output dir are as expected + if !assert.Equal(t, test.expectedPkg, out.String()) { + t.FailNow() + } + }) + } +} diff --git a/pkg/test/runner/runner.go b/pkg/test/runner/runner.go index d21c2e01c5..8082290c03 100644 --- a/pkg/test/runner/runner.go +++ b/pkg/test/runner/runner.go @@ -52,6 +52,7 @@ const ( expectedResultsFile string = "results.yaml" expectedDiffFile string = "diff.patch" expectedConfigFile string = "config.yaml" + outDir string = "out" setupScript string = "setup.sh" teardownScript string = "teardown.sh" execScript string = "exec.sh" @@ -137,7 +138,7 @@ func (r *Runner) runFnEval() error { defer os.RemoveAll(tmpDir) pkgPath := filepath.Join(tmpDir, r.pkgName) - var resultsDir string + var resultsDir, destDir string if r.IsFnResultExpected() { // create result dir @@ -148,6 +149,10 @@ func (r *Runner) runFnEval() error { } } + if r.IsOutOfPlace() { + destDir = filepath.Join(pkgPath, outDir) + } + // copy package to temp directory err = copyDir(r.testCase.Path, pkgPath) if err != nil { @@ -181,6 +186,9 @@ func (r *Runner) runFnEval() error { if resultsDir != "" { kptArgs = append(kptArgs, "--results-dir", resultsDir) } + if destDir != "" { + kptArgs = append(kptArgs, "-o", destDir) + } if r.testCase.Config.ImagePullPolicy != "" { kptArgs = append(kptArgs, "--image-pull-policy", string(r.testCase.Config.ImagePullPolicy)) } @@ -243,6 +251,12 @@ func (r *Runner) IsFnResultExpected() bool { return err == nil } +// IsOutOfPlace determines if command output is saved in a different directory (out-of-place). +func (r *Runner) IsOutOfPlace() bool { + _, err := ioutil.ReadDir(filepath.Join(r.testCase.Path, outDir)) + return err == nil +} + func (r *Runner) runFnRender() error { r.t.Logf("Running test against package %s\n", r.pkgName) tmpDir, err := ioutil.TempDir("", "kpt-pipeline-e2e-*") @@ -264,7 +278,7 @@ func (r *Runner) runFnRender() error { return fmt.Errorf("failed to create original dir %s: %w", origPkgPath, err) } - var resultsDir string + var resultsDir, destDir string if r.IsFnResultExpected() { // create result dir @@ -275,6 +289,10 @@ func (r *Runner) runFnRender() error { } } + if r.IsOutOfPlace() { + destDir = filepath.Join(pkgPath, outDir) + } + // copy package to temp directory err = copyDir(r.testCase.Path, pkgPath) if err != nil { @@ -314,6 +332,10 @@ func (r *Runner) runFnRender() error { kptArgs = append(kptArgs, "--results-dir", resultsDir) } + if destDir != "" { + kptArgs = append(kptArgs, "-o", destDir) + } + if r.testCase.Config.ImagePullPolicy != "" { kptArgs = append(kptArgs, "--image-pull-policy", string(r.testCase.Config.ImagePullPolicy)) } diff --git a/run/run.go b/run/run.go index 19a294a2e1..176a898220 100644 --- a/run/run.go +++ b/run/run.go @@ -39,8 +39,8 @@ func GetMain(ctx context.Context) *cobra.Command { installComp := false cmd := &cobra.Command{ Use: "kpt", - Short: overview.ReferenceShort, - Long: overview.ReferenceLong, + Short: overview.CliShort, + Long: overview.CliLong, SilenceUsage: true, // We handle all errors in main after return from cobra so we can // adjust the error message coming from libraries diff --git a/site/book/04-using-functions/02-imperative-function-execution.md b/site/book/04-using-functions/02-imperative-function-execution.md index d685d23cbf..519e37f9c2 100644 --- a/site/book/04-using-functions/02-imperative-function-execution.md +++ b/site/book/04-using-functions/02-imperative-function-execution.md @@ -156,8 +156,8 @@ Here is an example: ```shell $ kpt fn source wordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1 - -- app=wordpress env=prod \ + | kpt fn eval - --image gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ + | kpt fn eval - --image gcr.io/kpt-fn/set-labels:v0.1 - -- app=wordpress env=prod \ | kpt fn sink wordpress ``` diff --git a/site/reference/cli/fn/eval/README.md b/site/reference/cli/fn/eval/README.md index 5b31ab5400..fc6ad36e28 100644 --- a/site/reference/cli/fn/eval/README.md +++ b/site/reference/cli/fn/eval/README.md @@ -105,6 +105,14 @@ fn-args: If enabled, container functions are allowed to access network. By default it is disabled. +--output, o: + If specified, the output resources are written to provided location, + if not specified, resources are modified in-place. + Allowed values: stdout|unwrap| + 1. stdout: output resources are wrapped in ResourceList and written to stdout. + 2. unwrap: output resources are written to stdout, in multi-object yaml format. + 3. OUT_DIR_PATH: output resources are written to provided directory, the directory is created if it doesn't already exist. + --results-dir: Path to a directory to write structured results. Directory must exist. Structured results emitted by the functions are aggregated and saved @@ -118,67 +126,86 @@ fn-args: -``` +```shell # execute container my-fn on the resources in DIR directory and # write output back to DIR -$ kpt fn eval DIR --image gcr.io/example.com/my-fn +$ kpt fn eval DIR -i gcr.io/example.com/my-fn ``` -``` +```shell # execute container my-fn on the resources in DIR directory with # `functionConfig` my-fn-config -$ kpt fn eval DIR --image gcr.io/example.com/my-fn --fn-config my-fn-config +$ kpt fn eval DIR -i gcr.io/example.com/my-fn --fn-config my-fn-config ``` -``` +```shell # execute container my-fn with an input ConfigMap containing `data: {foo: bar}` -$ kpt fn eval DIR --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar +$ kpt fn eval DIR -i gcr.io/example.com/my-fn:v1.0.0 -- foo=bar ``` -``` +```shell # execute executable my-fn on the resources in DIR directory and # write output back to DIR $ kpt fn eval DIR --exec-path ./my-fn ``` -``` +```shell # execute container my-fn on the resources in DIR directory, # save structured results in /tmp/my-results dir and write output back to DIR -$ kpt fn eval DIR --image gcr.io/example.com/my-fn --results-dir /tmp/my-results-dir +$ kpt fn eval DIR -i gcr.io/example.com/my-fn --results-dir /tmp/my-results-dir ``` -``` +```shell # execute container my-fn on the resources in DIR directory with network access enabled, # and write output back to DIR -$ kpt fn eval DIR --image gcr.io/example.com/my-fn --network +$ kpt fn eval DIR -i gcr.io/example.com/my-fn --network ``` -``` +```shell # execute container my-fn on the resource in DIR and export KUBECONFIG # and foo environment variable -$ kpt fn eval DIR --image gcr.io/example.com/my-fn --env KUBECONFIG -e foo=bar +$ kpt fn eval DIR -i gcr.io/example.com/my-fn --env KUBECONFIG -e foo=bar ``` -``` +```shell # execute kubeval function by mounting schema from a local directory on wordpress package -$ kpt fn eval --image gcr.io/kpt-fn/kubeval:v0.1 \ +$ kpt fn eval -i gcr.io/kpt-fn/kubeval:v0.1 \ --mount type=bind,src="/path/to/schema-dir",dst=/schema-dir \ --as-current-user wordpress -- additional_schema_locations=/schema-dir ``` -``` +```shell # chaining functions using the unix pipe to set namespace and set labels on # wordpress package $ kpt fn source wordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ - | kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1 - -- label_name=color label_value=orange \ + | kpt fn eval - -i gcr.io/kpt-fn/set-namespace:v0.1 - -- namespace=mywordpress \ + | kpt fn eval - -i gcr.io/kpt-fn/set-labels:v0.1 - -- label_name=color label_value=orange \ | kpt fn sink wordpress ``` +```shell +# execute container 'set-namespace' on the resources in current directory and write +# the output resources to another directory +$ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o path/to/dir -- namespace=mywordpress +``` + +```shell +# execute container 'set-namespace' on the resources in current directory and write +# the output resources to stdout which are piped to 'kubectl apply' +$ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o unwrap -- namespace=mywordpress \ +| kubectl apply - +``` + +```shell +# execute container 'set-namespace' on the resources in current directory and write +# the wrapped output resources to stdout which are passed to 'set-annotations' function +# and the output resources after setting namespace and annotation is written to another directory +$ kpt fn eval -i gcr.io/kpt-fn/set-namespace:v0.1 -o stdout \ +| kpt fn eval - -i gcr.io/kpt-fn/set-annotations:v0.1.3 -o path/to/dir -- foo=bar +``` + [docker volumes]: https://docs.docker.com/storage/volumes/ -[imperative function execution]: - /book/04-using-functions/02-imperative-function-execution -[function specification]: - /book/05-developing-functions/01-functions-specification +[imperative function execution]: /book/04-using-functions/02-imperative-function-execution +[function specification]: /book/05-developing-functions/01-functions-specification diff --git a/site/reference/cli/fn/render/README.md b/site/reference/cli/fn/render/README.md index e9dcc57439..336cb9b3cf 100644 --- a/site/reference/cli/fn/render/README.md +++ b/site/reference/cli/fn/render/README.md @@ -3,8 +3,9 @@ title: "`render`" linkTitle: "render" type: docs description: > - Render a package + Render a package --- + @@ -31,6 +32,7 @@ Refer to the [Declarative Functions Execution] for more details. ### Synopsis + ```shell kpt fn render [PKG_PATH] [flags] ``` @@ -51,12 +53,21 @@ PKG_PATH: to one of always, ifNotPresent, never. If unspecified, always will be the default. +--output, o: + If specified, the output resources are written to provided location, + if not specified, resources are modified in-place. + Allowed values: stdout|unwrap| + 1. stdout: output resources are wrapped in ResourceList and written to stdout. + 2. unwrap: output resources are written to stdout, in multi-object yaml format. + 3. OUT_DIR_PATH: output resources are written to provided directory, the directory is created if it doesn't already exist. + --results-dir: Path to a directory to write structured results. Directory must exist. Structured results emitted by the functions are aggregated and saved to `results.yaml` file in the specified directory. If not specified, no result files are written to the local filesystem. ``` + ### Examples @@ -70,7 +81,7 @@ $ kpt fn render ```shell # Render the package in current directory and save results in my-results-dir -$ kpt fn render --results-dir my-results-dir +$ kpt fn render --results-dir my-results-dir ``` ```shell @@ -78,6 +89,25 @@ $ kpt fn render --results-dir my-results-dir $ kpt fn render my-package-dir ``` +```shell +# Render the package in current directory and write output resources to another DIR +$ kpt fn render -o path/to/dir +``` + +```shell +# Render resources in current directory and write unwrapped resources to stdout +# which can be piped to kubectl apply +$ kpt fn render -o unwrap | kubectl apply - +``` + +```shell +# Render resources in current directory, write the wrapped resources +# to stdout which are piped to 'set-annotations' function, +# the transformed resources are written to another directory +$ kpt fn render -o stdout \ +| kpt fn eval - -i gcr.io/kpt-fn/set-annotations:v0.1.3 -o path/to/dir -- foo=bar +``` + -[Declarative Functions Execution]: /book/04-using-functions/01-declarative-function-execution +[declarative functions execution]: /book/04-using-functions/01-declarative-function-execution diff --git a/site/reference/cli/fn/sink/README.md b/site/reference/cli/fn/sink/README.md index ac94295d8a..a81734ba02 100644 --- a/site/reference/cli/fn/sink/README.md +++ b/site/reference/cli/fn/sink/README.md @@ -42,7 +42,7 @@ DIR: # read resources from DIR directory, execute my-fn on them and write the # output to DIR directory. $ kpt fn source DIR | - kpt fn eval --image gcr.io/example.com/my-fn - | + kpt fn eval - --image gcr.io/example.com/my-fn - | kpt fn sink DIR ``` diff --git a/site/reference/cli/fn/source/README.md b/site/reference/cli/fn/source/README.md index d91a15dadb..ad7c48d774 100644 --- a/site/reference/cli/fn/source/README.md +++ b/site/reference/cli/fn/source/README.md @@ -55,7 +55,7 @@ $ kpt fn source DIR # read resources from DIR directory, execute my-fn on them and write the # output to DIR directory. $ kpt fn source DIR | - kpt fn eval --image gcr.io/example.com/my-fn - | + kpt fn eval - --image gcr.io/example.com/my-fn - | kpt fn sink DIR ``` diff --git a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go index 59ddfb2870..3997d58477 100644 --- a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go +++ b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go @@ -4,6 +4,7 @@ package cmdeval import ( + "bytes" "context" "fmt" "io" @@ -35,11 +36,10 @@ func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner { } r.Command = c - r.Command.Flags().BoolVar( - &r.DryRun, "dry-run", false, "print results to stdout") - r.Command.Flags().StringVarP( - &r.Image, "image", "i", "", - "run this image as a function") + r.Command.Flags().StringVarP(&r.Dest, "output", "o", "", + fmt.Sprintf("output resources are written to provided location. Allowed values: %s|%s|", cmdutil.Stdout, cmdutil.Unwrap)) + r.Command.Flags().StringVar( + &r.Image, "image", "", "run this image as a function") r.Command.Flags().StringVar( &r.ExecPath, "exec-path", "", "run an executable as a function.") r.Command.Flags().StringVar( @@ -71,7 +71,9 @@ func EvalCommand(ctx context.Context, name string) *cobra.Command { // EvalFnRunner contains the run function type EvalFnRunner struct { Command *cobra.Command - DryRun bool + Dest string + OutContent bytes.Buffer + FromStdin bool Image string ExecPath string FnConfigPath string @@ -87,7 +89,11 @@ type EvalFnRunner struct { } func (r *EvalFnRunner) runE(c *cobra.Command, _ []string) error { - return runner.HandleError(c, r.RunFns.Execute()) + err := runner.HandleError(c, r.RunFns.Execute()) + if err != nil { + return err + } + return cmdutil.WriteFnOutput(r.Dest, r.OutContent.String(), r.FromStdin, c.OutOrStdout()) } // getContainerFunctions parses the commandline flags and arguments into explicit @@ -254,13 +260,16 @@ func (r *EvalFnRunner) preRunE(c *cobra.Command, args []string) error { // set the output to stdout if in dry-run mode or no arguments are specified var output io.Writer var input io.Reader + r.OutContent = bytes.Buffer{} if args[0] == "-" { - output = c.OutOrStdout() + output = &r.OutContent input = c.InOrStdin() + r.FromStdin = true + // clear args as it indicates stdin and not path args = []string{} - } else if r.DryRun { - output = c.OutOrStdout() + } else if r.Dest != "" { + output = &r.OutContent } // set the path if specified as an argument @@ -299,7 +308,6 @@ func (r *EvalFnRunner) preRunE(c *cobra.Command, args []string) error { ContinueOnEmptyResult: true, } - // don't consider args for the function return nil } diff --git a/thirdparty/cmdconfig/commands/cmdeval/cmdeval_test.go b/thirdparty/cmdconfig/commands/cmdeval/cmdeval_test.go index 4ee3f6237b..3e2bf4406e 100644 --- a/thirdparty/cmdconfig/commands/cmdeval/cmdeval_test.go +++ b/thirdparty/cmdconfig/commands/cmdeval/cmdeval_test.go @@ -4,6 +4,7 @@ package cmdeval import ( + "bytes" "context" "io" "os" @@ -51,7 +52,7 @@ apiVersion: v1 name: "config map stdin / stdout", args: []string{"eval", "-", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"}, input: os.Stdin, - output: os.Stdout, + output: &bytes.Buffer{}, expected: ` metadata: name: function-input @@ -65,8 +66,8 @@ apiVersion: v1 }, { name: "config map dry-run", - args: []string{"eval", "dir", "--image", "foo:bar", "--dry-run", "--", "a=b", "c=d", "e=f"}, - output: os.Stdout, + args: []string{"eval", "dir", "--image", "foo:bar", "-o", "stdout", "--", "a=b", "c=d", "e=f"}, + output: &bytes.Buffer{}, path: "dir", expected: ` metadata: diff --git a/thirdparty/kyaml/runfn/runfn.go b/thirdparty/kyaml/runfn/runfn.go index df2ac2f13f..001b645fb2 100644 --- a/thirdparty/kyaml/runfn/runfn.go +++ b/thirdparty/kyaml/runfn/runfn.go @@ -212,7 +212,12 @@ func (r RunFns) runFunctions( } else { // write to the output instead of the directory if r.Output is specified or // the output is nil (reading from Input) - outputs = append(outputs, kio.ByteWriter{Writer: r.Output}) + outputs = append(outputs, kio.ByteWriter{ + Writer: r.Output, + KeepReaderAnnotations: true, + WrappingKind: kio.ResourceListKind, + WrappingAPIVersion: kio.ResourceListAPIVersion, + }) } // add format filter at the end to consistently format output resources diff --git a/thirdparty/kyaml/runfn/runfn_test.go b/thirdparty/kyaml/runfn/runfn_test.go index 28babc0ce6..150c546146 100644 --- a/thirdparty/kyaml/runfn/runfn_test.go +++ b/thirdparty/kyaml/runfn/runfn_test.go @@ -526,7 +526,11 @@ metadata: }, { ContinueOnEmptyResult: true, - ExpectedOutput: "kind: generated\n", + ExpectedOutput: `apiVersion: config.kubernetes.io/v1alpha1 +kind: ResourceList +items: + - kind: generated +`, }, } for i := range test {