From 8607391477e816e6e685fa5719c0d3c55ff1bc00 Mon Sep 17 00:00:00 2001 From: Niklas Hansson Date: Fri, 16 Apr 2021 17:14:55 +0200 Subject: [PATCH] feat(cli): Add offline linting (#5569) Co-authored-by: Alex Collins --- cmd/argo/commands/client/conn.go | 9 +- cmd/argo/commands/clustertemplate/create.go | 5 +- cmd/argo/commands/clustertemplate/delete.go | 4 +- cmd/argo/commands/clustertemplate/get.go | 5 +- cmd/argo/commands/clustertemplate/lint.go | 10 +- cmd/argo/commands/clustertemplate/list.go | 5 +- cmd/argo/commands/cron/create.go | 5 +- cmd/argo/commands/cron/delete.go | 3 +- cmd/argo/commands/cron/get.go | 3 +- cmd/argo/commands/cron/lint.go | 8 +- cmd/argo/commands/cron/list.go | 3 +- cmd/argo/commands/cron/resume.go | 3 +- cmd/argo/commands/cron/suspend.go | 3 +- cmd/argo/commands/lint.go | 11 +- cmd/argo/commands/template/create.go | 5 +- cmd/argo/commands/template/delete.go | 5 +- cmd/argo/commands/template/get.go | 5 +- cmd/argo/commands/template/lint.go | 8 +- cmd/argo/commands/template/list.go | 5 +- cmd/argo/lint/lint.go | 34 +++--- docs/cli/argo_lint.md | 1 + pkg/apiclient/apiclient.go | 14 ++- pkg/apiclient/argo-kube-client.go | 14 +-- pkg/apiclient/argo-server-client.go | 14 +-- pkg/apiclient/http1-client.go | 14 +-- pkg/apiclient/offline-client.go | 47 ++++++++ .../offline-workflow-service-client.go | 102 ++++++++++++++++++ test/e2e/cli_test.go | 14 +++ 28 files changed, 304 insertions(+), 55 deletions(-) create mode 100644 pkg/apiclient/offline-client.go create mode 100644 pkg/apiclient/offline-workflow-service-client.go diff --git a/cmd/argo/commands/client/conn.go b/cmd/argo/commands/client/conn.go index 87f4c131eeea..b0879b30e355 100644 --- a/cmd/argo/commands/client/conn.go +++ b/cmd/argo/commands/client/conn.go @@ -22,7 +22,10 @@ var ( var overrides = clientcmd.ConfigOverrides{} -var explicitPath string +var ( + explicitPath string + Offline bool +) func AddKubectlFlagsToCmd(cmd *cobra.Command) { kflags := clientcmd.RecommendedConfigOverrideFlags("") @@ -58,6 +61,7 @@ func NewAPIClient() (context.Context, apiclient.Client) { return GetAuthString() }, ClientConfigSupplier: func() clientcmd.ClientConfig { return GetConfig() }, + Offline: Offline, }) if err != nil { log.Fatal(err) @@ -66,6 +70,9 @@ func NewAPIClient() (context.Context, apiclient.Client) { } func Namespace() string { + if Offline { + return "" + } if overrides.Context.Namespace != "" { return overrides.Context.Namespace } diff --git a/cmd/argo/commands/clustertemplate/create.go b/cmd/argo/commands/clustertemplate/create.go index 6cea87c6dd96..c539327ead59 100644 --- a/cmd/argo/commands/clustertemplate/create.go +++ b/cmd/argo/commands/clustertemplate/create.go @@ -43,7 +43,10 @@ func createClusterWorkflowTemplates(filePaths []string, cliOpts *cliCreateOpts) cliOpts = &cliCreateOpts{} } ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewClusterWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } fileContents, err := util.ReadManifest(filePaths...) if err != nil { diff --git a/cmd/argo/commands/clustertemplate/delete.go b/cmd/argo/commands/clustertemplate/delete.go index 88507b9c1f74..07eae5291531 100644 --- a/cmd/argo/commands/clustertemplate/delete.go +++ b/cmd/argo/commands/clustertemplate/delete.go @@ -28,7 +28,9 @@ func NewDeleteCommand() *cobra.Command { func apiServerDeleteClusterWorkflowTemplates(allWFs bool, wfTmplNames []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewClusterWorkflowTemplateServiceClient() + errors.CheckError(err) + var delWFTmplNames []string if allWFs { cwftmplList, err := serviceClient.ListClusterWorkflowTemplates(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateListRequest{}) diff --git a/cmd/argo/commands/clustertemplate/get.go b/cmd/argo/commands/clustertemplate/get.go index 7d5c99fcd525..9705c0bbf2fb 100644 --- a/cmd/argo/commands/clustertemplate/get.go +++ b/cmd/argo/commands/clustertemplate/get.go @@ -22,7 +22,10 @@ func NewGetCommand() *cobra.Command { Short: "display details about a cluster workflow template", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewClusterWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } for _, name := range args { wftmpl, err := serviceClient.GetClusterWorkflowTemplate(ctx, &clusterworkflowtmplpkg.ClusterWorkflowTemplateGetRequest{ Name: name, diff --git a/cmd/argo/commands/clustertemplate/lint.go b/cmd/argo/commands/clustertemplate/lint.go index 333a0b984b47..5d410c592851 100644 --- a/cmd/argo/commands/clustertemplate/lint.go +++ b/cmd/argo/commands/clustertemplate/lint.go @@ -24,8 +24,16 @@ func NewLintCommand() *cobra.Command { cmd.HelpFunc()(cmd, args) os.Exit(1) } + ctx, apiClient := client.NewAPIClient() - lint.RunLint(ctx, apiClient, args, []string{wf.ClusterWorkflowTemplatePlural}, client.Namespace(), output, strict) + opts := lint.LintOptions{ + Files: args, + DefaultNamespace: client.Namespace(), + Strict: strict, + Printer: os.Stdout, + } + + lint.RunLint(ctx, apiClient, []string{wf.ClusterWorkflowTemplatePlural}, output, false, opts) }, } diff --git a/cmd/argo/commands/clustertemplate/list.go b/cmd/argo/commands/clustertemplate/list.go index 95295ecda598..4d9f5b7b93cd 100644 --- a/cmd/argo/commands/clustertemplate/list.go +++ b/cmd/argo/commands/clustertemplate/list.go @@ -24,7 +24,10 @@ func NewListCommand() *cobra.Command { Short: "list cluster workflow templates", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewClusterWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewClusterWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } cwftmplList, err := serviceClient.ListClusterWorkflowTemplates(ctx, &clusterworkflowtemplate.ClusterWorkflowTemplateListRequest{}) if err != nil { diff --git a/cmd/argo/commands/cron/create.go b/cmd/argo/commands/cron/create.go index f14e7a82ba84..3488dd832cca 100644 --- a/cmd/argo/commands/cron/create.go +++ b/cmd/argo/commands/cron/create.go @@ -48,7 +48,10 @@ func NewCreateCommand() *cobra.Command { func CreateCronWorkflows(filePaths []string, cliOpts *cliCreateOpts, submitOpts *wfv1.SubmitOpts) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + if err != nil { + log.Fatal(err) + } fileContents, err := util.ReadManifest(filePaths...) if err != nil { diff --git a/cmd/argo/commands/cron/delete.go b/cmd/argo/commands/cron/delete.go index 0d1964d04fb3..9efbe1fea52d 100644 --- a/cmd/argo/commands/cron/delete.go +++ b/cmd/argo/commands/cron/delete.go @@ -17,7 +17,8 @@ func NewDeleteCommand() *cobra.Command { Short: "delete a cron workflow", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + errors.CheckError(err) if all { cronWfList, err := serviceClient.ListCronWorkflows(ctx, &cronworkflowpkg.ListCronWorkflowsRequest{ Namespace: client.Namespace(), diff --git a/cmd/argo/commands/cron/get.go b/cmd/argo/commands/cron/get.go index e7bb75745f26..99b478aee546 100644 --- a/cmd/argo/commands/cron/get.go +++ b/cmd/argo/commands/cron/get.go @@ -30,7 +30,8 @@ func NewGetCommand() *cobra.Command { } ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + errors.CheckError(err) namespace := client.Namespace() for _, arg := range args { diff --git a/cmd/argo/commands/cron/lint.go b/cmd/argo/commands/cron/lint.go index 0ab6cae3c964..320143383b6a 100644 --- a/cmd/argo/commands/cron/lint.go +++ b/cmd/argo/commands/cron/lint.go @@ -25,7 +25,13 @@ func NewLintCommand() *cobra.Command { os.Exit(1) } ctx, apiClient := client.NewAPIClient() - lint.RunLint(ctx, apiClient, args, []string{wf.CronWorkflowPlural}, client.Namespace(), output, strict) + opts := lint.LintOptions{ + Files: args, + Strict: strict, + DefaultNamespace: client.Namespace(), + Printer: os.Stdout, + } + lint.RunLint(ctx, apiClient, []string{wf.CronWorkflowPlural}, output, false, opts) }, } diff --git a/cmd/argo/commands/cron/list.go b/cmd/argo/commands/cron/list.go index ca75c9cfee91..804b49bc44a9 100644 --- a/cmd/argo/commands/cron/list.go +++ b/cmd/argo/commands/cron/list.go @@ -30,7 +30,8 @@ func NewListCommand() *cobra.Command { Short: "list cron workflows", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + errors.CheckError(err) namespace := client.Namespace() if listArgs.allNamespaces { namespace = "" diff --git a/cmd/argo/commands/cron/resume.go b/cmd/argo/commands/cron/resume.go index 59b807ae14fc..abd3c15c14ee 100644 --- a/cmd/argo/commands/cron/resume.go +++ b/cmd/argo/commands/cron/resume.go @@ -17,7 +17,8 @@ func NewResumeCommand() *cobra.Command { Short: "resume zero or more cron workflows", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + errors.CheckError(err) namespace := client.Namespace() for _, name := range args { _, err := serviceClient.ResumeCronWorkflow(ctx, &cronworkflowpkg.CronWorkflowResumeRequest{ diff --git a/cmd/argo/commands/cron/suspend.go b/cmd/argo/commands/cron/suspend.go index 5209f12a8eb4..ff9cca8b3ed6 100644 --- a/cmd/argo/commands/cron/suspend.go +++ b/cmd/argo/commands/cron/suspend.go @@ -17,7 +17,8 @@ func NewSuspendCommand() *cobra.Command { Short: "suspend zero or more cron workflows", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewCronWorkflowServiceClient() + serviceClient, err := apiClient.NewCronWorkflowServiceClient() + errors.CheckError(err) namespace := client.Namespace() for _, name := range args { cronWf, err := serviceClient.SuspendCronWorkflow(ctx, &cronworkflowpkg.CronWorkflowSuspendRequest{ diff --git a/cmd/argo/commands/lint.go b/cmd/argo/commands/lint.go index 154e0e864222..e0aa3ebaab31 100644 --- a/cmd/argo/commands/lint.go +++ b/cmd/argo/commands/lint.go @@ -17,6 +17,7 @@ func NewLintCommand() *cobra.Command { strict bool lintKinds []string output string + offline bool ) allKinds := []string{wf.WorkflowPlural, wf.WorkflowTemplatePlural, wf.CronWorkflowPlural, wf.ClusterWorkflowTemplatePlural} @@ -33,6 +34,7 @@ func NewLintCommand() *cobra.Command { cat manifests.yaml | argo lint --kinds=workflows,cronworkflows -`, Run: func(cmd *cobra.Command, args []string) { + client.Offline = offline ctx, apiClient := client.NewAPIClient() if len(args) == 0 { cmd.HelpFunc()(cmd, args) @@ -41,13 +43,20 @@ func NewLintCommand() *cobra.Command { if len(lintKinds) == 0 || strings.Contains(strings.Join(lintKinds, ","), "all") { lintKinds = allKinds } - lint.RunLint(ctx, apiClient, args, lintKinds, client.Namespace(), output, strict) + ops := lint.LintOptions{ + Files: args, + Strict: strict, + DefaultNamespace: client.Namespace(), + Printer: os.Stdout, + } + lint.RunLint(ctx, apiClient, lintKinds, output, offline, ops) }, } command.Flags().StringSliceVar(&lintKinds, "kinds", []string{"all"}, fmt.Sprintf("Which kinds will be linted. Can be: %s", strings.Join(allKinds, "|"))) command.Flags().StringVarP(&output, "output", "o", "pretty", "Linting results output format. One of: pretty|simple") command.Flags().BoolVar(&strict, "strict", true, "Perform strict workflow validation") + command.Flags().BoolVar(&offline, "offline", false, "perform offline linting") return command } diff --git a/cmd/argo/commands/template/create.go b/cmd/argo/commands/template/create.go index b688b5c6f053..55280beb1142 100644 --- a/cmd/argo/commands/template/create.go +++ b/cmd/argo/commands/template/create.go @@ -43,7 +43,10 @@ func CreateWorkflowTemplates(filePaths []string, cliOpts *cliCreateOpts) { cliOpts = &cliCreateOpts{} } ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } fileContents, err := util.ReadManifest(filePaths...) if err != nil { diff --git a/cmd/argo/commands/template/delete.go b/cmd/argo/commands/template/delete.go index 12f10a9c8b7b..02d4103707ef 100644 --- a/cmd/argo/commands/template/delete.go +++ b/cmd/argo/commands/template/delete.go @@ -30,7 +30,10 @@ func NewDeleteCommand() *cobra.Command { func apiServerDeleteWorkflowTemplates(allWFs bool, wfTmplNames []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } namespace := client.Namespace() var delWFTmplNames []string if allWFs { diff --git a/cmd/argo/commands/template/get.go b/cmd/argo/commands/template/get.go index 2a73a085305c..b33d8f0707b3 100644 --- a/cmd/argo/commands/template/get.go +++ b/cmd/argo/commands/template/get.go @@ -22,7 +22,10 @@ func NewGetCommand() *cobra.Command { Short: "display details about a workflow template", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } namespace := client.Namespace() for _, name := range args { wftmpl, err := serviceClient.GetWorkflowTemplate(ctx, &workflowtemplatepkg.WorkflowTemplateGetRequest{ diff --git a/cmd/argo/commands/template/lint.go b/cmd/argo/commands/template/lint.go index 1da489b6bd31..f511aa75a7dc 100644 --- a/cmd/argo/commands/template/lint.go +++ b/cmd/argo/commands/template/lint.go @@ -25,7 +25,13 @@ func NewLintCommand() *cobra.Command { os.Exit(1) } ctx, apiClient := client.NewAPIClient() - lint.RunLint(ctx, apiClient, args, []string{wf.WorkflowTemplatePlural}, client.Namespace(), output, strict) + opts := lint.LintOptions{ + Files: args, + Strict: strict, + DefaultNamespace: client.Namespace(), + Printer: os.Stdout, + } + lint.RunLint(ctx, apiClient, []string{wf.WorkflowTemplatePlural}, output, false, opts) }, } diff --git a/cmd/argo/commands/template/list.go b/cmd/argo/commands/template/list.go index 2fab3026c73f..b6e73b64828d 100644 --- a/cmd/argo/commands/template/list.go +++ b/cmd/argo/commands/template/list.go @@ -26,7 +26,10 @@ func NewListCommand() *cobra.Command { Short: "list workflow templates", Run: func(cmd *cobra.Command, args []string) { ctx, apiClient := client.NewAPIClient() - serviceClient := apiClient.NewWorkflowTemplateServiceClient() + serviceClient, err := apiClient.NewWorkflowTemplateServiceClient() + if err != nil { + log.Fatal(err) + } namespace := client.Namespace() if listArgs.allNamespaces { namespace = apiv1.NamespaceAll diff --git a/cmd/argo/lint/lint.go b/cmd/argo/lint/lint.go index 03b354c099f5..c7e27a630a29 100644 --- a/cmd/argo/lint/lint.go +++ b/cmd/argo/lint/lint.go @@ -88,21 +88,14 @@ func GetFormatter(fmtr string) (Formatter, error) { // RunLint lints the specified kinds in the specified files and prints the results to os.Stdout. // If linting fails it will exit with status code 1. -func RunLint(ctx context.Context, client apiclient.Client, files, kinds []string, defaultNs, output string, strict bool) { +func RunLint(ctx context.Context, client apiclient.Client, kinds []string, output string, offline bool, opts LintOptions) { fmtr, err := GetFormatter(output) errors.CheckError(err) - clients, err := getLintClients(client, kinds) errors.CheckError(err) - - res, err := Lint(ctx, &LintOptions{ - ServiceClients: clients, - Files: files, - Strict: strict, - DefaultNamespace: defaultNs, - Formatter: fmtr, - Printer: os.Stdout, - }) + opts.ServiceClients = clients + opts.Formatter = fmtr + res, err := Lint(ctx, &opts) errors.CheckError(err) if !res.Success { @@ -314,16 +307,29 @@ func getObjectName(kind string, obj metav1.Object, objIndex int) string { func getLintClients(client apiclient.Client, kinds []string) (ServiceClients, error) { res := ServiceClients{} + var err error for _, kind := range kinds { switch kind { case wf.WorkflowPlural, wf.WorkflowShortName: res.WorkflowsClient = client.NewWorkflowServiceClient() case wf.WorkflowTemplatePlural, wf.WorkflowTemplateShortName: - res.WorkflowTemplatesClient = client.NewWorkflowTemplateServiceClient() + res.WorkflowTemplatesClient, err = client.NewWorkflowTemplateServiceClient() + if err != nil { + return ServiceClients{}, err + } + case wf.CronWorkflowPlural, wf.CronWorkflowShortName: - res.CronWorkflowsClient = client.NewCronWorkflowServiceClient() + res.CronWorkflowsClient, err = client.NewCronWorkflowServiceClient() + if err != nil { + return ServiceClients{}, err + } + case wf.ClusterWorkflowTemplatePlural, wf.ClusterWorkflowTemplateShortName: - res.ClusterWorkflowTemplateClient = client.NewClusterWorkflowTemplateServiceClient() + res.ClusterWorkflowTemplateClient, err = client.NewClusterWorkflowTemplateServiceClient() + if err != nil { + return ServiceClients{}, err + } + default: return res, fmt.Errorf("unknown kind: %s", kind) } diff --git a/docs/cli/argo_lint.md b/docs/cli/argo_lint.md index 9321e52b463e..6a76b6b27ca0 100644 --- a/docs/cli/argo_lint.md +++ b/docs/cli/argo_lint.md @@ -28,6 +28,7 @@ argo lint FILE... [flags] ``` -h, --help help for lint --kinds strings Which kinds will be linted. Can be: workflows|workflowtemplates|cronworkflows|clusterworkflowtemplates (default [all]) + --offline perform offline linting -o, --output string Linting results output format. One of: pretty|simple (default "pretty") --strict Perform strict workflow validation (default true) ``` diff --git a/pkg/apiclient/apiclient.go b/pkg/apiclient/apiclient.go index 797e109572e2..bf4600a9cee0 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -19,9 +19,9 @@ import ( type Client interface { NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) NewWorkflowServiceClient() workflowpkg.WorkflowServiceClient - NewCronWorkflowServiceClient() cronworkflowpkg.CronWorkflowServiceClient - NewWorkflowTemplateServiceClient() workflowtemplatepkg.WorkflowTemplateServiceClient - NewClusterWorkflowTemplateServiceClient() clusterworkflowtmplpkg.ClusterWorkflowTemplateServiceClient + NewCronWorkflowServiceClient() (cronworkflowpkg.CronWorkflowServiceClient, error) + NewWorkflowTemplateServiceClient() (workflowtemplatepkg.WorkflowTemplateServiceClient, error) + NewClusterWorkflowTemplateServiceClient() (clusterworkflowtmplpkg.ClusterWorkflowTemplateServiceClient, error) NewInfoServiceClient() (infopkg.InfoServiceClient, error) } @@ -32,6 +32,7 @@ type Opts struct { // DEPRECATED: use `ClientConfigSupplier` ClientConfig clientcmd.ClientConfig ClientConfigSupplier func() clientcmd.ClientConfig + Offline bool } func (o Opts) String() string { @@ -51,6 +52,9 @@ func NewClient(argoServer string, authSupplier func() string, clientConfig clien func NewClientFromOpts(opts Opts) (context.Context, Client, error) { log.WithField("opts", opts).Debug("Client options") + if opts.Offline { + return newOfflineClient() + } if opts.ArgoServerOpts.URL != "" && opts.InstanceID != "" { return nil, nil, fmt.Errorf("cannot use instance ID with Argo Server") } @@ -62,6 +66,8 @@ func NewClientFromOpts(opts Opts) (context.Context, Client, error) { if opts.ClientConfigSupplier != nil { opts.ClientConfig = opts.ClientConfigSupplier() } - return newArgoKubeClient(opts.ClientConfig, instanceid.NewService(opts.InstanceID)) + + ctx, client, err := newArgoKubeClient(opts.ClientConfig, instanceid.NewService(opts.InstanceID)) + return ctx, client, err } } diff --git a/pkg/apiclient/argo-kube-client.go b/pkg/apiclient/argo-kube-client.go index 780af2e14c83..911bc670dd9f 100644 --- a/pkg/apiclient/argo-kube-client.go +++ b/pkg/apiclient/argo-kube-client.go @@ -36,6 +36,8 @@ type argoKubeClient struct { instanceIDService instanceid.Service } +var _ Client = &argoKubeClient{} + func newArgoKubeClient(clientConfig clientcmd.ClientConfig, instanceIDService instanceid.Service) (context.Context, Client, error) { restConfig, err := clientConfig.ClientConfig() if err != nil { @@ -73,12 +75,12 @@ func (a *argoKubeClient) NewWorkflowServiceClient() workflowpkg.WorkflowServiceC return &errorTranslatingWorkflowServiceClient{&argoKubeWorkflowServiceClient{workflowserver.NewWorkflowServer(a.instanceIDService, argoKubeOffloadNodeStatusRepo)}} } -func (a *argoKubeClient) NewCronWorkflowServiceClient() cronworkflow.CronWorkflowServiceClient { - return &errorTranslatingCronWorkflowServiceClient{&argoKubeCronWorkflowServiceClient{cronworkflowserver.NewCronWorkflowServer(a.instanceIDService)}} +func (a *argoKubeClient) NewCronWorkflowServiceClient() (cronworkflow.CronWorkflowServiceClient, error) { + return &errorTranslatingCronWorkflowServiceClient{&argoKubeCronWorkflowServiceClient{cronworkflowserver.NewCronWorkflowServer(a.instanceIDService)}}, nil } -func (a *argoKubeClient) NewWorkflowTemplateServiceClient() workflowtemplate.WorkflowTemplateServiceClient { - return &errorTranslatingWorkflowTemplateServiceClient{&argoKubeWorkflowTemplateServiceClient{workflowtemplateserver.NewWorkflowTemplateServer(a.instanceIDService)}} +func (a *argoKubeClient) NewWorkflowTemplateServiceClient() (workflowtemplate.WorkflowTemplateServiceClient, error) { + return &errorTranslatingWorkflowTemplateServiceClient{&argoKubeWorkflowTemplateServiceClient{workflowtemplateserver.NewWorkflowTemplateServer(a.instanceIDService)}}, nil } func (a *argoKubeClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { @@ -89,6 +91,6 @@ func (a *argoKubeClient) NewInfoServiceClient() (infopkg.InfoServiceClient, erro return nil, NoArgoServerErr } -func (a *argoKubeClient) NewClusterWorkflowTemplateServiceClient() clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient { - return &errorTranslatingWorkflowClusterTemplateServiceClient{&argoKubeWorkflowClusterTemplateServiceClient{clusterworkflowtmplserver.NewClusterWorkflowTemplateServer(a.instanceIDService)}} +func (a *argoKubeClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient, error) { + return &errorTranslatingWorkflowClusterTemplateServiceClient{&argoKubeWorkflowClusterTemplateServiceClient{clusterworkflowtmplserver.NewClusterWorkflowTemplateServer(a.instanceIDService)}}, nil } diff --git a/pkg/apiclient/argo-server-client.go b/pkg/apiclient/argo-server-client.go index 3c87b95c3532..bc21c63c673a 100644 --- a/pkg/apiclient/argo-server-client.go +++ b/pkg/apiclient/argo-server-client.go @@ -25,6 +25,8 @@ type argoServerClient struct { *grpc.ClientConn } +var _ Client = &argoServerClient{} + func newArgoServerClient(opts ArgoServerOpts, auth string) (context.Context, Client, error) { conn, err := newClientConn(opts) if err != nil { @@ -37,20 +39,20 @@ func (a *argoServerClient) NewWorkflowServiceClient() workflowpkg.WorkflowServic return workflowpkg.NewWorkflowServiceClient(a.ClientConn) } -func (a *argoServerClient) NewCronWorkflowServiceClient() cronworkflowpkg.CronWorkflowServiceClient { - return cronworkflowpkg.NewCronWorkflowServiceClient(a.ClientConn) +func (a *argoServerClient) NewCronWorkflowServiceClient() (cronworkflowpkg.CronWorkflowServiceClient, error) { + return cronworkflowpkg.NewCronWorkflowServiceClient(a.ClientConn), nil } -func (a *argoServerClient) NewWorkflowTemplateServiceClient() workflowtemplatepkg.WorkflowTemplateServiceClient { - return workflowtemplatepkg.NewWorkflowTemplateServiceClient(a.ClientConn) +func (a *argoServerClient) NewWorkflowTemplateServiceClient() (workflowtemplatepkg.WorkflowTemplateServiceClient, error) { + return workflowtemplatepkg.NewWorkflowTemplateServiceClient(a.ClientConn), nil } func (a *argoServerClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { return workflowarchivepkg.NewArchivedWorkflowServiceClient(a.ClientConn), nil } -func (a *argoServerClient) NewClusterWorkflowTemplateServiceClient() clusterworkflowtmplpkg.ClusterWorkflowTemplateServiceClient { - return clusterworkflowtmplpkg.NewClusterWorkflowTemplateServiceClient(a.ClientConn) +func (a *argoServerClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtmplpkg.ClusterWorkflowTemplateServiceClient, error) { + return clusterworkflowtmplpkg.NewClusterWorkflowTemplateServiceClient(a.ClientConn), nil } func (a *argoServerClient) NewInfoServiceClient() (infopkg.InfoServiceClient, error) { diff --git a/pkg/apiclient/http1-client.go b/pkg/apiclient/http1-client.go index f936a5b15fc0..6c7d8e544e09 100644 --- a/pkg/apiclient/http1-client.go +++ b/pkg/apiclient/http1-client.go @@ -14,6 +14,8 @@ import ( type httpClient http1.Facade +var _ Client = &httpClient{} + func (h httpClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { return http1.ArchivedWorkflowsServiceClient(h), nil } @@ -22,16 +24,16 @@ func (h httpClient) NewWorkflowServiceClient() workflowpkg.WorkflowServiceClient return http1.WorkflowServiceClient(h) } -func (h httpClient) NewCronWorkflowServiceClient() cronworkflowpkg.CronWorkflowServiceClient { - return http1.CronWorkflowServiceClient(h) +func (h httpClient) NewCronWorkflowServiceClient() (cronworkflowpkg.CronWorkflowServiceClient, error) { + return http1.CronWorkflowServiceClient(h), nil } -func (h httpClient) NewWorkflowTemplateServiceClient() workflowtemplatepkg.WorkflowTemplateServiceClient { - return http1.WorkflowTemplateServiceClient(h) +func (h httpClient) NewWorkflowTemplateServiceClient() (workflowtemplatepkg.WorkflowTemplateServiceClient, error) { + return http1.WorkflowTemplateServiceClient(h), nil } -func (h httpClient) NewClusterWorkflowTemplateServiceClient() clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient { - return http1.ClusterWorkflowTemplateServiceClient(h) +func (h httpClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient, error) { + return http1.ClusterWorkflowTemplateServiceClient(h), nil } func (h httpClient) NewInfoServiceClient() (infopkg.InfoServiceClient, error) { diff --git a/pkg/apiclient/offline-client.go b/pkg/apiclient/offline-client.go new file mode 100644 index 000000000000..e260b2c06c08 --- /dev/null +++ b/pkg/apiclient/offline-client.go @@ -0,0 +1,47 @@ +package apiclient + +import ( + "context" + "fmt" + + "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" + "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" + infopkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/info" + workflowpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflow" + workflowarchivepkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowarchive" + "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowtemplate" +) + +type offlineClient struct{} + +var NotImplError error = fmt.Errorf("Not implemented for offline client, only valid for kind '--kinds=workflows'") + +var _ Client = &offlineClient{} + +func newOfflineClient() (context.Context, Client, error) { + return context.Background(), &offlineClient{}, nil +} + +func (a *offlineClient) NewWorkflowServiceClient() workflowpkg.WorkflowServiceClient { + return &errorTranslatingWorkflowServiceClient{OfflineWorkflowServiceClient{}} +} + +func (a *offlineClient) NewCronWorkflowServiceClient() (cronworkflow.CronWorkflowServiceClient, error) { + return nil, NotImplError +} + +func (a *offlineClient) NewWorkflowTemplateServiceClient() (workflowtemplate.WorkflowTemplateServiceClient, error) { + return nil, NotImplError +} + +func (a *offlineClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { + return nil, NotImplError +} + +func (a *offlineClient) NewInfoServiceClient() (infopkg.InfoServiceClient, error) { + return nil, NotImplError +} + +func (a *offlineClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient, error) { + return nil, NotImplError +} diff --git a/pkg/apiclient/offline-workflow-service-client.go b/pkg/apiclient/offline-workflow-service-client.go new file mode 100644 index 000000000000..fb2f109940cf --- /dev/null +++ b/pkg/apiclient/offline-workflow-service-client.go @@ -0,0 +1,102 @@ +package apiclient + +import ( + "context" + "fmt" + + "google.golang.org/grpc" + + workflowpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflow" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/validate" +) + +var OfflineErr = fmt.Errorf("not supported when you are in offline mode") + +type OfflineWorkflowServiceClient struct{} + +var _ workflowpkg.WorkflowServiceClient = &OfflineWorkflowServiceClient{} + +func (o OfflineWorkflowServiceClient) CreateWorkflow(context.Context, *workflowpkg.WorkflowCreateRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) GetWorkflow(context.Context, *workflowpkg.WorkflowGetRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) ListWorkflows(context.Context, *workflowpkg.WorkflowListRequest, ...grpc.CallOption) (*wfv1.WorkflowList, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) WatchWorkflows(context.Context, *workflowpkg.WatchWorkflowsRequest, ...grpc.CallOption) (workflowpkg.WorkflowService_WatchWorkflowsClient, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) WatchEvents(context.Context, *workflowpkg.WatchEventsRequest, ...grpc.CallOption) (workflowpkg.WorkflowService_WatchEventsClient, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) DeleteWorkflow(context.Context, *workflowpkg.WorkflowDeleteRequest, ...grpc.CallOption) (*workflowpkg.WorkflowDeleteResponse, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) RetryWorkflow(context.Context, *workflowpkg.WorkflowRetryRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) ResubmitWorkflow(context.Context, *workflowpkg.WorkflowResubmitRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) ResumeWorkflow(context.Context, *workflowpkg.WorkflowResumeRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) SuspendWorkflow(context.Context, *workflowpkg.WorkflowSuspendRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) TerminateWorkflow(context.Context, *workflowpkg.WorkflowTerminateRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) StopWorkflow(context.Context, *workflowpkg.WorkflowStopRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) SetWorkflow(context.Context, *workflowpkg.WorkflowSetRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} + +type offlineWorkflowTemplateNamespacedGetter struct{} + +func (w offlineWorkflowTemplateNamespacedGetter) Get(name string) (*wfv1.WorkflowTemplate, error) { + return nil, OfflineErr +} + +type offlineClusterWorkflowTemplateNamespacedGetter struct{} + +func (o offlineClusterWorkflowTemplateNamespacedGetter) Get(name string) (*wfv1.ClusterWorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) LintWorkflow(_ context.Context, req *workflowpkg.WorkflowLintRequest, _ ...grpc.CallOption) (*wfv1.Workflow, error) { + _, err := validate.ValidateWorkflow(&offlineWorkflowTemplateNamespacedGetter{}, &offlineClusterWorkflowTemplateNamespacedGetter{}, req.Workflow, validate.ValidateOpts{Lint: true}) + if err != nil { + return nil, err + } + return req.Workflow, nil +} + +func (o OfflineWorkflowServiceClient) PodLogs(context.Context, *workflowpkg.WorkflowLogRequest, ...grpc.CallOption) (workflowpkg.WorkflowService_PodLogsClient, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) WorkflowLogs(context.Context, *workflowpkg.WorkflowLogRequest, ...grpc.CallOption) (workflowpkg.WorkflowService_WorkflowLogsClient, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowServiceClient) SubmitWorkflow(context.Context, *workflowpkg.WorkflowSubmitRequest, ...grpc.CallOption) (*wfv1.Workflow, error) { + return nil, OfflineErr +} diff --git a/test/e2e/cli_test.go b/test/e2e/cli_test.go index b368f3ce39fe..d68122f53c94 100644 --- a/test/e2e/cli_test.go +++ b/test/e2e/cli_test.go @@ -25,6 +25,7 @@ const ( KUBE = "KUBE" HTTP1 = "HTTP1" DEFAULT = HTTP1 + OFFLINE = "OFFLINE" ) type CLISuite struct { @@ -60,6 +61,8 @@ func (s *CLISuite) setMode(mode string) { _ = os.Unsetenv("ARGO_TOKEN") _ = os.Unsetenv("ARGO_NAMESPACE") _ = os.Setenv("KUBECONFIG", kubeConfig) + case OFFLINE: + _ = os.Unsetenv("KUBECONFIG") default: panic(mode) } @@ -719,6 +722,17 @@ func (s *CLISuite) TestWorkflowLint() { }) } +func (s *CLISuite) TestWorkflowOfflineLint() { + s.setMode(OFFLINE) + s.Run("LintFile", func() { + s.Given().RunCli([]string{"lint", "--offline=true", "--kinds=workflows", "smoke/basic.yaml"}, func(t *testing.T, output string, err error) { + if assert.NoError(t, err) { + assert.Contains(t, output, "no linting errors found") + } + }) + }) +} + func (s *CLISuite) TestWorkflowRetry() { var retryTime metav1.Time