From 55e96972ec67d0fb5b871d1b1acc649837904fd3 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Thu, 1 Dec 2022 10:56:03 -0800 Subject: [PATCH] fix: Make `jq` work. Fixes #9860 (#10150) Signed-off-by: Alex Collins --- Dockerfile | 6 ++- test/e2e/resource_template_test.go | 19 +++++++++ .../testdata/resource-templates/outputs.yaml | 31 ++++++++++++++ workflow/executor/resource.go | 42 +++++++++---------- 4 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 test/e2e/testdata/resource-templates/outputs.yaml diff --git a/Dockerfile b/Dockerfile index 589cfbe2ac96..2d75f6e38cd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,6 @@ RUN apk update && apk add --no-cache \ curl \ gcc \ bash \ - jq \ mailcap WORKDIR /go/src/github.com/argoproj/argo-workflows @@ -43,6 +42,9 @@ RUN --mount=type=cache,target=/root/.yarn \ FROM builder as argoexec-build +RUN curl -L -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 && \ + chmod +x /usr/local/bin/jq + # Tell git to forget about all of the files that were not included because of .dockerignore in order to ensure that # the git state is "clean" even though said .dockerignore files are not present RUN cat .dockerignore >> .gitignore @@ -85,7 +87,7 @@ FROM bitnami/kubectl:1.24.8 as kubectl FROM gcr.io/distroless/static as argoexec COPY --from=kubectl /opt/bitnami/kubectl/bin/kubectl /bin/ -COPY --from=argoexec-build /usr/bin/jq /bin/ +COPY --from=argoexec-build /usr/local/bin/jq /bin/ COPY --from=argoexec-build /go/src/github.com/argoproj/argo-workflows/dist/argoexec /bin/ COPY --from=argoexec-build /etc/mime.types /etc/mime.types COPY hack/ssh_known_hosts /etc/ssh/ diff --git a/test/e2e/resource_template_test.go b/test/e2e/resource_template_test.go index 16df428ffa32..2a4916f85b2d 100644 --- a/test/e2e/resource_template_test.go +++ b/test/e2e/resource_template_test.go @@ -143,6 +143,25 @@ spec: }) } +func (s *ResourceTemplateSuite) TestResourceTemplateWithOutputs() { + s.Given(). + Workflow("@testdata/resource-templates/outputs.yaml"). + When(). + SubmitWorkflow(). + WaitForWorkflow(fixtures.ToBeSucceeded). + Then(). + ExpectWorkflow(func(t *testing.T, md *metav1.ObjectMeta, status *wfv1.WorkflowStatus) { + outputs := status.Nodes[md.Name].Outputs + if assert.NotNil(t, outputs) { + parameters := outputs.Parameters + if assert.Len(t, parameters, 2) { + assert.Equal(t, "my-pod", parameters[0].Value.String(), "metadata.name is capture for json") + assert.Equal(t, "my-pod", parameters[1].Value.String(), "metadata.name is capture for jq") + } + } + }) +} + func TestResourceTemplateSuite(t *testing.T) { suite.Run(t, new(ResourceTemplateSuite)) } diff --git a/test/e2e/testdata/resource-templates/outputs.yaml b/test/e2e/testdata/resource-templates/outputs.yaml new file mode 100644 index 000000000000..b55cd4519311 --- /dev/null +++ b/test/e2e/testdata/resource-templates/outputs.yaml @@ -0,0 +1,31 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: outputs- +spec: + entrypoint: main + templates: + - name: main + resource: + action: create + setOwnerReference: true + successCondition: status.phase == Succeeded + failureCondition: status.phase == Failed + manifest: | + apiVersion: v1 + kind: Pod + metadata: + name: my-pod + spec: + containers: + - name: main + image: argoproj/argosay:v2 + restartPolicy: Never + outputs: + parameters: + - name: json + valueFrom: + jsonPath: '{.metadata.name}' + - name: jq + valueFrom: + jqFilter: '.metadata.name' \ No newline at end of file diff --git a/workflow/executor/resource.go b/workflow/executor/resource.go index 6518a67bb929..3c7bcebc3a52 100644 --- a/workflow/executor/resource.go +++ b/workflow/executor/resource.go @@ -1,6 +1,7 @@ package executor import ( + "bytes" "context" "encoding/json" "fmt" @@ -294,37 +295,32 @@ func (we *WorkflowExecutor) SaveResourceParameters(ctx context.Context, resource we.Template.Outputs.Parameters[i].Value = wfv1.AnyStringPtr(output) continue } - var cmd *exec.Cmd + outputFormat := "" if param.ValueFrom.JSONPath != "" { - args := []string{"get", resourceName, "-o", fmt.Sprintf("jsonpath=%s", param.ValueFrom.JSONPath)} - if resourceNamespace != "" { - args = append(args, "-n", resourceNamespace) - } - cmd = exec.Command("kubectl", args...) + outputFormat = fmt.Sprintf("jsonpath=%s", param.ValueFrom.JSONPath) } else if param.ValueFrom.JQFilter != "" { - resArgs := []string{resourceName} - if resourceNamespace != "" { - resArgs = append(resArgs, "-n", resourceNamespace) - } - cmdStr := fmt.Sprintf("kubectl get %s -o json | jq -rc '%s'", strings.Join(resArgs, " "), param.ValueFrom.JQFilter) - cmd = exec.Command(cmdStr) + outputFormat = "json" } else { continue } - log.Info(cmd.Args) - out, err := cmd.Output() + kubectl := exec.Command("kubectl", "-n", resourceNamespace, "get", resourceName, "-o", outputFormat) + out, err := kubectl.Output() + log.WithError(err).WithField("out", string(out)).WithField("args", kubectl.Args).Info("kubectl") if err != nil { - // We have a default value to use instead of returning an error - if param.ValueFrom.Default != nil { - out = []byte(param.ValueFrom.Default.String()) - } else { - if exErr, ok := err.(*exec.ExitError); ok { - log.Errorf("`%s` stderr:\n%s", cmd.Args, string(exErr.Stderr)) - } - return errors.InternalWrapError(err) - } + return err } output := string(out) + if param.ValueFrom.JQFilter != "" { + jq := exec.Command("jq", "-rc", param.ValueFrom.JQFilter) + jq.Stdin = bytes.NewBuffer(out) + out, err := jq.Output() + log.WithError(err).WithField("out", string(out)).WithField("args", jq.Args).Info("jq") + if err != nil { + return err + } + output = strings.TrimSpace(string(out)) + } + we.Template.Outputs.Parameters[i].Value = wfv1.AnyStringPtr(output) log.Infof("Saved output parameter: %s, value: %s", param.Name, output) }