-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Cluster scoped workflow template (#2451)
- Loading branch information
1 parent
c63e3d4
commit cb739a6
Showing
94 changed files
with
10,848 additions
and
640 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"log" | ||
"os" | ||
|
||
"github.com/argoproj/pkg/json" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/argoproj/argo/cmd/argo/commands/client" | ||
"github.com/argoproj/argo/pkg/apiclient/clusterworkflowtemplate" | ||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" | ||
"github.com/argoproj/argo/workflow/common" | ||
"github.com/argoproj/argo/workflow/util" | ||
) | ||
|
||
type cliCreateOpts struct { | ||
output string // --output | ||
strict bool // --strict | ||
} | ||
|
||
func NewCreateCommand() *cobra.Command { | ||
var ( | ||
cliCreateOpts cliCreateOpts | ||
) | ||
var command = &cobra.Command{ | ||
Use: "create FILE1 FILE2...", | ||
Short: "create a cluster workflow template", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if len(args) == 0 { | ||
cmd.HelpFunc()(cmd, args) | ||
os.Exit(1) | ||
} | ||
|
||
createClusterWorkflowTemplates(args, &cliCreateOpts) | ||
}, | ||
} | ||
command.Flags().StringVarP(&cliCreateOpts.output, "output", "o", "", "Output format. One of: name|json|yaml|wide") | ||
command.Flags().BoolVar(&cliCreateOpts.strict, "strict", true, "perform strict workflow validation") | ||
return command | ||
} | ||
|
||
func createClusterWorkflowTemplates(filePaths []string, cliOpts *cliCreateOpts) { | ||
if cliOpts == nil { | ||
cliOpts = &cliCreateOpts{} | ||
} | ||
ctx, apiClient := client.NewAPIClient() | ||
serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() | ||
|
||
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) | ||
} | ||
|
||
for _, wftmpl := range clusterWorkflowTemplates { | ||
created, err := serviceClient.CreateClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateCreateRequest{ | ||
Template: &wftmpl, | ||
}) | ||
if err != nil { | ||
log.Fatalf("Failed to create cluster workflow template: %s, %v", wftmpl.Name, err) | ||
} | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
const cwfts = ` | ||
apiVersion: argoproj.io/v1alpha1 | ||
kind: ClusterWorkflowTemplate | ||
metadata: | ||
name: cluster-workflow-template-whalesay-template | ||
spec: | ||
templates: | ||
- name: whalesay-template | ||
inputs: | ||
parameters: | ||
- name: message | ||
container: | ||
image: docker/whalesay | ||
command: [cowsay] | ||
args: ["{{inputs.parameters.message}}"] | ||
--- | ||
apiVersion: argoproj.io/v1alpha1 | ||
kind: ClusterWorkflowTemplate | ||
metadata: | ||
name: cluster-workflow-template-whalesay-template | ||
spec: | ||
templates: | ||
- name: whalesay-template | ||
inputs: | ||
parameters: | ||
- name: message | ||
container: | ||
image: docker/whalesay | ||
command: [cowsay] | ||
args: ["{{inputs.parameters.message}}"] | ||
` | ||
|
||
func TestUnmarshalCWFT(t *testing.T) { | ||
|
||
clusterwfts, err := unmarshalClusterWorkflowTemplates([]byte(cwfts), false) | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, 2, len(clusterwfts)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/argoproj/pkg/errors" | ||
|
||
"github.com/argoproj/argo/cmd/argo/commands/client" | ||
"github.com/argoproj/argo/pkg/apiclient/clusterworkflowtemplate" | ||
) | ||
|
||
// NewDeleteCommand returns a new instance of an `argo delete` command | ||
func NewDeleteCommand() *cobra.Command { | ||
var ( | ||
all bool | ||
) | ||
|
||
var command = &cobra.Command{ | ||
Use: "delete WORKFLOW_TEMPLATE", | ||
Short: "delete a cluster workflow template", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
apiServerDeleteClusterWorkflowTemplates(all, args) | ||
}, | ||
} | ||
|
||
command.Flags().BoolVar(&all, "all", false, "Delete all cluster workflow templates") | ||
return command | ||
} | ||
|
||
func apiServerDeleteClusterWorkflowTemplates(allWFs bool, wfTmplNames []string) { | ||
ctx, apiClient := client.NewAPIClient() | ||
serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() | ||
var delWFTmplNames []string | ||
if allWFs { | ||
cwftmplList, err := serviceClient.ListClusterWorkflowTemplates(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateListRequest{}) | ||
errors.CheckError(err) | ||
for _, cwfTmpl := range cwftmplList.Items { | ||
delWFTmplNames = append(delWFTmplNames, cwfTmpl.Name) | ||
} | ||
|
||
} else { | ||
delWFTmplNames = wfTmplNames | ||
} | ||
for _, cwfTmplName := range delWFTmplNames { | ||
_, err := serviceClient.DeleteClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateDeleteRequest{ | ||
Name: cwfTmplName, | ||
}) | ||
errors.CheckError(err) | ||
fmt.Printf("ClusterWorkflowTemplate '%s' deleted\n", cwfTmplName) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
|
||
"github.com/spf13/cobra" | ||
"sigs.k8s.io/yaml" | ||
|
||
"github.com/argoproj/pkg/humanize" | ||
|
||
"github.com/argoproj/argo/cmd/argo/commands/client" | ||
clusterworkflowtmplpkg "github.com/argoproj/argo/pkg/apiclient/clusterworkflowtemplate" | ||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" | ||
) | ||
|
||
func NewGetCommand() *cobra.Command { | ||
var ( | ||
output string | ||
) | ||
|
||
var command = &cobra.Command{ | ||
Use: "get CLUSTER WORKFLOW_TEMPLATE...", | ||
Short: "display details about a cluster workflow template", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
ctx, apiClient := client.NewAPIClient() | ||
serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() | ||
for _, name := range args { | ||
wftmpl, err := serviceClient.GetClusterWorkflowTemplate(ctx, &clusterworkflowtmplpkg.ClusterWorkflowTemplateGetRequest{ | ||
Name: name, | ||
}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
printClusterWorkflowTemplate(wftmpl, output) | ||
} | ||
}, | ||
} | ||
|
||
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/argoproj/pkg/errors" | ||
|
||
"github.com/argoproj/argo/cmd/argo/commands/client" | ||
"github.com/argoproj/argo/pkg/apiclient/clusterworkflowtemplate" | ||
"github.com/argoproj/argo/workflow/validate" | ||
) | ||
|
||
func NewLintCommand() *cobra.Command { | ||
var ( | ||
strict bool | ||
) | ||
var command = &cobra.Command{ | ||
Use: "lint FILE...", | ||
Short: "validate files or directories of cluster workflow template manifests", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
ctx, apiClient := client.NewAPIClient() | ||
serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() | ||
|
||
lint := func(file string) error { | ||
cwfTmpls, err := validate.ParseCWfTmplFromFile(file, strict) | ||
if err != nil { | ||
return err | ||
} | ||
for _, cfwft := range cwfTmpls { | ||
_, err := serviceClient.LintClusterWorkflowTemplate(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateLintRequest{Template: &cfwft}) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
fmt.Printf("%s is valid\n", file) | ||
return nil | ||
} | ||
|
||
for _, file := range args { | ||
stat, err := os.Stat(file) | ||
errors.CheckError(err) | ||
if stat.IsDir() { | ||
err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error { | ||
fileExt := filepath.Ext(info.Name()) | ||
switch fileExt { | ||
case ".yaml", ".yml", ".json": | ||
default: | ||
return nil | ||
} | ||
return lint(path) | ||
}) | ||
errors.CheckError(err) | ||
} else { | ||
err := lint(file) | ||
errors.CheckError(err) | ||
} | ||
} | ||
fmt.Printf("Cluster Workflow Template manifests validated\n") | ||
}, | ||
} | ||
command.Flags().BoolVar(&strict, "strict", true, "perform strict workflow validation") | ||
return command | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package clustertemplate | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"text/tabwriter" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/argoproj/argo/cmd/argo/commands/client" | ||
"github.com/argoproj/argo/pkg/apiclient/clusterworkflowtemplate" | ||
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" | ||
) | ||
|
||
type listFlags struct { | ||
output string // --output | ||
} | ||
|
||
func NewListCommand() *cobra.Command { | ||
var ( | ||
listArgs listFlags | ||
) | ||
var command = &cobra.Command{ | ||
Use: "list", | ||
Short: "list cluster workflow templates", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
ctx, apiClient := client.NewAPIClient() | ||
serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() | ||
|
||
cwftmplList, err := serviceClient.ListClusterWorkflowTemplates(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateListRequest{}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
switch listArgs.output { | ||
case "", "wide": | ||
printTable(cwftmplList.Items) | ||
case "name": | ||
for _, cwftmp := range cwftmplList.Items { | ||
fmt.Println(cwftmp.ObjectMeta.Name) | ||
} | ||
default: | ||
log.Fatalf("Unknown output mode: %s", listArgs.output) | ||
} | ||
|
||
}, | ||
} | ||
command.Flags().StringVarP(&listArgs.output, "output", "o", "", "Output format. One of: wide|name") | ||
return command | ||
} | ||
|
||
func printTable(wfList []wfv1.ClusterWorkflowTemplate) { | ||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) | ||
_, _ = fmt.Fprint(w, "NAME") | ||
_, _ = fmt.Fprint(w, "\n") | ||
for _, wf := range wfList { | ||
_, _ = fmt.Fprintf(w, "%s\t", wf.ObjectMeta.Name) | ||
_, _ = fmt.Fprintf(w, "\n") | ||
} | ||
_ = w.Flush() | ||
} |
Oops, something went wrong.