diff --git a/.gitattributes b/.gitattributes index f3b3a3847710..91e3f9d17d14 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ manifests/base/crds/*/argoproj.io*.yaml linguist-generated manifests/quick-start-*.yaml linguist-generated api/jsonschema/schema.json linguist-generated api/openapi-spec/swagger.json linguist-generated +pkg/client/** linguist-generated diff --git a/cmd/argo/commands/clustertemplate/create.go b/cmd/argo/commands/clustertemplate/create.go index 39230ae9a41b..ddd72a18d970 100644 --- a/cmd/argo/commands/clustertemplate/create.go +++ b/cmd/argo/commands/clustertemplate/create.go @@ -5,13 +5,10 @@ import ( "log" "os" - "github.com/argoproj/pkg/json" "github.com/spf13/cobra" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - "github.com/argoproj/argo-workflows/v3/workflow/common" ) type cliCreateOpts struct { @@ -70,21 +67,3 @@ func createClusterWorkflowTemplates(ctx context.Context, filePaths []string, cli printClusterWorkflowTemplate(created, cliOpts.output) } } - -// unmarshalClusterWorkflowTemplates unmarshals the input bytes as either json or yaml -func unmarshalClusterWorkflowTemplates(wfBytes []byte, strict bool) ([]wfv1.ClusterWorkflowTemplate, error) { - var cwft wfv1.ClusterWorkflowTemplate - var jsonOpts []json.JSONOpt - if strict { - jsonOpts = append(jsonOpts, json.DisallowUnknownFields) - } - err := json.Unmarshal(wfBytes, &cwft, jsonOpts...) - if err == nil { - return []wfv1.ClusterWorkflowTemplate{cwft}, nil - } - yamlWfs, err := common.SplitClusterWorkflowTemplateYAMLFile(wfBytes, strict) - if err == nil { - return yamlWfs, nil - } - return nil, err -} diff --git a/cmd/argo/commands/clustertemplate/get.go b/cmd/argo/commands/clustertemplate/get.go index 13505ba82a53..5229150fb55c 100644 --- a/cmd/argo/commands/clustertemplate/get.go +++ b/cmd/argo/commands/clustertemplate/get.go @@ -1,17 +1,12 @@ package clustertemplate import ( - "encoding/json" - "fmt" "log" - "github.com/argoproj/pkg/humanize" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" clusterworkflowtmplpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" ) func NewGetCommand() *cobra.Command { @@ -41,26 +36,3 @@ func NewGetCommand() *cobra.Command { command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: json|yaml|wide") return command } - -func printClusterWorkflowTemplate(wf *wfv1.ClusterWorkflowTemplate, outFmt string) { - switch outFmt { - case "name": - fmt.Println(wf.ObjectMeta.Name) - case "json": - outBytes, _ := json.MarshalIndent(wf, "", " ") - fmt.Println(string(outBytes)) - case "yaml": - outBytes, _ := yaml.Marshal(wf) - fmt.Print(string(outBytes)) - case "wide", "": - printClusterWorkflowTemplateHelper(wf) - default: - log.Fatalf("Unknown output format: %s", outFmt) - } -} - -func printClusterWorkflowTemplateHelper(wf *wfv1.ClusterWorkflowTemplate) { - const fmtStr = "%-20s %v\n" - fmt.Printf(fmtStr, "Name:", wf.ObjectMeta.Name) - fmt.Printf(fmtStr, "Created:", humanize.Timestamp(wf.ObjectMeta.CreationTimestamp.Time)) -} diff --git a/cmd/argo/commands/clustertemplate/util.go b/cmd/argo/commands/clustertemplate/util.go index bc8ad18381dd..e2e414fa23ed 100644 --- a/cmd/argo/commands/clustertemplate/util.go +++ b/cmd/argo/commands/clustertemplate/util.go @@ -1,9 +1,17 @@ package clustertemplate import ( + "encoding/json" + "fmt" "log" + argoJson "github.com/argoproj/pkg/json" + "sigs.k8s.io/yaml" + + "github.com/argoproj/pkg/humanize" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/common" "github.com/argoproj/argo-workflows/v3/workflow/util" ) @@ -28,3 +36,44 @@ func generateClusterWorkflowTemplates(filePaths []string, strict bool) []wfv1.Cl return clusterWorkflowTemplates } + +// unmarshalClusterWorkflowTemplates unmarshals the input bytes as either json or yaml +func unmarshalClusterWorkflowTemplates(wfBytes []byte, strict bool) ([]wfv1.ClusterWorkflowTemplate, error) { + var cwft wfv1.ClusterWorkflowTemplate + var jsonOpts []argoJson.JSONOpt + if strict { + jsonOpts = append(jsonOpts, argoJson.DisallowUnknownFields) + } + err := argoJson.Unmarshal(wfBytes, &cwft, jsonOpts...) + if err == nil { + return []wfv1.ClusterWorkflowTemplate{cwft}, nil + } + yamlWfs, err := common.SplitClusterWorkflowTemplateYAMLFile(wfBytes, strict) + if err == nil { + return yamlWfs, nil + } + return nil, err +} + +func printClusterWorkflowTemplate(wf *wfv1.ClusterWorkflowTemplate, outFmt string) { + switch outFmt { + case "name": + fmt.Println(wf.ObjectMeta.Name) + case "json": + outBytes, _ := json.MarshalIndent(wf, "", " ") + fmt.Println(string(outBytes)) + case "yaml": + outBytes, _ := yaml.Marshal(wf) + fmt.Print(string(outBytes)) + case "wide", "": + printClusterWorkflowTemplateHelper(wf) + default: + log.Fatalf("Unknown output format: %s", outFmt) + } +} + +func printClusterWorkflowTemplateHelper(wf *wfv1.ClusterWorkflowTemplate) { + const fmtStr = "%-20s %v\n" + fmt.Printf(fmtStr, "Name:", wf.ObjectMeta.Name) + fmt.Printf(fmtStr, "Created:", humanize.Timestamp(wf.ObjectMeta.CreationTimestamp.Time)) +} diff --git a/cmd/argo/commands/clustertemplate/create_test.go b/cmd/argo/commands/clustertemplate/util_test.go similarity index 100% rename from cmd/argo/commands/clustertemplate/create_test.go rename to cmd/argo/commands/clustertemplate/util_test.go diff --git a/cmd/argo/commands/cron/create.go b/cmd/argo/commands/cron/create.go index 9010658ae85f..efe1d78da47e 100644 --- a/cmd/argo/commands/cron/create.go +++ b/cmd/argo/commands/cron/create.go @@ -5,13 +5,11 @@ import ( "fmt" "log" - "github.com/argoproj/pkg/json" "github.com/spf13/cobra" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" cronworkflowpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - "github.com/argoproj/argo-workflows/v3/workflow/common" "github.com/argoproj/argo-workflows/v3/workflow/util" ) @@ -88,22 +86,3 @@ func CreateCronWorkflows(ctx context.Context, filePaths []string, cliOpts *cliCr fmt.Print(getCronWorkflowGet(created)) } } - -// unmarshalCronWorkflows unmarshals the input bytes as either json or yaml -func unmarshalCronWorkflows(wfBytes []byte, strict bool) []wfv1.CronWorkflow { - var cronWf wfv1.CronWorkflow - var jsonOpts []json.JSONOpt - if strict { - jsonOpts = append(jsonOpts, json.DisallowUnknownFields) - } - err := json.Unmarshal(wfBytes, &cronWf, jsonOpts...) - if err == nil { - return []wfv1.CronWorkflow{cronWf} - } - yamlWfs, err := common.SplitCronWorkflowYAMLFile(wfBytes, strict) - if err == nil { - return yamlWfs - } - log.Fatalf("Failed to parse cron workflow: %v", err) - return nil -} diff --git a/cmd/argo/commands/cron/get.go b/cmd/argo/commands/cron/get.go index 060d57933944..9f3538f2f342 100644 --- a/cmd/argo/commands/cron/get.go +++ b/cmd/argo/commands/cron/get.go @@ -1,20 +1,13 @@ package cron import ( - "encoding/json" - "fmt" - "log" "os" - "strings" "github.com/argoproj/pkg/errors" - "github.com/argoproj/pkg/humanize" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" ) func NewGetCommand() *cobra.Command { @@ -48,69 +41,3 @@ func NewGetCommand() *cobra.Command { command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: json|yaml|wide") return command } - -func printCronWorkflow(wf *wfv1.CronWorkflow, outFmt string) { - switch outFmt { - case "name": - fmt.Println(wf.ObjectMeta.Name) - case "json": - outBytes, _ := json.MarshalIndent(wf, "", " ") - fmt.Println(string(outBytes)) - case "yaml": - outBytes, _ := yaml.Marshal(wf) - fmt.Print(string(outBytes)) - case "wide", "": - fmt.Print(getCronWorkflowGet(wf)) - default: - log.Fatalf("Unknown output format: %s", outFmt) - } -} - -func getCronWorkflowGet(cwf *wfv1.CronWorkflow) string { - const fmtStr = "%-30s %v\n" - - out := "" - out += fmt.Sprintf(fmtStr, "Name:", cwf.ObjectMeta.Name) - out += fmt.Sprintf(fmtStr, "Namespace:", cwf.ObjectMeta.Namespace) - out += fmt.Sprintf(fmtStr, "Created:", humanize.Timestamp(cwf.ObjectMeta.CreationTimestamp.Time)) - out += fmt.Sprintf(fmtStr, "Schedule:", cwf.Spec.GetScheduleString()) - out += fmt.Sprintf(fmtStr, "Suspended:", cwf.Spec.Suspend) - if cwf.Spec.Timezone != "" { - out += fmt.Sprintf(fmtStr, "Timezone:", cwf.Spec.Timezone) - } - if cwf.Spec.StartingDeadlineSeconds != nil { - out += fmt.Sprintf(fmtStr, "StartingDeadlineSeconds:", *cwf.Spec.StartingDeadlineSeconds) - } - if cwf.Spec.ConcurrencyPolicy != "" { - out += fmt.Sprintf(fmtStr, "ConcurrencyPolicy:", cwf.Spec.ConcurrencyPolicy) - } - if cwf.Status.LastScheduledTime != nil { - out += fmt.Sprintf(fmtStr, "LastScheduledTime:", humanize.Timestamp(cwf.Status.LastScheduledTime.Time)) - } - - next, err := GetNextRuntime(cwf) - if err == nil { - out += fmt.Sprintf(fmtStr, "NextScheduledTime:", humanize.Timestamp(next)+" (assumes workflow-controller is in UTC)") - } - - if len(cwf.Status.Active) > 0 { - var activeWfNames []string - for _, activeWf := range cwf.Status.Active { - activeWfNames = append(activeWfNames, activeWf.Name) - } - out += fmt.Sprintf(fmtStr, "Active Workflows:", strings.Join(activeWfNames, ", ")) - } - if len(cwf.Status.Conditions) > 0 { - out += cwf.Status.Conditions.DisplayString(fmtStr, map[wfv1.ConditionType]string{wfv1.ConditionTypeSubmissionError: "✖"}) - } - if len(cwf.Spec.WorkflowSpec.Arguments.Parameters) > 0 { - out += fmt.Sprintf(fmtStr, "Workflow Parameters:", "") - for _, param := range cwf.Spec.WorkflowSpec.Arguments.Parameters { - if !param.HasValue() { - continue - } - out += fmt.Sprintf(fmtStr, " "+param.Name+":", param.GetValue()) - } - } - return out -} diff --git a/cmd/argo/commands/cron/util.go b/cmd/argo/commands/cron/util.go index 8f94a2fc56b5..909284fb28b0 100644 --- a/cmd/argo/commands/cron/util.go +++ b/cmd/argo/commands/cron/util.go @@ -1,18 +1,26 @@ package cron import ( + "encoding/json" + "fmt" "log" "os" + "strings" "time" - "github.com/argoproj/pkg/errors" + argoJson "github.com/argoproj/pkg/json" + "github.com/robfig/cron/v3" "github.com/spf13/cobra" + "sigs.k8s.io/yaml" + "github.com/argoproj/argo-workflows/v3/workflow/common" "github.com/argoproj/argo-workflows/v3/workflow/util" - "github.com/robfig/cron/v3" + "github.com/argoproj/pkg/errors" + "github.com/argoproj/pkg/humanize" "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" ) // GetNextRuntime returns the next time the workflow should run in local time. It assumes the workflow-controller is in @@ -64,3 +72,88 @@ func checkArgs(cmd *cobra.Command, args []string, parametersFile string, submitO errors.CheckError(err) } } + +// unmarshalCronWorkflows unmarshals the input bytes as either json or yaml +func unmarshalCronWorkflows(wfBytes []byte, strict bool) []wfv1.CronWorkflow { + var cronWf wfv1.CronWorkflow + var jsonOpts []argoJson.JSONOpt + if strict { + jsonOpts = append(jsonOpts, argoJson.DisallowUnknownFields) + } + err := argoJson.Unmarshal(wfBytes, &cronWf, jsonOpts...) + if err == nil { + return []wfv1.CronWorkflow{cronWf} + } + yamlWfs, err := common.SplitCronWorkflowYAMLFile(wfBytes, strict) + if err == nil { + return yamlWfs + } + log.Fatalf("Failed to parse cron workflow: %v", err) + return nil +} + +func printCronWorkflow(wf *wfv1.CronWorkflow, outFmt string) { + switch outFmt { + case "name": + fmt.Println(wf.ObjectMeta.Name) + case "json": + outBytes, _ := json.MarshalIndent(wf, "", " ") + fmt.Println(string(outBytes)) + case "yaml": + outBytes, _ := yaml.Marshal(wf) + fmt.Print(string(outBytes)) + case "wide", "": + fmt.Print(getCronWorkflowGet(wf)) + default: + log.Fatalf("Unknown output format: %s", outFmt) + } +} + +func getCronWorkflowGet(cwf *wfv1.CronWorkflow) string { + const fmtStr = "%-30s %v\n" + + out := "" + out += fmt.Sprintf(fmtStr, "Name:", cwf.ObjectMeta.Name) + out += fmt.Sprintf(fmtStr, "Namespace:", cwf.ObjectMeta.Namespace) + out += fmt.Sprintf(fmtStr, "Created:", humanize.Timestamp(cwf.ObjectMeta.CreationTimestamp.Time)) + out += fmt.Sprintf(fmtStr, "Schedule:", cwf.Spec.GetScheduleString()) + out += fmt.Sprintf(fmtStr, "Suspended:", cwf.Spec.Suspend) + if cwf.Spec.Timezone != "" { + out += fmt.Sprintf(fmtStr, "Timezone:", cwf.Spec.Timezone) + } + if cwf.Spec.StartingDeadlineSeconds != nil { + out += fmt.Sprintf(fmtStr, "StartingDeadlineSeconds:", *cwf.Spec.StartingDeadlineSeconds) + } + if cwf.Spec.ConcurrencyPolicy != "" { + out += fmt.Sprintf(fmtStr, "ConcurrencyPolicy:", cwf.Spec.ConcurrencyPolicy) + } + if cwf.Status.LastScheduledTime != nil { + out += fmt.Sprintf(fmtStr, "LastScheduledTime:", humanize.Timestamp(cwf.Status.LastScheduledTime.Time)) + } + + next, err := GetNextRuntime(cwf) + if err == nil { + out += fmt.Sprintf(fmtStr, "NextScheduledTime:", humanize.Timestamp(next)+" (assumes workflow-controller is in UTC)") + } + + if len(cwf.Status.Active) > 0 { + var activeWfNames []string + for _, activeWf := range cwf.Status.Active { + activeWfNames = append(activeWfNames, activeWf.Name) + } + out += fmt.Sprintf(fmtStr, "Active Workflows:", strings.Join(activeWfNames, ", ")) + } + if len(cwf.Status.Conditions) > 0 { + out += cwf.Status.Conditions.DisplayString(fmtStr, map[wfv1.ConditionType]string{wfv1.ConditionTypeSubmissionError: "✖"}) + } + if len(cwf.Spec.WorkflowSpec.Arguments.Parameters) > 0 { + out += fmt.Sprintf(fmtStr, "Workflow Parameters:", "") + for _, param := range cwf.Spec.WorkflowSpec.Arguments.Parameters { + if !param.HasValue() { + continue + } + out += fmt.Sprintf(fmtStr, " "+param.Name+":", param.GetValue()) + } + } + return out +} diff --git a/cmd/argo/commands/cron/get_test.go b/cmd/argo/commands/cron/util_test.go similarity index 100% rename from cmd/argo/commands/cron/get_test.go rename to cmd/argo/commands/cron/util_test.go diff --git a/cmd/argo/commands/template/create.go b/cmd/argo/commands/template/create.go index 21d9c0cf55c0..388ab98e9cd0 100644 --- a/cmd/argo/commands/template/create.go +++ b/cmd/argo/commands/template/create.go @@ -5,13 +5,10 @@ import ( "log" "os" - "github.com/argoproj/pkg/json" "github.com/spf13/cobra" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" workflowtemplatepkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowtemplate" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - "github.com/argoproj/argo-workflows/v3/workflow/common" ) type cliCreateOpts struct { @@ -64,22 +61,3 @@ func CreateWorkflowTemplates(ctx context.Context, filePaths []string, cliOpts *c printWorkflowTemplate(created, cliOpts.output) } } - -// unmarshalWorkflowTemplates unmarshals the input bytes as either json or yaml -func unmarshalWorkflowTemplates(wfBytes []byte, strict bool) []wfv1.WorkflowTemplate { - var wf wfv1.WorkflowTemplate - var jsonOpts []json.JSONOpt - if strict { - jsonOpts = append(jsonOpts, json.DisallowUnknownFields) - } - err := json.Unmarshal(wfBytes, &wf, jsonOpts...) - if err == nil { - return []wfv1.WorkflowTemplate{wf} - } - yamlWfs, err := common.SplitWorkflowTemplateYAMLFile(wfBytes, strict) - if err == nil { - return yamlWfs - } - log.Fatalf("Failed to parse workflow template: %v", err) - return nil -} diff --git a/cmd/argo/commands/template/get.go b/cmd/argo/commands/template/get.go index 276e19495296..49820ead8f78 100644 --- a/cmd/argo/commands/template/get.go +++ b/cmd/argo/commands/template/get.go @@ -1,17 +1,12 @@ package template import ( - "encoding/json" - "fmt" "log" - "github.com/argoproj/pkg/humanize" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" workflowtemplatepkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowtemplate" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" ) func NewGetCommand() *cobra.Command { @@ -43,27 +38,3 @@ func NewGetCommand() *cobra.Command { command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: json|yaml|wide") return command } - -func printWorkflowTemplate(wf *wfv1.WorkflowTemplate, outFmt string) { - switch outFmt { - case "name": - fmt.Println(wf.ObjectMeta.Name) - case "json": - outBytes, _ := json.MarshalIndent(wf, "", " ") - fmt.Println(string(outBytes)) - case "yaml": - outBytes, _ := yaml.Marshal(wf) - fmt.Print(string(outBytes)) - case "wide", "": - printWorkflowTemplateHelper(wf) - default: - log.Fatalf("Unknown output format: %s", outFmt) - } -} - -func printWorkflowTemplateHelper(wf *wfv1.WorkflowTemplate) { - const fmtStr = "%-20s %v\n" - fmt.Printf(fmtStr, "Name:", wf.ObjectMeta.Name) - fmt.Printf(fmtStr, "Namespace:", wf.ObjectMeta.Namespace) - fmt.Printf(fmtStr, "Created:", humanize.Timestamp(wf.ObjectMeta.CreationTimestamp.Time)) -} diff --git a/cmd/argo/commands/template/util.go b/cmd/argo/commands/template/util.go index 753b84a8edb8..7abb358c1a7a 100644 --- a/cmd/argo/commands/template/util.go +++ b/cmd/argo/commands/template/util.go @@ -1,9 +1,17 @@ package template import ( + "encoding/json" + "fmt" "log" + argoJson "github.com/argoproj/pkg/json" + "sigs.k8s.io/yaml" + + "github.com/argoproj/pkg/humanize" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/common" "github.com/argoproj/argo-workflows/v3/workflow/util" ) @@ -25,3 +33,46 @@ func generateWorkflowTemplates(filePaths []string, strict bool) []wfv1.WorkflowT return workflowTemplates } + +// unmarshalWorkflowTemplates unmarshals the input bytes as either json or yaml +func unmarshalWorkflowTemplates(wfBytes []byte, strict bool) []wfv1.WorkflowTemplate { + var wf wfv1.WorkflowTemplate + var jsonOpts []argoJson.JSONOpt + if strict { + jsonOpts = append(jsonOpts, argoJson.DisallowUnknownFields) + } + err := argoJson.Unmarshal(wfBytes, &wf, jsonOpts...) + if err == nil { + return []wfv1.WorkflowTemplate{wf} + } + yamlWfs, err := common.SplitWorkflowTemplateYAMLFile(wfBytes, strict) + if err == nil { + return yamlWfs + } + log.Fatalf("Failed to parse workflow template: %v", err) + return nil +} + +func printWorkflowTemplate(wf *wfv1.WorkflowTemplate, outFmt string) { + switch outFmt { + case "name": + fmt.Println(wf.ObjectMeta.Name) + case "json": + outBytes, _ := json.MarshalIndent(wf, "", " ") + fmt.Println(string(outBytes)) + case "yaml": + outBytes, _ := yaml.Marshal(wf) + fmt.Print(string(outBytes)) + case "wide", "": + printWorkflowTemplateHelper(wf) + default: + log.Fatalf("Unknown output format: %s", outFmt) + } +} + +func printWorkflowTemplateHelper(wf *wfv1.WorkflowTemplate) { + const fmtStr = "%-20s %v\n" + fmt.Printf(fmtStr, "Name:", wf.ObjectMeta.Name) + fmt.Printf(fmtStr, "Namespace:", wf.ObjectMeta.Namespace) + fmt.Printf(fmtStr, "Created:", humanize.Timestamp(wf.ObjectMeta.CreationTimestamp.Time)) +} diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 4d8af7777ccd..8edda5a5f561 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -1223,7 +1223,18 @@ func (woc *wfOperationCtx) podReconciliation(ctx context.Context) (error, bool) node.Daemoned = nil woc.updated = true } - woc.markNodePhase(node.Name, wfv1.NodeError, "pod deleted") + woc.markNodeError(node.Name, errors.New("", "pod deleted")) + // Set pod's child(container) error if pod deleted + for _, childNodeID := range node.Children { + childNode, err := woc.wf.Status.Nodes.Get(childNodeID) + if err != nil { + woc.log.Errorf("was unable to obtain node for %s", childNodeID) + continue + } + if childNode.Type == wfv1.NodeTypeContainer { + woc.markNodeError(childNode.Name, errors.New("", "container deleted")) + } + } } } return nil, !taskResultIncomplete diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go index c9c7227322fc..3d27e3b7782a 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -10974,3 +10974,92 @@ status: woc.operate(ctx) assert.Equal(t, wfv1.WorkflowSucceeded, woc.wf.Status.Phase) } + +var wfHasContainerSet = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: lovely-rhino +spec: + templates: + - name: init + dag: + tasks: + - name: A + template: run + arguments: {} + - name: run + containerSet: + containers: + - name: main + image: alpine:latest + command: + - /bin/sh + args: + - '-c' + - sleep 9000 + resources: {} + - name: main2 + image: alpine:latest + command: + - /bin/sh + args: + - '-c' + - sleep 9000 + resources: {} + entrypoint: init + arguments: {} + ttlStrategy: + secondsAfterCompletion: 300 + podGC: + strategy: OnPodCompletion` + +// TestContainerSetDeleteContainerWhenPodDeleted test whether a workflow has ContainerSet error when pod deleted. +func TestContainerSetDeleteContainerWhenPodDeleted(t *testing.T) { + cancel, controller := newController() + defer cancel() + ctx := context.Background() + wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("") + wf := wfv1.MustUnmarshalWorkflow(wfHasContainerSet) + wf, err := wfcset.Create(ctx, wf, metav1.CreateOptions{}) + assert.Nil(t, err) + wf, err = wfcset.Get(ctx, wf.ObjectMeta.Name, metav1.GetOptions{}) + assert.Nil(t, err) + woc := newWorkflowOperationCtx(wf, controller) + woc.operate(ctx) + pods, err := listPods(woc) + assert.Nil(t, err) + assert.Equal(t, 1, len(pods.Items)) + + // mark pod Running + makePodsPhase(ctx, woc, apiv1.PodRunning) + woc = newWorkflowOperationCtx(woc.wf, controller) + woc.operate(ctx) + for _, node := range woc.wf.Status.Nodes { + if node.Type == wfv1.NodeTypePod { + assert.Equal(t, wfv1.NodeRunning, node.Phase) + } + } + + // TODO: Refactor to use local-scoped env vars in test to avoid long wait. See https://github.com/argoproj/argo-workflows/pull/12756#discussion_r1530245007 + // delete pod + time.Sleep(10 * time.Second) + deletePods(ctx, woc) + pods, err = listPods(woc) + assert.Nil(t, err) + assert.Equal(t, 0, len(pods.Items)) + + // reconcile + woc = newWorkflowOperationCtx(woc.wf, controller) + woc.operate(ctx) + assert.Equal(t, wfv1.WorkflowError, woc.wf.Status.Phase) + for _, node := range woc.wf.Status.Nodes { + assert.Equal(t, wfv1.NodeError, node.Phase) + if node.Type == wfv1.NodeTypePod { + assert.Equal(t, "pod deleted", node.Message) + } + if node.Type == wfv1.NodeTypeContainer { + assert.Equal(t, "container deleted", node.Message) + } + } +}