From 8973fe44d8879ee34094208c58736332455a44b2 Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Thu, 17 Nov 2022 10:59:12 +0000 Subject: [PATCH] feat: enable full offline lint of all resources Currently, the offline linting only works for Workflow because other resources (`WorkflowTemplate`, `ClusterWorkflowTemplate` and `CronWorkflow`) can depend on references to other resources. However, this behavior is very limiting in a CI context where a user may modify both a "top-level" file and its dependencies at the same time. The dependencies are fetched from the server and the validation fails. This PR extends the offline linter so that it uses the whole list of files passed as arguments in order to validate. This is useful in a GitOps context where a user can pass the whole list of Argo files to the CI check. Signed-off-by: Julien Duchesne --- cmd/argo/commands/client/conn.go | 6 +- cmd/argo/commands/lint.go | 6 +- docs/cli/argo_lint.md | 2 +- pkg/apiclient/apiclient.go | 6 +- pkg/apiclient/offline-client.go | 121 +++++++++++++++--- ...luster-workflow-template-service-client.go | 47 +++++++ .../offline-cron-workflow-service-client.go | 56 ++++++++ .../offline-workflow-service-client.go | 23 +--- ...ffline-workflow-template-service-client.go | 47 +++++++ 9 files changed, 272 insertions(+), 42 deletions(-) create mode 100644 pkg/apiclient/offline-cluster-workflow-template-service-client.go create mode 100644 pkg/apiclient/offline-cron-workflow-service-client.go create mode 100644 pkg/apiclient/offline-workflow-template-service-client.go diff --git a/cmd/argo/commands/client/conn.go b/cmd/argo/commands/client/conn.go index 5f28d1d8268d..a8e975c6f171 100644 --- a/cmd/argo/commands/client/conn.go +++ b/cmd/argo/commands/client/conn.go @@ -24,7 +24,7 @@ var overrides = clientcmd.ConfigOverrides{} var ( explicitPath string - Offline bool + OfflineFiles []string ) func AddKubectlFlagsToCmd(cmd *cobra.Command) { @@ -62,7 +62,7 @@ func NewAPIClient(ctx context.Context) (context.Context, apiclient.Client) { return GetAuthString() }, ClientConfigSupplier: func() clientcmd.ClientConfig { return GetConfig() }, - Offline: Offline, + OfflineFiles: OfflineFiles, Context: ctx, }) if err != nil { @@ -72,7 +72,7 @@ func NewAPIClient(ctx context.Context) (context.Context, apiclient.Client) { } func Namespace() string { - if Offline { + if len(OfflineFiles) > 0 { return "" } if overrides.Context.Namespace != "" { diff --git a/cmd/argo/commands/lint.go b/cmd/argo/commands/lint.go index 608be5835efc..c0fc3839aa9a 100644 --- a/cmd/argo/commands/lint.go +++ b/cmd/argo/commands/lint.go @@ -34,7 +34,9 @@ func NewLintCommand() *cobra.Command { cat manifests.yaml | argo lint --kinds=workflows,cronworkflows -`, Run: func(cmd *cobra.Command, args []string) { - client.Offline = offline + if offline { + client.OfflineFiles = args + } ctx, apiClient := client.NewAPIClient(cmd.Context()) if len(args) == 0 { cmd.HelpFunc()(cmd, args) @@ -56,7 +58,7 @@ func NewLintCommand() *cobra.Command { 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") + command.Flags().BoolVar(&offline, "offline", false, "perform offline linting. When using this mode, you should provide the entire list of Argo Workflows resources as arguments, in order to allow ref resolution.") return command } diff --git a/docs/cli/argo_lint.md b/docs/cli/argo_lint.md index 3bab859412c3..f00fd155e9f5 100644 --- a/docs/cli/argo_lint.md +++ b/docs/cli/argo_lint.md @@ -24,7 +24,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 + --offline perform offline linting. When using this mode, you should provide the entire list of Argo Workflows resources as arguments, in order to allow ref resolution. -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 40fa69bd62be..02f152831bdb 100644 --- a/pkg/apiclient/apiclient.go +++ b/pkg/apiclient/apiclient.go @@ -32,7 +32,7 @@ type Opts struct { // DEPRECATED: use `ClientConfigSupplier` ClientConfig clientcmd.ClientConfig ClientConfigSupplier func() clientcmd.ClientConfig - Offline bool + OfflineFiles []string Context context.Context } @@ -62,8 +62,8 @@ 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 len(opts.OfflineFiles) > 0 { + return newOfflineClient(opts.OfflineFiles) } if opts.ArgoServerOpts.URL != "" && opts.InstanceID != "" { return nil, nil, fmt.Errorf("cannot use instance ID with Argo Server") diff --git a/pkg/apiclient/offline-client.go b/pkg/apiclient/offline-client.go index e260b2c06c08..0cefa36690fb 100644 --- a/pkg/apiclient/offline-client.go +++ b/pkg/apiclient/offline-client.go @@ -3,6 +3,7 @@ package apiclient import ( "context" "fmt" + "os" "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" @@ -10,38 +11,126 @@ import ( 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" + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/templateresolution" + + "sigs.k8s.io/yaml" ) -type offlineClient struct{} +type offlineClient struct { + clusterWorkflowTemplateGetter templateresolution.ClusterWorkflowTemplateGetter + namespacedWorkflowTemplateGetterMap map[string]templateresolution.WorkflowTemplateNamespacedGetter +} -var NotImplError error = fmt.Errorf("Not implemented for offline client, only valid for kind '--kinds=workflows'") +var OfflineErr = fmt.Errorf("not supported when you are in offline mode") var _ Client = &offlineClient{} -func newOfflineClient() (context.Context, Client, error) { - return context.Background(), &offlineClient{}, nil +func newOfflineClient(files []string) (context.Context, Client, error) { + clusterWorkflowTemplateGetter := &offlineClusterWorkflowTemplateGetter{ + clusterWorkflowTemplates: map[string]*wfv1.ClusterWorkflowTemplate{}, + } + workflowTemplateGetters := map[string]templateresolution.WorkflowTemplateNamespacedGetter{} + + for _, file := range files { + bytes, err := os.ReadFile(file) + if err != nil { + return nil, nil, fmt.Errorf("failed to read file %s: %w", file, err) + } + var generic map[string]interface{} + if err := yaml.Unmarshal(bytes, &generic); err != nil { + return nil, nil, fmt.Errorf("failed to parse YAML from file %s: %w", file, err) + } + switch generic["kind"] { + case "ClusterWorkflowTemplate": + cwftmpl := new(v1alpha1.ClusterWorkflowTemplate) + if err := yaml.Unmarshal(bytes, &cwftmpl); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal file %s as a ClusterWorkflowTemplate: %w", file, err) + } + clusterWorkflowTemplateGetter.clusterWorkflowTemplates[cwftmpl.Name] = cwftmpl + + case "WorkflowTemplate": + wftmpl := new(v1alpha1.WorkflowTemplate) + if err := yaml.Unmarshal(bytes, &wftmpl); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal file %s as a WorkflowTemplate: %w", file, err) + } + getter, ok := workflowTemplateGetters[wftmpl.Namespace] + if !ok { + getter = &offlineWorkflowTemplateNamespacedGetter{ + namespace: wftmpl.Namespace, + workflowTemplates: map[string]*wfv1.WorkflowTemplate{}, + } + workflowTemplateGetters[wftmpl.Namespace] = getter + } + + getter.(*offlineWorkflowTemplateNamespacedGetter).workflowTemplates[wftmpl.Name] = wftmpl + } + + } + + return context.Background(), &offlineClient{ + clusterWorkflowTemplateGetter: clusterWorkflowTemplateGetter, + namespacedWorkflowTemplateGetterMap: workflowTemplateGetters, + }, nil +} + +func (c *offlineClient) NewWorkflowServiceClient() workflowpkg.WorkflowServiceClient { + return &errorTranslatingWorkflowServiceClient{OfflineWorkflowServiceClient{ + clusterWorkflowTemplateGetter: c.clusterWorkflowTemplateGetter, + namespacedWorkflowTemplateGetterMap: c.namespacedWorkflowTemplateGetterMap, + }} +} + +func (c *offlineClient) NewCronWorkflowServiceClient() (cronworkflow.CronWorkflowServiceClient, error) { + return &errorTranslatingCronWorkflowServiceClient{OfflineCronWorkflowServiceClient{ + clusterWorkflowTemplateGetter: c.clusterWorkflowTemplateGetter, + namespacedWorkflowTemplateGetterMap: c.namespacedWorkflowTemplateGetterMap, + }}, nil +} + +func (c *offlineClient) NewWorkflowTemplateServiceClient() (workflowtemplate.WorkflowTemplateServiceClient, error) { + return &errorTranslatingWorkflowTemplateServiceClient{OfflineWorkflowTemplateServiceClient{ + clusterWorkflowTemplateGetter: c.clusterWorkflowTemplateGetter, + namespacedWorkflowTemplateGetterMap: c.namespacedWorkflowTemplateGetterMap, + }}, nil +} + +func (c *offlineClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient, error) { + return &errorTranslatingWorkflowClusterTemplateServiceClient{OfflineClusterWorkflowTemplateServiceClient{ + clusterWorkflowTemplateGetter: c.clusterWorkflowTemplateGetter, + namespacedWorkflowTemplateGetterMap: c.namespacedWorkflowTemplateGetterMap, + }}, nil } -func (a *offlineClient) NewWorkflowServiceClient() workflowpkg.WorkflowServiceClient { - return &errorTranslatingWorkflowServiceClient{OfflineWorkflowServiceClient{}} +func (c *offlineClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { + return nil, NoArgoServerErr } -func (a *offlineClient) NewCronWorkflowServiceClient() (cronworkflow.CronWorkflowServiceClient, error) { - return nil, NotImplError +func (c *offlineClient) NewInfoServiceClient() (infopkg.InfoServiceClient, error) { + return nil, NoArgoServerErr } -func (a *offlineClient) NewWorkflowTemplateServiceClient() (workflowtemplate.WorkflowTemplateServiceClient, error) { - return nil, NotImplError +type offlineWorkflowTemplateNamespacedGetter struct { + namespace string + workflowTemplates map[string]*wfv1.WorkflowTemplate } -func (a *offlineClient) NewArchivedWorkflowServiceClient() (workflowarchivepkg.ArchivedWorkflowServiceClient, error) { - return nil, NotImplError +func (w offlineWorkflowTemplateNamespacedGetter) Get(name string) (*wfv1.WorkflowTemplate, error) { + if v, ok := w.workflowTemplates[name]; ok { + return v, nil + } + return nil, fmt.Errorf("couldn't find workflow template %q in namespace %q", name, w.namespace) } -func (a *offlineClient) NewInfoServiceClient() (infopkg.InfoServiceClient, error) { - return nil, NotImplError +type offlineClusterWorkflowTemplateGetter struct { + clusterWorkflowTemplates map[string]*wfv1.ClusterWorkflowTemplate } -func (a *offlineClient) NewClusterWorkflowTemplateServiceClient() (clusterworkflowtemplate.ClusterWorkflowTemplateServiceClient, error) { - return nil, NotImplError +func (o offlineClusterWorkflowTemplateGetter) Get(name string) (*wfv1.ClusterWorkflowTemplate, error) { + if v, ok := o.clusterWorkflowTemplates[name]; ok { + return v, nil + } + + return nil, fmt.Errorf("couldn't find cluster workflow template %q", name) } diff --git a/pkg/apiclient/offline-cluster-workflow-template-service-client.go b/pkg/apiclient/offline-cluster-workflow-template-service-client.go new file mode 100644 index 000000000000..35d0bf647fdd --- /dev/null +++ b/pkg/apiclient/offline-cluster-workflow-template-service-client.go @@ -0,0 +1,47 @@ +package apiclient + +import ( + "context" + + "google.golang.org/grpc" + + clusterworkflowtmplpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/clusterworkflowtemplate" + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/templateresolution" + "github.com/argoproj/argo-workflows/v3/workflow/validate" +) + +type OfflineClusterWorkflowTemplateServiceClient struct { + clusterWorkflowTemplateGetter templateresolution.ClusterWorkflowTemplateGetter + namespacedWorkflowTemplateGetterMap map[string]templateresolution.WorkflowTemplateNamespacedGetter +} + +var _ clusterworkflowtmplpkg.ClusterWorkflowTemplateServiceClient = &OfflineClusterWorkflowTemplateServiceClient{} + +func (o OfflineClusterWorkflowTemplateServiceClient) CreateClusterWorkflowTemplate(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateCreateRequest, opts ...grpc.CallOption) (*v1alpha1.ClusterWorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineClusterWorkflowTemplateServiceClient) GetClusterWorkflowTemplate(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateGetRequest, opts ...grpc.CallOption) (*v1alpha1.ClusterWorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineClusterWorkflowTemplateServiceClient) ListClusterWorkflowTemplates(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateListRequest, opts ...grpc.CallOption) (*v1alpha1.ClusterWorkflowTemplateList, error) { + return nil, OfflineErr +} + +func (o OfflineClusterWorkflowTemplateServiceClient) UpdateClusterWorkflowTemplate(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateUpdateRequest, opts ...grpc.CallOption) (*v1alpha1.ClusterWorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineClusterWorkflowTemplateServiceClient) DeleteClusterWorkflowTemplate(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateDeleteRequest, opts ...grpc.CallOption) (*clusterworkflowtmplpkg.ClusterWorkflowTemplateDeleteResponse, error) { + return nil, OfflineErr +} + +func (o OfflineClusterWorkflowTemplateServiceClient) LintClusterWorkflowTemplate(ctx context.Context, req *clusterworkflowtmplpkg.ClusterWorkflowTemplateLintRequest, opts ...grpc.CallOption) (*v1alpha1.ClusterWorkflowTemplate, error) { + err := validate.ValidateClusterWorkflowTemplate(nil, o.clusterWorkflowTemplateGetter, req.Template, validate.ValidateOpts{Lint: true}) + if err != nil { + return nil, err + } + return req.Template, nil +} diff --git a/pkg/apiclient/offline-cron-workflow-service-client.go b/pkg/apiclient/offline-cron-workflow-service-client.go new file mode 100644 index 000000000000..ffd0f94d0fba --- /dev/null +++ b/pkg/apiclient/offline-cron-workflow-service-client.go @@ -0,0 +1,56 @@ +package apiclient + +import ( + "context" + + "google.golang.org/grpc" + + "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" + cronworkflowpkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/cronworkflow" + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/templateresolution" + "github.com/argoproj/argo-workflows/v3/workflow/validate" +) + +type OfflineCronWorkflowServiceClient struct { + clusterWorkflowTemplateGetter templateresolution.ClusterWorkflowTemplateGetter + namespacedWorkflowTemplateGetterMap map[string]templateresolution.WorkflowTemplateNamespacedGetter +} + +var _ cronworkflow.CronWorkflowServiceClient = &OfflineCronWorkflowServiceClient{} + +func (o OfflineCronWorkflowServiceClient) LintCronWorkflow(ctx context.Context, req *cronworkflowpkg.LintCronWorkflowRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + err := validate.ValidateCronWorkflow(o.namespacedWorkflowTemplateGetterMap[req.Namespace], o.clusterWorkflowTemplateGetter, req.CronWorkflow) + if err != nil { + return nil, err + } + return req.CronWorkflow, nil +} + +func (o OfflineCronWorkflowServiceClient) CreateCronWorkflow(ctx context.Context, req *cronworkflowpkg.CreateCronWorkflowRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) ListCronWorkflows(ctx context.Context, req *cronworkflowpkg.ListCronWorkflowsRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflowList, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) GetCronWorkflow(ctx context.Context, req *cronworkflowpkg.GetCronWorkflowRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) UpdateCronWorkflow(ctx context.Context, req *cronworkflowpkg.UpdateCronWorkflowRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) DeleteCronWorkflow(ctx context.Context, req *cronworkflowpkg.DeleteCronWorkflowRequest, _ ...grpc.CallOption) (*cronworkflowpkg.CronWorkflowDeletedResponse, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) ResumeCronWorkflow(ctx context.Context, req *cronworkflowpkg.CronWorkflowResumeRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + return nil, OfflineErr +} + +func (o OfflineCronWorkflowServiceClient) SuspendCronWorkflow(ctx context.Context, req *cronworkflowpkg.CronWorkflowSuspendRequest, _ ...grpc.CallOption) (*v1alpha1.CronWorkflow, error) { + return nil, OfflineErr +} diff --git a/pkg/apiclient/offline-workflow-service-client.go b/pkg/apiclient/offline-workflow-service-client.go index add75290ceaf..c5925e54c6dc 100644 --- a/pkg/apiclient/offline-workflow-service-client.go +++ b/pkg/apiclient/offline-workflow-service-client.go @@ -2,18 +2,19 @@ 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/templateresolution" "github.com/argoproj/argo-workflows/v3/workflow/validate" ) -var OfflineErr = fmt.Errorf("not supported when you are in offline mode") - -type OfflineWorkflowServiceClient struct{} +type OfflineWorkflowServiceClient struct { + clusterWorkflowTemplateGetter templateresolution.ClusterWorkflowTemplateGetter + namespacedWorkflowTemplateGetterMap map[string]templateresolution.WorkflowTemplateNamespacedGetter +} var _ workflowpkg.WorkflowServiceClient = &OfflineWorkflowServiceClient{} @@ -69,20 +70,8 @@ func (o OfflineWorkflowServiceClient) SetWorkflow(context.Context, *workflowpkg. 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}) + err := validate.ValidateWorkflow(o.namespacedWorkflowTemplateGetterMap[req.Namespace], o.clusterWorkflowTemplateGetter, req.Workflow, validate.ValidateOpts{Lint: true}) if err != nil { return nil, err } diff --git a/pkg/apiclient/offline-workflow-template-service-client.go b/pkg/apiclient/offline-workflow-template-service-client.go new file mode 100644 index 000000000000..4cb70c9f29d1 --- /dev/null +++ b/pkg/apiclient/offline-workflow-template-service-client.go @@ -0,0 +1,47 @@ +package apiclient + +import ( + "context" + + "google.golang.org/grpc" + + workflowtemplatepkg "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflowtemplate" + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "github.com/argoproj/argo-workflows/v3/workflow/templateresolution" + "github.com/argoproj/argo-workflows/v3/workflow/validate" +) + +type OfflineWorkflowTemplateServiceClient struct { + clusterWorkflowTemplateGetter templateresolution.ClusterWorkflowTemplateGetter + namespacedWorkflowTemplateGetterMap map[string]templateresolution.WorkflowTemplateNamespacedGetter +} + +var _ workflowtemplatepkg.WorkflowTemplateServiceClient = &OfflineWorkflowTemplateServiceClient{} + +func (o OfflineWorkflowTemplateServiceClient) CreateWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateCreateRequest, _ ...grpc.CallOption) (*v1alpha1.WorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowTemplateServiceClient) GetWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateGetRequest, _ ...grpc.CallOption) (*v1alpha1.WorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowTemplateServiceClient) ListWorkflowTemplates(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateListRequest, _ ...grpc.CallOption) (*v1alpha1.WorkflowTemplateList, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowTemplateServiceClient) UpdateWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateUpdateRequest, _ ...grpc.CallOption) (*v1alpha1.WorkflowTemplate, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowTemplateServiceClient) DeleteWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateDeleteRequest, _ ...grpc.CallOption) (*workflowtemplatepkg.WorkflowTemplateDeleteResponse, error) { + return nil, OfflineErr +} + +func (o OfflineWorkflowTemplateServiceClient) LintWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateLintRequest, _ ...grpc.CallOption) (*v1alpha1.WorkflowTemplate, error) { + err := validate.ValidateWorkflowTemplate(o.namespacedWorkflowTemplateGetterMap[req.Namespace], o.clusterWorkflowTemplateGetter, req.Template, validate.ValidateOpts{Lint: true}) + if err != nil { + return nil, err + } + return req.Template, nil +}