diff --git a/cmd/argo/commands/clustertemplate/create.go b/cmd/argo/commands/clustertemplate/create.go index 5e739a22041d..39230ae9a41b 100644 --- a/cmd/argo/commands/clustertemplate/create.go +++ b/cmd/argo/commands/clustertemplate/create.go @@ -12,7 +12,6 @@ import ( "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" - "github.com/argoproj/argo-workflows/v3/workflow/util" ) type cliCreateOpts struct { @@ -59,24 +58,7 @@ func createClusterWorkflowTemplates(ctx context.Context, filePaths []string, cli log.Fatal(err) } - fileContents, err := util.ReadManifest(filePaths...) - if err != nil { - log.Fatal(err) - } - - var clusterWorkflowTemplates []wfv1.ClusterWorkflowTemplate - for _, body := range fileContents { - cwftmpls, err := unmarshalClusterWorkflowTemplates(body, cliOpts.strict) - if err != nil { - log.Fatalf("Failed to parse cluster workflow template: %v", err) - } - clusterWorkflowTemplates = append(clusterWorkflowTemplates, cwftmpls...) - } - - if len(clusterWorkflowTemplates) == 0 { - log.Println("No cluster workflow template found in given files") - os.Exit(1) - } + clusterWorkflowTemplates := generateClusterWorkflowTemplates(filePaths, cliOpts.strict) for _, wftmpl := range clusterWorkflowTemplates { created, err := serviceClient.CreateClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateCreateRequest{ diff --git a/cmd/argo/commands/clustertemplate/root.go b/cmd/argo/commands/clustertemplate/root.go index 3550f48b8b58..3a25195b32f1 100644 --- a/cmd/argo/commands/clustertemplate/root.go +++ b/cmd/argo/commands/clustertemplate/root.go @@ -19,6 +19,7 @@ func NewClusterTemplateCommand() *cobra.Command { command.AddCommand(NewCreateCommand()) command.AddCommand(NewDeleteCommand()) command.AddCommand(NewLintCommand()) + command.AddCommand(NewUpdateCommand()) return command } diff --git a/cmd/argo/commands/clustertemplate/update.go b/cmd/argo/commands/clustertemplate/update.go new file mode 100644 index 000000000000..346e723380f6 --- /dev/null +++ b/cmd/argo/commands/clustertemplate/update.go @@ -0,0 +1,75 @@ +package clustertemplate + +import ( + "context" + "log" + "os" + + "github.com/spf13/cobra" + + "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" + "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" +) + +type cliUpdateOpts struct { + output string // --output + strict bool // --strict +} + +func NewUpdateCommand() *cobra.Command { + var cliUpdateOpts cliUpdateOpts + command := &cobra.Command{ + Use: "update FILE1 FILE2...", + Short: "update a cluster workflow template", + Example: `# Update a Cluster Workflow Template: + argo cluster-template update FILE1 + +# Update a Cluster Workflow Template and print it as YAML: + argo cluster-template update FILE1 --output yaml + +# Update a Cluster Workflow Template with relaxed validation: + argo cluster-template update FILE1 --strict false +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.HelpFunc()(cmd, args) + os.Exit(1) + } + + updateClusterWorkflowTemplates(cmd.Context(), args, &cliUpdateOpts) + }, + } + command.Flags().StringVarP(&cliUpdateOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide") + command.Flags().BoolVar(&cliUpdateOpts.strict, "strict", true, "perform strict workflow validation") + return command +} + +func updateClusterWorkflowTemplates(ctx context.Context, filePaths []string, cliOpts *cliUpdateOpts) { + if cliOpts == nil { + cliOpts = &cliUpdateOpts{} + } + ctx, apiClient := client.NewAPIClient(ctx) + serviceClient, err := apiClient.NewClusterWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } + + clusterWorkflowTemplates := generateClusterWorkflowTemplates(filePaths, cliOpts.strict) + + for _, wftmpl := range clusterWorkflowTemplates { + current, err := serviceClient.GetClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateGetRequest{ + Name: wftmpl.Name, + }) + if err != nil { + log.Fatalf("Failed to get existing cluster workflow template %q to update: %v", wftmpl.Name, err) + } + wftmpl.ResourceVersion = current.ResourceVersion + updated, err := serviceClient.UpdateClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateUpdateRequest{ + Template: &wftmpl, + }) + if err != nil { + log.Fatalf("Failed to update cluster workflow template: %s, %v", wftmpl.Name, err) + } + printClusterWorkflowTemplate(updated, cliOpts.output) + } +} diff --git a/cmd/argo/commands/clustertemplate/util.go b/cmd/argo/commands/clustertemplate/util.go new file mode 100644 index 000000000000..bc8ad18381dd --- /dev/null +++ b/cmd/argo/commands/clustertemplate/util.go @@ -0,0 +1,30 @@ +package clustertemplate + +import ( + "log" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/util" +) + +func generateClusterWorkflowTemplates(filePaths []string, strict bool) []wfv1.ClusterWorkflowTemplate { + fileContents, err := util.ReadManifest(filePaths...) + if err != nil { + log.Fatal(err) + } + + var clusterWorkflowTemplates []wfv1.ClusterWorkflowTemplate + for _, body := range fileContents { + cwftmpls, err := unmarshalClusterWorkflowTemplates(body, strict) + if err != nil { + log.Fatalf("Failed to parse cluster workflow template: %v", err) + } + clusterWorkflowTemplates = append(clusterWorkflowTemplates, cwftmpls...) + } + + if len(clusterWorkflowTemplates) == 0 { + log.Fatalln("No cluster workflow template found in given files") + } + + return clusterWorkflowTemplates +} diff --git a/cmd/argo/commands/cron/create.go b/cmd/argo/commands/cron/create.go index a79b1c67c691..9010658ae85f 100644 --- a/cmd/argo/commands/cron/create.go +++ b/cmd/argo/commands/cron/create.go @@ -4,9 +4,7 @@ import ( "context" "fmt" "log" - "os" - "github.com/argoproj/pkg/errors" "github.com/argoproj/pkg/json" "github.com/spf13/cobra" @@ -33,15 +31,7 @@ func NewCreateCommand() *cobra.Command { Use: "create FILE1 FILE2...", Short: "create a cron workflow", Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.HelpFunc()(cmd, args) - os.Exit(1) - } - - if parametersFile != "" { - err := util.ReadParametersFile(parametersFile, &submitOpts) - errors.CheckError(err) - } + checkArgs(cmd, args, parametersFile, &submitOpts) CreateCronWorkflows(cmd.Context(), args, &cliCreateOpts, &submitOpts) }, @@ -61,24 +51,9 @@ func CreateCronWorkflows(ctx context.Context, filePaths []string, cliOpts *cliCr log.Fatal(err) } - fileContents, err := util.ReadManifest(filePaths...) - if err != nil { - log.Fatal(err) - } - - var cronWorkflows []wfv1.CronWorkflow - for _, body := range fileContents { - cronWfs := unmarshalCronWorkflows(body, cliOpts.strict) - cronWorkflows = append(cronWorkflows, cronWfs...) - } - - if len(cronWorkflows) == 0 { - log.Println("No CronWorkflows found in given files") - os.Exit(1) - } + cronWorkflows := generateCronWorkflows(filePaths, cliOpts.strict) for _, cronWf := range cronWorkflows { - if cliOpts.schedule != "" { cronWf.Spec.Schedule = cliOpts.schedule } diff --git a/cmd/argo/commands/cron/root.go b/cmd/argo/commands/cron/root.go index 0af6f0385c55..da11a6f3a886 100644 --- a/cmd/argo/commands/cron/root.go +++ b/cmd/argo/commands/cron/root.go @@ -21,6 +21,7 @@ func NewCronWorkflowCommand() *cobra.Command { command.AddCommand(NewLintCommand()) command.AddCommand(NewSuspendCommand()) command.AddCommand(NewResumeCommand()) + command.AddCommand(NewUpdateCommand()) return command } diff --git a/cmd/argo/commands/cron/update.go b/cmd/argo/commands/cron/update.go new file mode 100644 index 000000000000..cb92661543d0 --- /dev/null +++ b/cmd/argo/commands/cron/update.go @@ -0,0 +1,87 @@ +package cron + +import ( + "context" + "fmt" + "log" + + "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/util" +) + +type cliUpdateOpts struct { + output string // --output + strict bool // --strict +} + +func NewUpdateCommand() *cobra.Command { + var ( + cliUpdateOpts cliUpdateOpts + submitOpts wfv1.SubmitOpts + parametersFile string + ) + command := &cobra.Command{ + Use: "update FILE1 FILE2...", + Short: "update a cron workflow", + Example: `# Update a Cron Workflow Template: + argo cron update FILE1 + +# Update a Cron Workflow Template and print it as YAML: + argo cron update FILE1 --output yaml + +# Update a Cron Workflow Template with relaxed validation: + argo cron update FILE1 --strict false +`, + Run: func(cmd *cobra.Command, args []string) { + checkArgs(cmd, args, parametersFile, &submitOpts) + + updateCronWorkflows(cmd.Context(), args, &cliUpdateOpts, &submitOpts) + }, + } + + util.PopulateSubmitOpts(command, &submitOpts, ¶metersFile, false) + command.Flags().StringVarP(&cliUpdateOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide") + command.Flags().BoolVar(&cliUpdateOpts.strict, "strict", true, "perform strict workflow validation") + return command +} + +func updateCronWorkflows(ctx context.Context, filePaths []string, cliOpts *cliUpdateOpts, submitOpts *wfv1.SubmitOpts) { + ctx, apiClient := client.NewAPIClient(ctx) + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + if err != nil { + log.Fatal(err) + } + + cronWorkflows := generateCronWorkflows(filePaths, cliOpts.strict) + + for _, cronWf := range cronWorkflows { + newWf := wfv1.Workflow{Spec: cronWf.Spec.WorkflowSpec} + err := util.ApplySubmitOpts(&newWf, submitOpts) + if err != nil { + log.Fatal(err) + } + if cronWf.Namespace == "" { + cronWf.Namespace = client.Namespace() + } + current, err := serviceClient.GetCronWorkflow(ctx, &cronworkflowpkg.GetCronWorkflowRequest{ + Name: cronWf.Name, + Namespace: cronWf.Namespace, + }) + if err != nil { + log.Fatalf("Failed to get existing cron workflow %q to update: %v", cronWf.Name, err) + } + cronWf.ResourceVersion = current.ResourceVersion + updated, err := serviceClient.UpdateCronWorkflow(ctx, &cronworkflowpkg.UpdateCronWorkflowRequest{ + Namespace: cronWf.Namespace, + CronWorkflow: &cronWf, + }) + if err != nil { + log.Fatalf("Failed to update workflow template: %v", err) + } + fmt.Print(getCronWorkflowGet(updated)) + } +} diff --git a/cmd/argo/commands/cron/util.go b/cmd/argo/commands/cron/util.go index 0df2dba9e627..8f94a2fc56b5 100644 --- a/cmd/argo/commands/cron/util.go +++ b/cmd/argo/commands/cron/util.go @@ -1,8 +1,15 @@ package cron import ( + "log" + "os" "time" + "github.com/argoproj/pkg/errors" + "github.com/spf13/cobra" + + "github.com/argoproj/argo-workflows/v3/workflow/util" + "github.com/robfig/cron/v3" "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" @@ -26,3 +33,34 @@ func GetNextRuntime(cwf *v1alpha1.CronWorkflow) (time.Time, error) { return nextRunTime, nil } + +func generateCronWorkflows(filePaths []string, strict bool) []v1alpha1.CronWorkflow { + fileContents, err := util.ReadManifest(filePaths...) + if err != nil { + log.Fatal(err) + } + + var cronWorkflows []v1alpha1.CronWorkflow + for _, body := range fileContents { + cronWfs := unmarshalCronWorkflows(body, strict) + cronWorkflows = append(cronWorkflows, cronWfs...) + } + + if len(cronWorkflows) == 0 { + log.Fatalln("No CronWorkflows found in given files") + } + + return cronWorkflows +} + +func checkArgs(cmd *cobra.Command, args []string, parametersFile string, submitOpts *v1alpha1.SubmitOpts) { + if len(args) == 0 { + cmd.HelpFunc()(cmd, args) + os.Exit(1) + } + + if parametersFile != "" { + err := util.ReadParametersFile(parametersFile, submitOpts) + errors.CheckError(err) + } +} diff --git a/cmd/argo/commands/template/create.go b/cmd/argo/commands/template/create.go index 7dc08c87d8a7..21d9c0cf55c0 100644 --- a/cmd/argo/commands/template/create.go +++ b/cmd/argo/commands/template/create.go @@ -12,7 +12,6 @@ import ( 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" - "github.com/argoproj/argo-workflows/v3/workflow/util" ) type cliCreateOpts struct { @@ -49,21 +48,7 @@ func CreateWorkflowTemplates(ctx context.Context, filePaths []string, cliOpts *c log.Fatal(err) } - fileContents, err := util.ReadManifest(filePaths...) - if err != nil { - log.Fatal(err) - } - - var workflowTemplates []wfv1.WorkflowTemplate - for _, body := range fileContents { - wftmpls := unmarshalWorkflowTemplates(body, cliOpts.strict) - workflowTemplates = append(workflowTemplates, wftmpls...) - } - - if len(workflowTemplates) == 0 { - log.Println("No workflow template found in given files") - os.Exit(1) - } + workflowTemplates := generateWorkflowTemplates(filePaths, cliOpts.strict) for _, wftmpl := range workflowTemplates { if wftmpl.Namespace == "" { diff --git a/cmd/argo/commands/template/root.go b/cmd/argo/commands/template/root.go index 370a8978142e..2706a3b867fa 100644 --- a/cmd/argo/commands/template/root.go +++ b/cmd/argo/commands/template/root.go @@ -18,6 +18,7 @@ func NewTemplateCommand() *cobra.Command { command.AddCommand(NewCreateCommand()) command.AddCommand(NewDeleteCommand()) command.AddCommand(NewLintCommand()) + command.AddCommand(NewUpdateCommand()) return command } diff --git a/cmd/argo/commands/template/update.go b/cmd/argo/commands/template/update.go new file mode 100644 index 000000000000..21db4445fc11 --- /dev/null +++ b/cmd/argo/commands/template/update.go @@ -0,0 +1,80 @@ +package template + +import ( + "context" + "log" + "os" + + "github.com/spf13/cobra" + + "github.com/argoproj/argo-workflows/v3/cmd/argo/commands/client" + workflowtemplatepkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowtemplate" +) + +type cliUpdateOpts struct { + output string // --output + strict bool // --strict +} + +func NewUpdateCommand() *cobra.Command { + var cliUpdateOpts cliUpdateOpts + command := &cobra.Command{ + Use: "update FILE1 FILE2...", + Short: "update a workflow template", + Example: `# Update a Workflow Template: + argo template update FILE1 + +# Update a Workflow Template and print it as YAML: + argo template update FILE1 --output yaml + +# Update a Workflow Template with relaxed validation: + argo template update FILE1 --strict false +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.HelpFunc()(cmd, args) + os.Exit(1) + } + + updateWorkflowTemplates(cmd.Context(), args, &cliUpdateOpts) + }, + } + command.Flags().StringVarP(&cliUpdateOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide") + command.Flags().BoolVar(&cliUpdateOpts.strict, "strict", true, "perform strict workflow validation") + return command +} + +func updateWorkflowTemplates(ctx context.Context, filePaths []string, cliOpts *cliUpdateOpts) { + if cliOpts == nil { + cliOpts = &cliUpdateOpts{} + } + ctx, apiClient := client.NewAPIClient(ctx) + serviceClient, err := apiClient.NewWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } + + workflowTemplates := generateWorkflowTemplates(filePaths, cliOpts.strict) + + for _, wftmpl := range workflowTemplates { + if wftmpl.Namespace == "" { + wftmpl.Namespace = client.Namespace() + } + current, err := serviceClient.GetWorkflowTemplate(ctx, &workflowtemplatepkg.WorkflowTemplateGetRequest{ + Name: wftmpl.Name, + Namespace: wftmpl.Namespace, + }) + if err != nil { + log.Fatalf("Failed to get existing workflow template %q to update: %v", wftmpl.Name, err) + } + wftmpl.ResourceVersion = current.ResourceVersion + updated, err := serviceClient.UpdateWorkflowTemplate(ctx, &workflowtemplatepkg.WorkflowTemplateUpdateRequest{ + Namespace: wftmpl.Namespace, + Template: &wftmpl, + }) + if err != nil { + log.Fatalf("Failed to update workflow template: %v", err) + } + printWorkflowTemplate(updated, cliOpts.output) + } +} diff --git a/cmd/argo/commands/template/util.go b/cmd/argo/commands/template/util.go new file mode 100644 index 000000000000..753b84a8edb8 --- /dev/null +++ b/cmd/argo/commands/template/util.go @@ -0,0 +1,27 @@ +package template + +import ( + "log" + + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/util" +) + +func generateWorkflowTemplates(filePaths []string, strict bool) []wfv1.WorkflowTemplate { + fileContents, err := util.ReadManifest(filePaths...) + if err != nil { + log.Fatal(err) + } + + var workflowTemplates []wfv1.WorkflowTemplate + for _, body := range fileContents { + wftmpls := unmarshalWorkflowTemplates(body, strict) + workflowTemplates = append(workflowTemplates, wftmpls...) + } + + if len(workflowTemplates) == 0 { + log.Fatalln("No workflow template found in given files") + } + + return workflowTemplates +} diff --git a/docs/cli/argo_cluster-template.md b/docs/cli/argo_cluster-template.md index 9419675090d6..813fea0171d7 100644 --- a/docs/cli/argo_cluster-template.md +++ b/docs/cli/argo_cluster-template.md @@ -54,4 +54,5 @@ argo cluster-template [flags] * [argo cluster-template get](argo_cluster-template_get.md) - display details about a cluster workflow template * [argo cluster-template lint](argo_cluster-template_lint.md) - validate files or directories of cluster workflow template manifests * [argo cluster-template list](argo_cluster-template_list.md) - list cluster workflow templates +* [argo cluster-template update](argo_cluster-template_update.md) - update a cluster workflow template diff --git a/docs/cli/argo_cluster-template_update.md b/docs/cli/argo_cluster-template_update.md new file mode 100644 index 000000000000..f0563d017ca0 --- /dev/null +++ b/docs/cli/argo_cluster-template_update.md @@ -0,0 +1,68 @@ +## argo cluster-template update + +update a cluster workflow template + +``` +argo cluster-template update FILE1 FILE2... [flags] +``` + +### Examples + +``` +# Update a Cluster Workflow Template: + argo cluster-template update FILE1 + +# Update a Cluster Workflow Template and print it as YAML: + argo cluster-template update FILE1 --output yaml + +# Update a Cluster Workflow Template with relaxed validation: + argo cluster-template update FILE1 --strict false + +``` + +### Options + +``` + -h, --help help for update + -o, --output string Output format. One of: name|json|yaml|wide + --strict perform strict workflow validation (default true) +``` + +### Options inherited from parent commands + +``` + --argo-base-href string Path to use with HTTP client due to BASE_HREF. Defaults to the ARGO_BASE_HREF environment variable. + --argo-http1 If true, use the HTTP client. Defaults to the ARGO_HTTP1 environment variable. + -s, --argo-server host:port API server host:port. e.g. localhost:2746. Defaults to the ARGO_SERVER environment variable. + --as string Username to impersonate for the operation + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --gloglevel int Set the glog logging level + -H, --header strings Sets additional header to all requests made by Argo CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers) Used only when either ARGO_HTTP1 or --argo-http1 is set to true. + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + -k, --insecure-skip-verify If true, the Argo Server's certificate will not be checked for validity. This will make your HTTPS connections insecure. Defaults to the ARGO_INSECURE_SKIP_VERIFY environment variable. + --instanceid string submit with a specific controller's instance id label. Default to the ARGO_INSTANCEID environment variable. + --kubeconfig string Path to a kube config. Only required if out-of-cluster + --loglevel string Set the logging level. One of: debug|info|warn|error (default "info") + -n, --namespace string If present, the namespace scope for this CLI request + --password string Password for basic authentication to the API server + --proxy-url string If provided, this URL will be used to connect via proxy + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -e, --secure Whether or not the server is using TLS with the Argo Server. Defaults to the ARGO_SECURE environment variable. (default true) + --server string The address and port of the Kubernetes API server + --tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used. + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use + --username string Username for basic authentication to the API server + -v, --verbose Enabled verbose logging, i.e. --loglevel debug +``` + +### SEE ALSO + +* [argo cluster-template](argo_cluster-template.md) - manipulate cluster workflow templates + diff --git a/docs/cli/argo_cron.md b/docs/cli/argo_cron.md index 880b011562b0..b47f3f09ddc5 100644 --- a/docs/cli/argo_cron.md +++ b/docs/cli/argo_cron.md @@ -60,4 +60,5 @@ argo cron [flags] * [argo cron list](argo_cron_list.md) - list cron workflows * [argo cron resume](argo_cron_resume.md) - resume zero or more cron workflows * [argo cron suspend](argo_cron_suspend.md) - suspend zero or more cron workflows +* [argo cron update](argo_cron_update.md) - update a cron workflow diff --git a/docs/cli/argo_cron_update.md b/docs/cli/argo_cron_update.md new file mode 100644 index 000000000000..64ed2c08415a --- /dev/null +++ b/docs/cli/argo_cron_update.md @@ -0,0 +1,75 @@ +## argo cron update + +update a cron workflow + +``` +argo cron update FILE1 FILE2... [flags] +``` + +### Examples + +``` +# Update a Cron Workflow Template: + argo cron update FILE1 + +# Update a Cron Workflow Template and print it as YAML: + argo cron update FILE1 --output yaml + +# Update a Cron Workflow Template with relaxed validation: + argo cron update FILE1 --strict false + +``` + +### Options + +``` + --entrypoint string override entrypoint + --generate-name string override metadata.generateName + -h, --help help for update + -l, --labels string Comma separated labels to apply to the workflow. Will override previous values. + --name string override metadata.name + -o, --output string Output format. One of: name|json|yaml|wide + -p, --parameter stringArray pass an input parameter + -f, --parameter-file string pass a file containing all input parameters + --serviceaccount string run all pods in the workflow using specified serviceaccount + --strict perform strict workflow validation (default true) +``` + +### Options inherited from parent commands + +``` + --argo-base-href string Path to use with HTTP client due to BASE_HREF. Defaults to the ARGO_BASE_HREF environment variable. + --argo-http1 If true, use the HTTP client. Defaults to the ARGO_HTTP1 environment variable. + -s, --argo-server host:port API server host:port. e.g. localhost:2746. Defaults to the ARGO_SERVER environment variable. + --as string Username to impersonate for the operation + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --gloglevel int Set the glog logging level + -H, --header strings Sets additional header to all requests made by Argo CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers) Used only when either ARGO_HTTP1 or --argo-http1 is set to true. + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + -k, --insecure-skip-verify If true, the Argo Server's certificate will not be checked for validity. This will make your HTTPS connections insecure. Defaults to the ARGO_INSECURE_SKIP_VERIFY environment variable. + --instanceid string submit with a specific controller's instance id label. Default to the ARGO_INSTANCEID environment variable. + --kubeconfig string Path to a kube config. Only required if out-of-cluster + --loglevel string Set the logging level. One of: debug|info|warn|error (default "info") + -n, --namespace string If present, the namespace scope for this CLI request + --password string Password for basic authentication to the API server + --proxy-url string If provided, this URL will be used to connect via proxy + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -e, --secure Whether or not the server is using TLS with the Argo Server. Defaults to the ARGO_SECURE environment variable. (default true) + --server string The address and port of the Kubernetes API server + --tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used. + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use + --username string Username for basic authentication to the API server + -v, --verbose Enabled verbose logging, i.e. --loglevel debug +``` + +### SEE ALSO + +* [argo cron](argo_cron.md) - manage cron workflows + diff --git a/docs/cli/argo_template.md b/docs/cli/argo_template.md index 7c1f6629bd53..146c61686c2b 100644 --- a/docs/cli/argo_template.md +++ b/docs/cli/argo_template.md @@ -54,4 +54,5 @@ argo template [flags] * [argo template get](argo_template_get.md) - display details about a workflow template * [argo template lint](argo_template_lint.md) - validate a file or directory of workflow template manifests * [argo template list](argo_template_list.md) - list workflow templates +* [argo template update](argo_template_update.md) - update a workflow template diff --git a/docs/cli/argo_template_update.md b/docs/cli/argo_template_update.md new file mode 100644 index 000000000000..b02b2f4075da --- /dev/null +++ b/docs/cli/argo_template_update.md @@ -0,0 +1,68 @@ +## argo template update + +update a workflow template + +``` +argo template update FILE1 FILE2... [flags] +``` + +### Examples + +``` +# Update a Workflow Template: + argo template update FILE1 + +# Update a Workflow Template and print it as YAML: + argo template update FILE1 --output yaml + +# Update a Workflow Template with relaxed validation: + argo template update FILE1 --strict false + +``` + +### Options + +``` + -h, --help help for update + -o, --output string Output format. One of: name|json|yaml|wide + --strict perform strict workflow validation (default true) +``` + +### Options inherited from parent commands + +``` + --argo-base-href string Path to use with HTTP client due to BASE_HREF. Defaults to the ARGO_BASE_HREF environment variable. + --argo-http1 If true, use the HTTP client. Defaults to the ARGO_HTTP1 environment variable. + -s, --argo-server host:port API server host:port. e.g. localhost:2746. Defaults to the ARGO_SERVER environment variable. + --as string Username to impersonate for the operation + --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. + --as-uid string UID to impersonate for the operation + --certificate-authority string Path to a cert file for the certificate authority + --client-certificate string Path to a client certificate file for TLS + --client-key string Path to a client key file for TLS + --cluster string The name of the kubeconfig cluster to use + --context string The name of the kubeconfig context to use + --gloglevel int Set the glog logging level + -H, --header strings Sets additional header to all requests made by Argo CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers) Used only when either ARGO_HTTP1 or --argo-http1 is set to true. + --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure + -k, --insecure-skip-verify If true, the Argo Server's certificate will not be checked for validity. This will make your HTTPS connections insecure. Defaults to the ARGO_INSECURE_SKIP_VERIFY environment variable. + --instanceid string submit with a specific controller's instance id label. Default to the ARGO_INSTANCEID environment variable. + --kubeconfig string Path to a kube config. Only required if out-of-cluster + --loglevel string Set the logging level. One of: debug|info|warn|error (default "info") + -n, --namespace string If present, the namespace scope for this CLI request + --password string Password for basic authentication to the API server + --proxy-url string If provided, this URL will be used to connect via proxy + --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") + -e, --secure Whether or not the server is using TLS with the Argo Server. Defaults to the ARGO_SECURE environment variable. (default true) + --server string The address and port of the Kubernetes API server + --tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used. + --token string Bearer token for authentication to the API server + --user string The name of the kubeconfig user to use + --username string Username for basic authentication to the API server + -v, --verbose Enabled verbose logging, i.e. --loglevel debug +``` + +### SEE ALSO + +* [argo template](argo_template.md) - manipulate workflow templates + diff --git a/mkdocs.yml b/mkdocs.yml index fb79d3b356ea..3403c489f3d3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -194,6 +194,7 @@ nav: - argo cluster-template get: cli/argo_cluster-template_get.md - argo cluster-template lint: cli/argo_cluster-template_lint.md - argo cluster-template list: cli/argo_cluster-template_list.md + - argo cluster-template update: cli/argo_cluster-template_update.md - argo completion: cli/argo_completion.md - argo cp: cli/argo_cp.md - argo cron: cli/argo_cron.md @@ -204,6 +205,7 @@ nav: - argo cron list: cli/argo_cron_list.md - argo cron resume: cli/argo_cron_resume.md - argo cron suspend: cli/argo_cron_suspend.md + - argo cron update: cli/argo_cron_update.md - argo delete: cli/argo_delete.md - argo executor-plugin: cli/argo_executor-plugin.md - argo executor-plugin build: cli/argo_executor-plugin_build.md @@ -225,6 +227,7 @@ nav: - argo template get: cli/argo_template_get.md - argo template lint: cli/argo_template_lint.md - argo template list: cli/argo_template_list.md + - argo template update: cli/argo_template_update.md - argo terminate: cli/argo_terminate.md - argo version: cli/argo_version.md - argo wait: cli/argo_wait.md diff --git a/test/e2e/cli_test.go b/test/e2e/cli_test.go index 0f67010cebed..394db640d073 100644 --- a/test/e2e/cli_test.go +++ b/test/e2e/cli_test.go @@ -1041,7 +1041,7 @@ func (s *CLISuite) TestWorkflowWatch() { }) } -func (s *CLISuite) TestTemplate() { +func (s *CLISuite) TestTemplateCommands() { s.Run("LintWithoutArgs", func() { s.Given().RunCli([]string{"template", "lint"}, func(t *testing.T, output string, err error) { if assert.Error(t, err) { @@ -1105,6 +1105,44 @@ func (s *CLISuite) TestTemplate() { } }) }) + s.Run("Update", func() { + s.Given(). + RunCli([]string{"template", "update", "testdata/basic-workflowtemplate-update.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "Name:") + assert.Contains(t, output, "Namespace:") + assert.Contains(t, output, "Created:") + } + }) + }) + s.Run("Get", func() { + s.Given().RunCli([]string{"template", "get", "basic", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "entrypoint: main-2") + assert.Contains(t, output, "name: main-2") + } + }) + }) + s.Run("Update", func() { + s.Given(). + RunCli([]string{"template", "update", "testdata/basic-workflowtemplate.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "Name:") + assert.Contains(t, output, "Namespace:") + assert.Contains(t, output, "Created:") + } + }) + }) + s.Run("Get", func() { + s.Given().RunCli([]string{"template", "get", "basic", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "entrypoint: main") + assert.Contains(t, output, "name: main") + assert.NotContains(t, output, "entrypoint: main-2") + assert.NotContains(t, output, "name: main-2") + } + }) + }) s.Run("Delete", func() { s.Given().RunCli([]string{"template", "delete", "basic"}, func(t *testing.T, output string, err error) { assert.NoError(t, err) @@ -1166,7 +1204,7 @@ func (s *CLISuite) TestWorkflowResubmitByFieldSelector() { }) } -func (s *CLISuite) TestCron() { +func (s *CLISuite) TestCronCommands() { s.Run("Lint", func() { s.Given().RunCli([]string{"cron", "lint", "cron/basic.yaml"}, func(t *testing.T, output string, err error) { if assert.NoError(t, err) { @@ -1236,7 +1274,44 @@ func (s *CLISuite) TestCron() { } }) }) - + s.Run("Update", func() { + s.Given(). + RunCli([]string{"cron", "update", "cron/basic-update.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "Schedule: 5 5 5 * *") + } + }) + }) + s.Run("Get", func() { + s.Given().RunCli([]string{"cron", "get", "test-cron-wf-basic", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "5 5 5 * *") + assert.Contains(t, output, "Replace") + assert.Contains(t, output, "startingDeadlineSeconds: 10") + assert.Contains(t, output, "successfulJobsHistoryLimit: 5") + assert.Contains(t, output, "failedJobsHistoryLimit: 3") + assert.Contains(t, output, "argosay") + } + }) + }) + s.Run("Update", func() { + s.Given(). + RunCli([]string{"cron", "update", "cron/basic-update-template.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "Schedule: 5 5 5 * *") + } + }) + }) + s.Run("Get", func() { + s.Given().RunCli([]string{"cron", "get", "test-cron-wf-basic", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "5 5 5 * *") + assert.Contains(t, output, "Replace") + assert.Contains(t, output, "whalesay") + assert.NotContains(t, output, "argosay") + } + }) + }) s.Run("Create Parameter Override", func() { s.Given().RunCli([]string{"cron", "create", "cron/param.yaml", "-p", "message=\"bar test passed\"", "-l", "workflows.argoproj.io/test=true"}, func(t *testing.T, output string, err error) { if assert.NoError(t, err) { @@ -1353,6 +1428,40 @@ func (s *CLISuite) TestClusterTemplateCommands() { } }) }) + s.Run("Update", func() { + s.Given(). + RunCli([]string{"cluster-template", "update", "smoke/cluster-workflow-template-whalesay-template-update.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "cluster-workflow-template-whalesay-template") + } + }) + }) + s.Run("Get", func() { + s.Given(). + RunCli([]string{"cluster-template", "get", "cluster-workflow-template-whalesay-template", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "cluster-workflow-template-whalesay-template") + assert.Contains(t, output, "Updated: {{inputs.parameters.message}}") + } + }) + }) + s.Run("Update", func() { + s.Given(). + RunCli([]string{"cluster-template", "update", "smoke/cluster-workflow-template-whalesay-template-update-parameters.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "cluster-workflow-template-whalesay-template") + } + }) + }) + s.Run("Get", func() { + s.Given(). + RunCli([]string{"cluster-template", "get", "cluster-workflow-template-whalesay-template", "-o", "yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "cluster-workflow-template-whalesay-template") + assert.Contains(t, output, "thanks") + } + }) + }) s.Run("list", func() { s.Given(). RunCli([]string{"cluster-template", "list"}, func(t *testing.T, output string, err error) { diff --git a/test/e2e/cron/basic-update-template.yaml b/test/e2e/cron/basic-update-template.yaml new file mode 100644 index 000000000000..075d5f797325 --- /dev/null +++ b/test/e2e/cron/basic-update-template.yaml @@ -0,0 +1,31 @@ +apiVersion: argoproj.io/v1alpha1 +kind: CronWorkflow +metadata: + name: test-cron-wf-basic + labels: + workflows.argoproj.io/test: "true" +spec: + schedule: "5 5 5 * *" + concurrencyPolicy: "Replace" + startingDeadlineSeconds: 10 + successfulJobsHistoryLimit: 5 + failedJobsHistoryLimit: 3 + workflowMetadata: + labels: + workflows.argoproj.io/test: "true" + # template main -> whalesay + workflowSpec: + entrypoint: whalesay + arguments: + parameters: + - name: message + value: "foo" + templates: + - name: whalesay + inputs: + parameters: + - name: message + container: + image: python:alpine3.6 + command: [ "sh", -c ] + args: [ "echo {{inputs.parameters.message}}" ] \ No newline at end of file diff --git a/test/e2e/cron/basic-update.yaml b/test/e2e/cron/basic-update.yaml new file mode 100644 index 000000000000..67c37e0b8428 --- /dev/null +++ b/test/e2e/cron/basic-update.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: CronWorkflow +metadata: + name: test-cron-wf-basic + labels: + workflows.argoproj.io/test: "true" +spec: + schedule: "5 5 5 * *" # "* * * * *" -> "5 5 5 * *" + concurrencyPolicy: "Replace" # Allow -> Replace + startingDeadlineSeconds: 10 # 0 -> 10 + successfulJobsHistoryLimit: 5 # 4 -> 5 + failedJobsHistoryLimit: 3 # 2 -> 3 + workflowMetadata: + labels: + workflows.argoproj.io/test: "true" + workflowSpec: + entrypoint: main + templates: + - name: main + container: + image: argoproj/argosay:v2 \ No newline at end of file diff --git a/test/e2e/smoke/cluster-workflow-template-whalesay-template-update-parameters.yaml b/test/e2e/smoke/cluster-workflow-template-whalesay-template-update-parameters.yaml new file mode 100644 index 000000000000..79923282384f --- /dev/null +++ b/test/e2e/smoke/cluster-workflow-template-whalesay-template-update-parameters.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ClusterWorkflowTemplate +metadata: + name: cluster-workflow-template-whalesay-template + labels: + workflows.argoproj.io/test: "true" +spec: + entrypoint: whalesay-template + arguments: + parameters: + - name: message + value: thanks # hello world -> thanks + templates: + - name: whalesay-template + inputs: + parameters: + - name: message + container: + image: argoproj/argosay:v2 + args: ["echo", "Updated: {{inputs.parameters.message}}"] + imagePullPolicy: IfNotPresent \ No newline at end of file diff --git a/test/e2e/smoke/cluster-workflow-template-whalesay-template-update.yaml b/test/e2e/smoke/cluster-workflow-template-whalesay-template-update.yaml new file mode 100644 index 000000000000..785fc5351462 --- /dev/null +++ b/test/e2e/smoke/cluster-workflow-template-whalesay-template-update.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ClusterWorkflowTemplate +metadata: + name: cluster-workflow-template-whalesay-template + labels: + workflows.argoproj.io/test: "true" +spec: + entrypoint: whalesay-template + arguments: + parameters: + - name: message + value: hello world + templates: + - name: whalesay-template + inputs: + parameters: + - name: message + container: + image: argoproj/argosay:v2 + args: ["echo", "Updated: {{inputs.parameters.message}}"] # {{inputs.parameters.message}} -> Updated: {{inputs.parameters.message}} + imagePullPolicy: IfNotPresent \ No newline at end of file diff --git a/test/e2e/testdata/basic-workflowtemplate-update.yaml b/test/e2e/testdata/basic-workflowtemplate-update.yaml new file mode 100644 index 000000000000..2f58332ce6a1 --- /dev/null +++ b/test/e2e/testdata/basic-workflowtemplate-update.yaml @@ -0,0 +1,13 @@ +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: basic +spec: + workflowMetadata: + labels: + workflows.argoproj.io/test: "true" + entrypoint: main-2 # main -> main-2 + templates: + - name: main-2 # main -> main-2 + container: + image: argoproj/argosay:v1 \ No newline at end of file