diff --git a/api/v1alpha1/template_types.go b/api/v1alpha1/template_types.go index 209d7c17..f8928b99 100644 --- a/api/v1alpha1/template_types.go +++ b/api/v1alpha1/template_types.go @@ -18,8 +18,8 @@ const ( // TemplateAnnKeyUserRoles is an annotation key on Template for specific UserRoles TemplateAnnKeyUserRoles = "cosmo-workspace.github.io/userroles" - // TemplateAnnKeyForbiddenUserRoles is an annotation key on Template which is not for specific UserRoles - TemplateAnnKeyForbiddenUserRoles = "cosmo-workspace.github.io/forbidden-userroles" + // TemplateAnnKeyRequiredAddons is a annotation key for Template which requires useraddons + TemplateAnnKeyRequiredAddons = "cosmo-workspace.github.io/required-useraddons" ) func init() { diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index 2a7cc735..60496953 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -29,7 +29,10 @@ const NamespaceLabelKeyUserName = "cosmo-workspace.github.io/user" const UserAddonTemplateAnnKeyDefaultUserAddon = "useraddon.cosmo-workspace.github.io/default" // Var for user addon -const TemplateVarUserName = "{{USER_NAME}}" +const ( + TemplateVarUser = "{{USER}}" + TemplateVarUserName = "{{USER_NAME}}" +) const UserNamespacePrefix = "cosmo-user-" diff --git a/hack/local-run-test/Makefile b/hack/local-run-test/Makefile index ed3cdf9c..66f101c4 100644 --- a/hack/local-run-test/Makefile +++ b/hack/local-run-test/Makefile @@ -309,6 +309,7 @@ apply-template: kubectl cosmoctl ## Apply template. for i in `ls ../../example/useraddons/*/*.yaml`; do until (kubectl apply -f $$i) do sleep 1; done; done for i in `ls ../../example/workspaces/*.yaml`; do until (kubectl apply -f $$i) do sleep 1; done; done $(MAKE) -C templates/dev-code-server apply + kubectl apply -f templates/iamserviceaccount/addon.yaml add-user: kubectl cosmoctl ## add user diff --git a/hack/local-run-test/templates/dev-code-server/Makefile b/hack/local-run-test/templates/dev-code-server/Makefile index 6d4964b6..264f97dc 100644 --- a/hack/local-run-test/templates/dev-code-server/Makefile +++ b/hack/local-run-test/templates/dev-code-server/Makefile @@ -8,19 +8,14 @@ template: ## Create template cd kubernetes/ && kustomize edit set image $(IMAGE_REPO)/$(IMAGE_NAME):$(IMAGE_TAG) kustomize build kubernetes/ | cosmoctl tmpl generate -o cosmo-template.yaml --workspace \ --required-vars CODE-SERVER_STORAGE_GB:20,DOCKER_STORAGE:20 - kustomize build kubernetes/ | cosmoctl tmpl generate -o team-a-template.yaml --workspace \ + kustomize build team-a | cosmoctl tmpl generate -o team-a-template.yaml --workspace \ --name team-a-codeserver \ --desc 'only for team A' \ --userroles 'team-a-*' \ - --required-vars CODE-SERVER_STORAGE_GB:20,DOCKER_STORAGE:20 - kustomize build kubernetes/ | cosmoctl tmpl generate -o team-shared-template.yaml --workspace \ - --name team-shared-codeserver \ - --desc 'shared template excluding team-a-dev' \ - --forbidden-userroles team-a-dev \ + --required-useraddons team-a-serviceaccount \ --required-vars CODE-SERVER_STORAGE_GB:20,DOCKER_STORAGE:20 .PHONY: apply apply: template ## Apply template kubectl apply -f cosmo-template.yaml - kubectl apply -f team-a-template.yaml - kubectl apply -f team-shared-template.yaml + kubectl apply -f team-a-template.yaml \ No newline at end of file diff --git a/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml b/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml index 568f61d4..080805dd 100644 --- a/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml +++ b/hack/local-run-test/templates/dev-code-server/cosmo-template.yaml @@ -1,4 +1,4 @@ -# Generated by cosmoctl - cosmo v0.10.0 cosmo-workspace 2023 +# Generated by cosmoctl - cosmo v1.0.0-rc2 cosmo-workspace 2023 apiVersion: cosmo-workspace.github.io/v1alpha1 kind: Template metadata: diff --git a/hack/local-run-test/templates/dev-code-server/team-a-template.yaml b/hack/local-run-test/templates/dev-code-server/team-a-template.yaml index 88e1abd1..4a24333c 100644 --- a/hack/local-run-test/templates/dev-code-server/team-a-template.yaml +++ b/hack/local-run-test/templates/dev-code-server/team-a-template.yaml @@ -1,8 +1,9 @@ -# Generated by cosmoctl - cosmo v0.10.0 cosmo-workspace 2023 +# Generated by cosmoctl - cosmo v1.0.0-rc2 cosmo-workspace 2023 apiVersion: cosmo-workspace.github.io/v1alpha1 kind: Template metadata: annotations: + cosmo-workspace.github.io/required-useraddons: team-a-serviceaccount cosmo-workspace.github.io/userroles: team-a-* workspace.cosmo-workspace.github.io/deployment: workspace workspace.cosmo-workspace.github.io/service: workspace @@ -84,7 +85,7 @@ spec: runAsUser: 1000 securityContext: fsGroup: 1000 - serviceAccountName: default + serviceAccountName: iamserviceaccount requiredVars: - default: "20" var: CODE-SERVER_STORAGE_GB diff --git a/hack/local-run-test/templates/dev-code-server/team-a/kustomization.yaml b/hack/local-run-test/templates/dev-code-server/team-a/kustomization.yaml new file mode 100644 index 00000000..b0645cde --- /dev/null +++ b/hack/local-run-test/templates/dev-code-server/team-a/kustomization.yaml @@ -0,0 +1,10 @@ +resources: +- ../kubernetes + +patches: +- target: + kind: Deployment + patch: | + - op: replace + path: /spec/template/spec/serviceAccountName + value: iamserviceaccount \ No newline at end of file diff --git a/hack/local-run-test/templates/dev-code-server/team-shared-template.yaml b/hack/local-run-test/templates/dev-code-server/team-shared-template.yaml deleted file mode 100644 index 37c5b2e8..00000000 --- a/hack/local-run-test/templates/dev-code-server/team-shared-template.yaml +++ /dev/null @@ -1,92 +0,0 @@ -# Generated by cosmoctl - cosmo v0.10.0 cosmo-workspace 2023 -apiVersion: cosmo-workspace.github.io/v1alpha1 -kind: Template -metadata: - annotations: - cosmo-workspace.github.io/forbidden-userroles: team-a-dev - workspace.cosmo-workspace.github.io/deployment: workspace - workspace.cosmo-workspace.github.io/service: workspace - workspace.cosmo-workspace.github.io/service-main-port: main - creationTimestamp: null - labels: - cosmo-workspace.github.io/type: workspace - name: team-shared-codeserver -spec: - description: shared template excluding team-a-dev - rawYaml: | - apiVersion: v1 - kind: Service - metadata: - labels: - cosmo-workspace.github.io/instance: '{{INSTANCE}}' - cosmo-workspace.github.io/template: '{{TEMPLATE}}' - name: '{{INSTANCE}}-workspace' - namespace: '{{NAMESPACE}}' - spec: - ports: - - name: main - port: 18080 - protocol: TCP - selector: - cosmo-workspace.github.io/instance: '{{INSTANCE}}' - cosmo-workspace.github.io/template: '{{TEMPLATE}}' - type: ClusterIP - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - cosmo-workspace.github.io/instance: '{{INSTANCE}}' - cosmo-workspace.github.io/template: '{{TEMPLATE}}' - name: '{{INSTANCE}}-workspace' - namespace: '{{NAMESPACE}}' - spec: - replicas: 1 - selector: - matchLabels: - cosmo-workspace.github.io/instance: '{{INSTANCE}}' - cosmo-workspace.github.io/template: '{{TEMPLATE}}' - strategy: - type: Recreate - template: - metadata: - labels: - cosmo-workspace.github.io/instance: '{{INSTANCE}}' - cosmo-workspace.github.io/template: '{{TEMPLATE}}' - spec: - containers: - - command: - - sh - - -c - - | - /usr/bin/entrypoint.sh --log debug --auth=none --bind-addr 0.0.0.0:18080 --app-name=cosmo . - image: ghcr.io/cosmo-workspace/dev-code-server:v0.0.2-4.13.0 - imagePullPolicy: Always - livenessProbe: - httpGet: - path: / - port: 18080 - name: code-server - ports: - - containerPort: 18080 - name: main - protocol: TCP - readinessProbe: - httpGet: - path: / - port: 18080 - resources: - limits: - memory: 6Gi - requests: - memory: 100Mi - securityContext: - runAsUser: 1000 - securityContext: - fsGroup: 1000 - serviceAccountName: default - requiredVars: - - default: "20" - var: CODE-SERVER_STORAGE_GB - - default: "20" - var: DOCKER_STORAGE diff --git a/hack/local-run-test/templates/iamserviceaccount/Makefile b/hack/local-run-test/templates/iamserviceaccount/Makefile new file mode 100644 index 00000000..8d98092e --- /dev/null +++ b/hack/local-run-test/templates/iamserviceaccount/Makefile @@ -0,0 +1,11 @@ +all: template + +.PHONY: template +template: + kustomize build kubernetes | cosmoctl tmpl gen --useraddon \ + --name team-a-serviceaccount \ + --userroles=team-a-* \ + --disable-nameprefix \ + --cluster-scope \ + --required-useraddons resource-limitter \ + -o addon.yaml diff --git a/hack/local-run-test/templates/iamserviceaccount/addon.yaml b/hack/local-run-test/templates/iamserviceaccount/addon.yaml new file mode 100644 index 00000000..b618c6f9 --- /dev/null +++ b/hack/local-run-test/templates/iamserviceaccount/addon.yaml @@ -0,0 +1,38 @@ +# Generated by cosmoctl - cosmo v1.0.0-rc2 cosmo-workspace 2023 +apiVersion: cosmo-workspace.github.io/v1alpha1 +kind: ClusterTemplate +metadata: + annotations: + cosmo-workspace.github.io/disable-nameprefix: "true" + cosmo-workspace.github.io/required-useraddons: resource-limitter + cosmo-workspace.github.io/userroles: team-a-* + creationTimestamp: null + labels: + cosmo-workspace.github.io/type: useraddon + name: team-a-serviceaccount +spec: + rawYaml: | + apiVersion: v1 + kind: ServiceAccount + metadata: + labels: + cosmo-workspace.github.io/instance: '{{INSTANCE}}' + cosmo-workspace.github.io/template: '{{TEMPLATE}}' + name: iamserviceaccount + namespace: '{{NAMESPACE}}' + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + cosmo-workspace.github.io/instance: '{{INSTANCE}}' + cosmo-workspace.github.io/template: '{{TEMPLATE}}' + name: '{{USER_NAME}}-view' + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view + subjects: + - kind: ServiceAccount + name: iamserviceaccount + namespace: '{{NAMESPACE}}' diff --git a/hack/local-run-test/templates/iamserviceaccount/kubernetes/clusterrolebinding.yaml b/hack/local-run-test/templates/iamserviceaccount/kubernetes/clusterrolebinding.yaml new file mode 100644 index 00000000..a5f69844 --- /dev/null +++ b/hack/local-run-test/templates/iamserviceaccount/kubernetes/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{USER_NAME}}-view" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view +subjects: +- kind: ServiceAccount + name: iamserviceaccount + namespace: "{{NAMESPACE}}" diff --git a/hack/local-run-test/templates/iamserviceaccount/kubernetes/kustomization.yaml b/hack/local-run-test/templates/iamserviceaccount/kubernetes/kustomization.yaml new file mode 100644 index 00000000..a16f3c19 --- /dev/null +++ b/hack/local-run-test/templates/iamserviceaccount/kubernetes/kustomization.yaml @@ -0,0 +1,3 @@ +resources: +- clusterrolebinding.yaml +- serviceaccount.yaml \ No newline at end of file diff --git a/hack/local-run-test/templates/iamserviceaccount/kubernetes/serviceaccount.yaml b/hack/local-run-test/templates/iamserviceaccount/kubernetes/serviceaccount.yaml new file mode 100644 index 00000000..01f0e77f --- /dev/null +++ b/hack/local-run-test/templates/iamserviceaccount/kubernetes/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: iamserviceaccount + namespace: "{{NAMESPACE}}" diff --git a/internal/cmd/__snapshots__/template_test.snap b/internal/cmd/__snapshots__/template_test.snap index 9d576cab..9414a37e 100644 --- a/internal/cmd/__snapshots__/template_test.snap +++ b/internal/cmd/__snapshots__/template_test.snap @@ -218,14 +218,13 @@ spec: ['cosmoctl [template] [generate] ✅ success in normal context: template generate --user-addon --set-default-user-addon --disable-nameprefix 2'] SnapShot = 'success' -['cosmoctl [template] [generate] ✅ success in normal context: template generate --workspace --userroles teama-* --forbidden-userroles teama-operator,teama-testuser 1'] +['cosmoctl [template] [generate] ✅ success in normal context: template generate --workspace --userroles teama-* 1'] SnapShot = """ # Generated by cosmoctl - cosmo vX.X.X cosmo-workspace 2023 apiVersion: cosmo-workspace.github.io/v1alpha1 kind: Template metadata: annotations: - cosmo-workspace.github.io/forbidden-userroles: teama-operator,teama-testuser cosmo-workspace.github.io/userroles: teama-* workspace.cosmo-workspace.github.io/deployment: workspace workspace.cosmo-workspace.github.io/service: workspace @@ -328,7 +327,7 @@ spec: """ -['cosmoctl [template] [generate] ✅ success in normal context: template generate --workspace --userroles teama-* --forbidden-userroles teama-operator,teama-testuser 2'] +['cosmoctl [template] [generate] ✅ success in normal context: template generate --workspace --userroles teama-* 2'] SnapShot = 'success' ['cosmoctl [template] [generate] ✅ success in normal context: template generate --workspace --workspace-main-service-port-name main --required-vars HOGE:HOGEHOGE,FUGA:FUGAFUGA 1'] @@ -466,15 +465,15 @@ Flags: --cluster-scope generate ClusterTemplate (default generate namespaced Template) --desc string template description --disable-nameprefix disable adding instance name prefix on child resource name - --forbidden-userroles string user roles NOT to show this template (e.g. 'teama-*', 'teamb-admin', etc.) -h, --help help for generate -n, --name string template name (use directory name if not specified) -o, --output string write output into file (default: Stdout) - --required-vars string template custom vars to be replaced by instance. format --required-vars VAR1,VAR2:default-value + --required-useraddons strings required user addons + --required-vars strings template custom vars to be replaced by instance. format --required-vars VAR1,VAR2:default-value --set-default-user-addon set default user addon --user-addon template as type useraddon --useraddon template as type useraddon - --userroles string user roles to show this template (e.g. 'teama-*', 'teamb-admin', etc.) + --userroles strings user roles to show this template (e.g. 'teama-*', 'teamb-admin', etc.) --workspace template as type workspace --workspace-deployment-name string Deployment name for Workspace. use with --workspace (auto detected if not specified) --workspace-main-service-port-name string ServicePort name for Workspace main container port. use with --workspace (auto detected if not specified) @@ -492,9 +491,9 @@ SnapShot = 'validation error: --workspace and --user-addon cannot be specified c ['cosmoctl [template] [get] ✅ success in normal context: template get --workspace 1'] SnapShot = """ -NAME REQUIRED-VARS DEPLOYMENT/SERVICE -template1 {{HOGE}},{{FUGA}} /workspace -template2 {{HOGE}},{{FUGA}} /workspace +NAME REQUIRED_VARS USERROLE REQUIRED_ADDONS +template1 {{HOGE}},{{FUGA}} +template2 {{HOGE}},{{FUGA}} """ ['cosmoctl [template] [get] ✅ success in normal context: template get --workspace 2'] @@ -502,11 +501,11 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get 1'] SnapShot = """ -NAME REQUIREDVARS TYPE ISCLUSTERSCOPE FORROLE FORBIDDENROLE -template1 {{HOGE}},{{FUGA}} workspace false -template2 {{HOGE}},{{FUGA}} workspace false -template3 {{HOGE}},{{FUGA}} useraddon false -cluster-template1 {{HOGE}},{{FUGA}} useraddon true +TYPE NAME CLUSTERSCOPE REQUIRED_VARS DEFAULT USERROLE REQUIRED_ADDONS +workspace template1 false {{HOGE}},{{FUGA}} true +workspace template2 false {{HOGE}},{{FUGA}} true +useraddon template3 false {{HOGE}},{{FUGA}} true +useraddon cluster-template1 true {{HOGE}},{{FUGA}} """ ['cosmoctl [template] [get] ✅ success in normal context: template get 2'] @@ -514,7 +513,7 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get notfound 1'] SnapShot = """ -NAME REQUIREDVARS TYPE ISCLUSTERSCOPE FORROLE FORBIDDENROLE +TYPE NAME CLUSTERSCOPE REQUIRED_VARS DEFAULT USERROLE REQUIRED_ADDONS """ ['cosmoctl [template] [get] ✅ success in normal context: template get notfound 2'] @@ -522,8 +521,8 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get template2 --workspace 1'] SnapShot = """ -NAME REQUIRED-VARS DEPLOYMENT/SERVICE -template2 {{HOGE}},{{FUGA}} /workspace +NAME REQUIRED_VARS USERROLE REQUIRED_ADDONS +template2 {{HOGE}},{{FUGA}} """ ['cosmoctl [template] [get] ✅ success in normal context: template get template2 --workspace 2'] @@ -531,8 +530,8 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get template2 1'] SnapShot = """ -NAME REQUIREDVARS TYPE ISCLUSTERSCOPE FORROLE FORBIDDENROLE -template2 {{HOGE}},{{FUGA}} workspace false +TYPE NAME CLUSTERSCOPE REQUIRED_VARS DEFAULT USERROLE REQUIRED_ADDONS +workspace template2 false {{HOGE}},{{FUGA}} true """ ['cosmoctl [template] [get] ✅ success in normal context: template get template2 2'] @@ -540,9 +539,9 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get template2 cluster-template1 notfound 1'] SnapShot = """ -NAME REQUIREDVARS TYPE ISCLUSTERSCOPE FORROLE FORBIDDENROLE -template2 {{HOGE}},{{FUGA}} workspace false -cluster-template1 {{HOGE}},{{FUGA}} useraddon true +TYPE NAME CLUSTERSCOPE REQUIRED_VARS DEFAULT USERROLE REQUIRED_ADDONS +workspace template2 false {{HOGE}},{{FUGA}} true +useraddon cluster-template1 true {{HOGE}},{{FUGA}} """ ['cosmoctl [template] [get] ✅ success in normal context: template get template2 cluster-template1 notfound 2'] @@ -550,9 +549,9 @@ SnapShot = 'success' ['cosmoctl [template] [get] ✅ success in normal context: template get template2 template3 1'] SnapShot = """ -NAME REQUIREDVARS TYPE ISCLUSTERSCOPE FORROLE FORBIDDENROLE -template2 {{HOGE}},{{FUGA}} workspace false -template3 {{HOGE}},{{FUGA}} useraddon false +TYPE NAME CLUSTERSCOPE REQUIRED_VARS DEFAULT USERROLE REQUIRED_ADDONS +workspace template2 false {{HOGE}},{{FUGA}} true +useraddon template3 false {{HOGE}},{{FUGA}} true """ ['cosmoctl [template] [get] ✅ success in normal context: template get template2 template3 2'] @@ -566,6 +565,7 @@ Usage: Flags: -h, --help help for get + --useraddon show type useraddon template --workspace show type workspace template Global Flags: @@ -586,6 +586,7 @@ Usage: Flags: -h, --help help for get + --useraddon show type useraddon template --workspace show type workspace template Global Flags: diff --git a/internal/cmd/template/generate.go b/internal/cmd/template/generate.go index a7149849..178a93b4 100644 --- a/internal/cmd/template/generate.go +++ b/internal/cmd/template/generate.go @@ -32,7 +32,7 @@ type generateOption struct { Name string OutputFile string - RequiredVars string + RequiredVars []string Desc string TypeWorkspace bool @@ -41,8 +41,8 @@ type generateOption struct { SetDefaultUserAddon bool DisableNamePrefix bool ClusterScope bool - UserRoles string - ForbiddenUserRoles string + UserRoles []string + RequiredUserAddons []string tmpl cosmov1alpha1.TemplateObject } @@ -54,7 +54,7 @@ func generateCmd(cmd *cobra.Command, cliOpt *cmdutil.CliOptions) *cobra.Command cmd.Flags().StringVarP(&o.Name, "name", "n", "", "template name (use directory name if not specified)") cmd.Flags().StringVarP(&o.OutputFile, "output", "o", "", "write output into file (default: Stdout)") - cmd.Flags().StringVar(&o.RequiredVars, "required-vars", "", "template custom vars to be replaced by instance. format --required-vars VAR1,VAR2:default-value") + cmd.Flags().StringSliceVar(&o.RequiredVars, "required-vars", []string{}, "template custom vars to be replaced by instance. format --required-vars VAR1,VAR2:default-value") cmd.Flags().StringVar(&o.Desc, "desc", "", "template description") cmd.Flags().BoolVar(&o.TypeWorkspace, "workspace", false, "template as type workspace") @@ -68,8 +68,9 @@ func generateCmd(cmd *cobra.Command, cliOpt *cmdutil.CliOptions) *cobra.Command cmd.Flags().BoolVar(&o.DisableNamePrefix, "disable-nameprefix", false, "disable adding instance name prefix on child resource name") cmd.Flags().BoolVar(&o.ClusterScope, "cluster-scope", false, "generate ClusterTemplate (default generate namespaced Template)") - cmd.Flags().StringVar(&o.UserRoles, "userroles", "", "user roles to show this template (e.g. 'teama-*', 'teamb-admin', etc.)") - cmd.Flags().StringVar(&o.ForbiddenUserRoles, "forbidden-userroles", "", "user roles NOT to show this template (e.g. 'teama-*', 'teamb-admin', etc.)") + cmd.Flags().StringSliceVar(&o.UserRoles, "userroles", []string{}, "user roles to show this template (e.g. 'teama-*', 'teamb-admin', etc.)") + + cmd.Flags().StringSliceVar(&o.RequiredUserAddons, "required-useraddons", []string{}, "required user addons") return cmd } @@ -127,11 +128,9 @@ func (o *generateOption) Complete(cmd *cobra.Command, args []string) error { } } - if o.RequiredVars != "" { - varsList := strings.Split(o.RequiredVars, ",") - - vars := make([]cosmov1alpha1.RequiredVarSpec, 0, len(varsList)) - for _, v := range varsList { + if len(o.RequiredVars) > 0 { + vars := make([]cosmov1alpha1.RequiredVarSpec, 0, len(o.RequiredVars)) + for _, v := range o.RequiredVars { vcol := strings.Split(v, ":") varSpec := cosmov1alpha1.RequiredVarSpec{Var: vcol[0]} if len(vcol) > 1 { @@ -170,11 +169,11 @@ func (o *generateOption) Complete(cmd *cobra.Command, args []string) error { } } - if o.UserRoles != "" { - ann[cosmov1alpha1.TemplateAnnKeyUserRoles] = o.UserRoles + if len(o.UserRoles) > 0 { + ann[cosmov1alpha1.TemplateAnnKeyUserRoles] = strings.Join(o.UserRoles, ",") } - if o.ForbiddenUserRoles != "" { - ann[cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles] = o.ForbiddenUserRoles + if len(o.RequiredUserAddons) > 0 { + ann[cosmov1alpha1.TemplateAnnKeyRequiredAddons] = strings.Join(o.RequiredUserAddons, ",") } o.tmpl.SetAnnotations(ann) diff --git a/internal/cmd/template/get.go b/internal/cmd/template/get.go index 51f89ba3..cd05a4aa 100644 --- a/internal/cmd/template/get.go +++ b/internal/cmd/template/get.go @@ -14,13 +14,13 @@ import ( cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" "github.com/cosmo-workspace/cosmo/pkg/cmdutil" "github.com/cosmo-workspace/cosmo/pkg/kubeutil" - "github.com/cosmo-workspace/cosmo/pkg/workspace" ) type GetOption struct { *cmdutil.CliOptions TemplateNames []string TypeWorkspace bool + TypeUserAddon bool tmpltype string } @@ -30,6 +30,7 @@ func GetCmd(cmd *cobra.Command, cliOpt *cmdutil.CliOptions) *cobra.Command { cmd.PersistentPreRunE = o.PreRunE cmd.RunE = cmdutil.RunEHandler(o.RunE) cmd.PersistentFlags().BoolVar(&o.TypeWorkspace, "workspace", false, "show type workspace template") + cmd.PersistentFlags().BoolVar(&o.TypeUserAddon, "useraddon", false, "show type useraddon template") return cmd } @@ -49,6 +50,8 @@ func (o *GetOption) Validate(cmd *cobra.Command, args []string) error { } if o.TypeWorkspace { o.tmpltype = cosmov1alpha1.TemplateLabelEnumTypeWorkspace + } else if o.TypeUserAddon { + o.tmpltype = cosmov1alpha1.TemplateLabelEnumTypeUserAddon } return nil } @@ -104,29 +107,50 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error { switch o.tmpltype { case cosmov1alpha1.TemplateLabelEnumTypeWorkspace: - columnNames := []string{"NAME", "REQUIRED-VARS", "DEPLOYMENT/SERVICE"} + columnNames := []string{"NAME", "REQUIRED_VARS", "USERROLE", "REQUIRED_ADDONS"} fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")) for _, v := range tmpls { - cfg, err := workspace.ConfigFromTemplateAnnotations(v.(*cosmov1alpha1.Template)) - if err != nil { - o.Logr.Error(err, "failed to get workspace config", "template", v.GetName()) - continue + vars := make([]string, 0, len(v.GetSpec().RequiredVars)) + for _, t := range v.GetSpec().RequiredVars { + vars = append(vars, t.Var) + } + rawTmplVars := strings.Join(vars, ",") + + var forRoles, requiredAddons string + ann := v.GetAnnotations() + if ann != nil { + forRoles = ann[cosmov1alpha1.TemplateAnnKeyUserRoles] + requiredAddons = ann[cosmov1alpha1.TemplateAnnKeyRequiredAddons] } + rowdata := []string{v.GetName(), rawTmplVars, forRoles, requiredAddons} + fmt.Fprintf(w, "%s\n", strings.Join(rowdata, "\t")) + } + + case cosmov1alpha1.TemplateLabelEnumTypeUserAddon: + columnNames := []string{"NAME", "REQUIRED_VARS", "CLUSTERSCOPE", "DEFAULT", "USERROLE"} + fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")) + + for _, v := range tmpls { vars := make([]string, 0, len(v.GetSpec().RequiredVars)) for _, t := range v.GetSpec().RequiredVars { vars = append(vars, t.Var) } rawTmplVars := strings.Join(vars, ",") - resources := fmt.Sprintf("%s/%s", cfg.DeploymentName, cfg.ServiceName) - rowdata := []string{v.GetName(), rawTmplVars, resources} + var isDefault, forRoles string + ann := v.GetAnnotations() + if ann != nil { + isDefault = ann[cosmov1alpha1.UserAddonTemplateAnnKeyDefaultUserAddon] + forRoles = ann[cosmov1alpha1.TemplateAnnKeyUserRoles] + } + rowdata := []string{v.GetName(), rawTmplVars, strconv.FormatBool(v.GetScope() == meta.RESTScopeRoot), isDefault, forRoles} fmt.Fprintf(w, "%s\n", strings.Join(rowdata, "\t")) } default: - columnNames := []string{"NAME", "REQUIREDVARS", "TYPE", "ISCLUSTERSCOPE", "FORROLE", "FORBIDDENROLE"} + columnNames := []string{"TYPE", "NAME", "CLUSTERSCOPE", "REQUIRED_VARS", "DEFAULT", "USERROLE", "REQUIRED_ADDONS"} fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t")) for _, v := range tmpls { @@ -136,15 +160,16 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error { } rawTmplVars := strings.Join(vars, ",") - var forRoles, forbiddenRoles string + var isDefault, forRoles, requiredAddons string ann := v.GetAnnotations() if ann != nil { + isDefault = ann[cosmov1alpha1.UserAddonTemplateAnnKeyDefaultUserAddon] forRoles = ann[cosmov1alpha1.TemplateAnnKeyUserRoles] - forbiddenRoles = ann[cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles] + requiredAddons = ann[cosmov1alpha1.TemplateAnnKeyRequiredAddons] } tmplType := v.GetLabels()[cosmov1alpha1.TemplateLabelKeyType] - rowdata := []string{v.GetName(), rawTmplVars, tmplType, strconv.FormatBool(v.GetScope() == meta.RESTScopeRoot), forRoles, forbiddenRoles} + rowdata := []string{tmplType, v.GetName(), strconv.FormatBool(v.GetScope() == meta.RESTScopeRoot), rawTmplVars, isDefault, forRoles, requiredAddons} fmt.Fprintf(w, "%s\n", strings.Join(rowdata, "\t")) } } diff --git a/internal/cmd/template_test.go b/internal/cmd/template_test.go index 52a88a22..43f8d61f 100644 --- a/internal/cmd/template_test.go +++ b/internal/cmd/template_test.go @@ -101,7 +101,7 @@ var _ = Describe("cosmoctl [template]", func() { Entry(desc, "template", "generate", "--workspace", "--workspace-main-service-port-name", "main", "-o", "/tmp/test-cosmo-template"), Entry(desc, "template", "generate", "--user-addon", "--set-default-user-addon", "--disable-nameprefix"), Entry(desc, "template", "generate", "--user-addon", "--set-default-user-addon", "--cluster-scope", "--disable-nameprefix"), - Entry(desc, "template", "generate", "--workspace", "--userroles", "teama-*", "--forbidden-userroles", "teama-operator,teama-testuser"), + Entry(desc, "template", "generate", "--workspace", "--userroles", "teama-*"), ) DescribeTable("❌ fail with invalid args:", diff --git a/internal/dashboard/__snapshots__/template_handler_test.snap b/internal/dashboard/__snapshots__/template_handler_test.snap index c9e80bf7..5738e87a 100644 --- a/internal/dashboard/__snapshots__/template_handler_test.snap +++ b/internal/dashboard/__snapshots__/template_handler_test.snap @@ -1,11 +1,11 @@ -['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: admin-user, empty 1'] +['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: admin-user, empty, 1'] SnapShot = """ { \"message\": \"No items found\" } """ -['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: admin-user, not empty 1'] +['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: admin-user, not empty, 1'] SnapShot = """ { \"items\": [ @@ -41,14 +41,14 @@ SnapShot = """ } """ -['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: normal-user, empty 1'] +['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: normal-user, empty, 1'] SnapShot = """ { \"message\": \"No items found\" } """ -['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: normal-user, not empty 1'] +['Dashboard server [Template] [GetUserAddonTemplates] ✅ success in normal context: Entry: normal-user, not empty, 1'] SnapShot = """ { \"items\": [ @@ -84,17 +84,17 @@ SnapShot = """ } """ -['Dashboard server [Template] [GetUserAddonTemplates] ❌ fail with an unexpected error at list: Entry: admin-user, not empty 1'] +['Dashboard server [Template] [GetUserAddonTemplates] ❌ fail with an unexpected error at list: Entry: admin-user, not empty, 1'] SnapShot = 'internal: failed to list UserAddon Templates' -['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: admin-user, empty 1'] +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: admin-user, empty, 1'] SnapShot = """ { \"message\": \"No items found\" } """ -['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: admin-user, not empty 1'] +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: admin-user, not empty, 1'] SnapShot = """ { \"items\": [ @@ -123,19 +123,93 @@ SnapShot = """ \"default_value\": \"FUGAfuga\" } ] + }, + { + \"name\": \"template3\" } ] } """ -['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: normal-user, empty 1'] +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: normal-user, empty, 1'] SnapShot = """ { \"message\": \"No items found\" } """ -['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: normal-user, not empty 1'] +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: normal-user, not empty, 1'] +SnapShot = """ +{ + \"items\": [ + { + \"name\": \"template1\", + \"required_vars\": [ + { + \"var_name\": \"{{HOGE}}\", + \"default_value\": \"HOGEhoge\" + }, + { + \"var_name\": \"{{FUGA}}\", + \"default_value\": \"FUGAfuga\" + } + ] + }, + { + \"name\": \"template2\", + \"required_vars\": [ + { + \"var_name\": \"{{HOGE}}\", + \"default_value\": \"HOGEhoge\" + }, + { + \"var_name\": \"{{FUGA}}\", + \"default_value\": \"FUGAfuga\" + } + ] + }, + { + \"name\": \"template3\" + } + ] +} +""" + +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: normal-user, not empty, use_role_filter:true 1'] +SnapShot = """ +{ + \"items\": [ + { + \"name\": \"template1\", + \"required_vars\": [ + { + \"var_name\": \"{{HOGE}}\", + \"default_value\": \"HOGEhoge\" + }, + { + \"var_name\": \"{{FUGA}}\", + \"default_value\": \"FUGAfuga\" + } + ] + }, + { + \"name\": \"template2\", + \"required_vars\": [ + { + \"var_name\": \"{{HOGE}}\", + \"default_value\": \"HOGEhoge\" + }, + { + \"var_name\": \"{{FUGA}}\", + \"default_value\": \"FUGAfuga\" + } + ] + } + ] +} +""" + +['Dashboard server [Template] [GetWorkspaceTemplates] ✅ success in normal context: Entry: role-user, not empty, 1'] SnapShot = """ { \"items\": [ @@ -164,10 +238,13 @@ SnapShot = """ \"default_value\": \"FUGAfuga\" } ] + }, + { + \"name\": \"template3\" } ] } """ -['Dashboard server [Template] [GetWorkspaceTemplates] ❌ fail with an unexpected error at list: Entry: admin-user, not empty 1'] +['Dashboard server [Template] [GetWorkspaceTemplates] ❌ fail with an unexpected error at list: Entry: admin-user, not empty, 1'] SnapShot = 'internal: failed to list WorkspaceTemplates' diff --git a/internal/dashboard/auth_middleware.go b/internal/dashboard/auth_middleware.go index 710fe66c..7516e745 100644 --- a/internal/dashboard/auth_middleware.go +++ b/internal/dashboard/auth_middleware.go @@ -198,6 +198,21 @@ func validateCallerHasAdminForAllRoles(tryRoleNames []string) func(map[string]st } } +func validateCallerHasAdminForAtLeastOneRole(tryRoleNames []cosmov1alpha1.UserRole) func(map[string]string) error { + return func(callerGroupRoleMap map[string]string) error { + for _, r := range tryRoleNames { + tryAccessGroup, _ := r.GetGroupAndRole() + callerRoleForTriedGroup := callerGroupRoleMap[tryAccessGroup] + + // Allow if caller has at least one administrative privilege for tried group. + if callerRoleForTriedGroup == cosmov1alpha1.AdminRoleName { + return nil + } + } + return fmt.Errorf("denied to access") + } +} + var passAllAdmin = func(map[string]string) error { return nil } diff --git a/internal/dashboard/template_handler.go b/internal/dashboard/template_handler.go index f6cc3fb8..41db6438 100644 --- a/internal/dashboard/template_handler.go +++ b/internal/dashboard/template_handler.go @@ -4,14 +4,16 @@ import ( "context" "net/http" "strconv" + "strings" connect_go "github.com/bufbuild/connect-go" - "google.golang.org/protobuf/types/known/emptypb" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/utils/pointer" cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" "github.com/cosmo-workspace/cosmo/pkg/clog" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" + "github.com/cosmo-workspace/cosmo/pkg/kubeutil" dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect" ) @@ -24,16 +26,20 @@ func (s *Server) TemplateServiceHandler(mux *http.ServeMux) { mux.Handle(path, s.contextMiddleware(handler)) } -func (s *Server) GetWorkspaceTemplates(ctx context.Context, req *connect_go.Request[emptypb.Empty]) (*connect_go.Response[dashv1alpha1.GetWorkspaceTemplatesResponse], error) { +func (s *Server) GetWorkspaceTemplates(ctx context.Context, req *connect_go.Request[dashv1alpha1.GetWorkspaceTemplatesRequest]) (*connect_go.Response[dashv1alpha1.GetWorkspaceTemplatesResponse], error) { log := clog.FromContext(ctx).WithCaller() user := callerFromContext(ctx) - tmpls, err := s.Klient.ListWorkspaceTemplates(ctx, user.Spec.Roles) + tmpls, err := s.Klient.ListWorkspaceTemplates(ctx) if err != nil { return nil, ErrResponse(log, err) } + if req.Msg.UseRoleFilter != nil && *req.Msg.UseRoleFilter { + tmpls = kosmo.FilterTemplates(ctx, tmpls, user) + } + addonTmpls := make([]*dashv1alpha1.Template, 0, len(tmpls)) for _, v := range tmpls { addonTmpls = append(addonTmpls, convertTemplateToDashv1alpha1Template(v)) @@ -50,16 +56,20 @@ func (s *Server) GetWorkspaceTemplates(ctx context.Context, req *connect_go.Requ return connect_go.NewResponse(res), nil } -func (s *Server) GetUserAddonTemplates(ctx context.Context, req *connect_go.Request[emptypb.Empty]) (*connect_go.Response[dashv1alpha1.GetUserAddonTemplatesResponse], error) { +func (s *Server) GetUserAddonTemplates(ctx context.Context, req *connect_go.Request[dashv1alpha1.GetUserAddonTemplatesRequest]) (*connect_go.Response[dashv1alpha1.GetUserAddonTemplatesResponse], error) { log := clog.FromContext(ctx).WithCaller() user := callerFromContext(ctx) - tmpls, err := s.Klient.ListUserAddonTemplates(ctx, user.Spec.Roles) + tmpls, err := s.Klient.ListUserAddonTemplates(ctx) if err != nil { return nil, ErrResponse(log, err) } + if req.Msg.UseRoleFilter != nil && *req.Msg.UseRoleFilter { + tmpls = kosmo.FilterTemplates(ctx, tmpls, user) + } + addonTmpls := make([]*dashv1alpha1.Template, len(tmpls)) for i, v := range tmpls { tmpl := convertTemplateToDashv1alpha1Template(v) @@ -100,5 +110,12 @@ func convertTemplateToDashv1alpha1Template(tmpl cosmov1alpha1.TemplateObject) *d Description: tmpl.GetSpec().Description, RequiredVars: requiredVars, IsClusterScope: tmpl.GetScope() == meta.RESTScopeRoot, + RequiredUseraddons: func() []string { + requiredAddons := kubeutil.GetAnnotation(tmpl, cosmov1alpha1.TemplateAnnKeyRequiredAddons) + if requiredAddons != "" { + return strings.Split(requiredAddons, ",") + } + return nil + }(), } } diff --git a/internal/dashboard/template_handler_test.go b/internal/dashboard/template_handler_test.go index b0c29dd0..38a97f1b 100644 --- a/internal/dashboard/template_handler_test.go +++ b/internal/dashboard/template_handler_test.go @@ -8,23 +8,25 @@ import ( . "github.com/cosmo-workspace/cosmo/pkg/snap" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "google.golang.org/protobuf/types/known/emptypb" + "k8s.io/utils/pointer" cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" + dashboardv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect" ) var _ = Describe("Dashboard server [Template]", func() { var ( - userSession string - adminSession string - client dashboardv1alpha1connect.TemplateServiceClient + userSession string + roleUserSession string + adminSession string + client dashboardv1alpha1connect.TemplateServiceClient ) BeforeEach(func() { userSession = test_CreateLoginUserSession("normal-user", "お名前", nil, "password") + roleUserSession = test_CreateLoginUserSession("role-user", "お名前", []cosmov1alpha1.UserRole{{Name: "my-role"}}, "password") adminSession = test_CreateLoginUserSession("admin-user", "アドミン", []cosmov1alpha1.UserRole{cosmov1alpha1.PrivilegedRole}, "password") client = dashboardv1alpha1connect.NewTemplateServiceClient(http.DefaultClient, "http://localhost:8888") }) @@ -37,18 +39,21 @@ var _ = Describe("Dashboard server [Template]", func() { Describe("[GetWorkspaceTemplates]", func() { - run_test := func(loginUser string, testCase string) { + run_test := func(loginUser string, testCase string, req *dashboardv1alpha1.GetWorkspaceTemplatesRequest) { if testCase == "not empty" { testUtil.CreateTemplate(cosmov1alpha1.TemplateLabelEnumTypeWorkspace, "template1") testUtil.CreateTemplate(cosmov1alpha1.TemplateLabelEnumTypeWorkspace, "template2") + testUtil.CreateTemplateForUserRole(cosmov1alpha1.TemplateLabelEnumTypeWorkspace, "template3", "my-role") } session := userSession if loginUser == "admin-user" { session = adminSession + } else if loginUser == "role-user" { + session = roleUserSession } By("---------------test start----------------") ctx := context.Background() - res, err := client.GetWorkspaceTemplates(ctx, NewRequestWithSession(&emptypb.Empty{}, session)) + res, err := client.GetWorkspaceTemplates(ctx, NewRequestWithSession(req, session)) if err == nil { Ω(res.Msg).To(MatchSnapShot()) } else { @@ -60,24 +65,26 @@ var _ = Describe("Dashboard server [Template]", func() { DescribeTable("✅ success in normal context:", run_test, - Entry(nil, "admin-user", "empty"), - Entry(nil, "admin-user", "not empty"), - Entry(nil, "normal-user", "empty"), - Entry(nil, "normal-user", "not empty"), + Entry(nil, "admin-user", "empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), + Entry(nil, "admin-user", "not empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), + Entry(nil, "normal-user", "empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), + Entry(nil, "normal-user", "not empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), + Entry(nil, "normal-user", "not empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{UseRoleFilter: pointer.Bool(true)}), + Entry(nil, "role-user", "not empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), ) DescribeTable("❌ fail with an unexpected error at list:", - func(user string, testCase string) { + func(user string, testCase string, req *dashboardv1alpha1.GetWorkspaceTemplatesRequest) { clientMock.SetListError((*Server).GetWorkspaceTemplates, errors.New("template list error")) - run_test(user, testCase) + run_test(user, testCase, req) }, - Entry(nil, "admin-user", "not empty"), + Entry(nil, "admin-user", "not empty", &dashboardv1alpha1.GetWorkspaceTemplatesRequest{}), ) }) Describe("[GetUserAddonTemplates]", func() { - run_test := func(loginUser string, testCase string) { + run_test := func(loginUser string, testCase string, req *dashboardv1alpha1.GetUserAddonTemplatesRequest) { if testCase == "not empty" { testUtil.CreateTemplate(cosmov1alpha1.TemplateLabelEnumTypeUserAddon, "useraddon1") testUtil.CreateTemplate(cosmov1alpha1.TemplateLabelEnumTypeUserAddon, "useraddon2") @@ -88,7 +95,7 @@ var _ = Describe("Dashboard server [Template]", func() { } By("---------------test start----------------") ctx := context.Background() - res, err := client.GetUserAddonTemplates(ctx, NewRequestWithSession(&emptypb.Empty{}, session)) + res, err := client.GetUserAddonTemplates(ctx, NewRequestWithSession(req, session)) if err == nil { Ω(res.Msg).To(MatchSnapShot()) } else { @@ -100,18 +107,18 @@ var _ = Describe("Dashboard server [Template]", func() { DescribeTable("✅ success in normal context:", run_test, - Entry(nil, "admin-user", "empty"), - Entry(nil, "admin-user", "not empty"), - Entry(nil, "normal-user", "empty"), - Entry(nil, "normal-user", "not empty"), + Entry(nil, "admin-user", "empty", &dashboardv1alpha1.GetUserAddonTemplatesRequest{}), + Entry(nil, "admin-user", "not empty", &dashboardv1alpha1.GetUserAddonTemplatesRequest{}), + Entry(nil, "normal-user", "empty", &dashboardv1alpha1.GetUserAddonTemplatesRequest{}), + Entry(nil, "normal-user", "not empty", &dashboardv1alpha1.GetUserAddonTemplatesRequest{}), ) DescribeTable("❌ fail with an unexpected error at list:", - func(user string, testCase string) { + func(user string, testCase string, req *dashboardv1alpha1.GetUserAddonTemplatesRequest) { clientMock.SetListError((*Server).GetUserAddonTemplates, errors.New("template list error")) - run_test(user, testCase) + run_test(user, testCase, req) }, - Entry(nil, "admin-user", "not empty"), + Entry(nil, "admin-user", "not empty", &dashboardv1alpha1.GetUserAddonTemplatesRequest{}), ) }) }) diff --git a/internal/dashboard/user_sub_handler.go b/internal/dashboard/user_sub_handler.go index 444770b3..33fc92c1 100644 --- a/internal/dashboard/user_sub_handler.go +++ b/internal/dashboard/user_sub_handler.go @@ -2,11 +2,15 @@ package dashboard import ( "context" + "fmt" + "reflect" connect_go "github.com/bufbuild/connect-go" + "k8s.io/apimachinery/pkg/types" "github.com/cosmo-workspace/cosmo/pkg/clog" "github.com/cosmo-workspace/cosmo/pkg/kosmo" + "github.com/cosmo-workspace/cosmo/pkg/useraddon" dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" ) @@ -14,10 +18,34 @@ func (s *Server) UpdateUserAddons(ctx context.Context, req *connect_go.Request[d log := clog.FromContext(ctx).WithCaller() log.Debug().Info("request", "req", req) + currentUser, err := s.Klient.GetUser(ctx, req.Msg.UserName) + if err != nil { + return nil, ErrResponse(log, err) + } + + // caller can attach or detach only: + // - User who have group-role which caller is admin for + // - Addons which is allowed for caller to manage + err = adminAuthentication(ctx, + validateCallerHasAdminForAtLeastOneRole(currentUser.Spec.Roles)) + if err != nil { + return nil, ErrResponse(log, err) + } + caller := callerFromContext(ctx) if caller == nil { return nil, kosmo.NewInternalServerError("unable get caller", nil) } + for _, addon := range diff(currentUser.Spec.Addons, convertDashv1alpha1UserAddonToUserAddon(req.Msg.Addons)) { + tmpl := useraddon.EmptyTemplateObject(addon) + err := s.Klient.Get(ctx, types.NamespacedName{Name: tmpl.GetName()}, tmpl) + if err != nil { + return nil, kosmo.NewInternalServerError(fmt.Sprintf("failed to fetch addon '%s'", tmpl.GetName()), nil) + } + if ok := kosmo.IsAllowedToUseTemplate(ctx, caller, tmpl); !ok { + return nil, kosmo.NewForbiddenError("no roles for addon", nil) + } + } addons := convertDashv1alpha1UserAddonToUserAddon(req.Msg.Addons) user, err := s.Klient.UpdateUser(ctx, req.Msg.UserName, kosmo.UpdateUserOpts{ @@ -56,15 +84,15 @@ func (s *Server) UpdateUserDisplayName(ctx context.Context, req *connect_go.Requ return connect_go.NewResponse(res), nil } -func diff(slice1 []string, slice2 []string) []string { - var diff []string +func diff[T any](slice1 []T, slice2 []T) []T { + var diff []T // Loop two times, first to find slice1 strings not in slice2, // second loop to find slice2 strings not in slice1 for i := 0; i < 2; i++ { for _, s1 := range slice1 { found := false for _, s2 := range slice2 { - if s1 == s2 { + if reflect.DeepEqual(s1, s2) { found = true break } diff --git a/internal/webhooks/user_webhook.go b/internal/webhooks/user_webhook.go index 8aea562f..7fc89803 100644 --- a/internal/webhooks/user_webhook.go +++ b/internal/webhooks/user_webhook.go @@ -18,6 +18,7 @@ import ( cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" "github.com/cosmo-workspace/cosmo/pkg/clog" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" "github.com/cosmo-workspace/cosmo/pkg/kubeutil" "github.com/cosmo-workspace/cosmo/pkg/useraddon" ) @@ -161,36 +162,37 @@ func (h *UserValidationWebhookHandler) Handle(ctx context.Context, req admission // check addon template is labeled as useraddon if len(user.Spec.Addons) > 0 { for _, addon := range user.Spec.Addons { + // fetch addon template tmpl := useraddon.EmptyTemplateObject(addon) if tmpl == nil { continue } - err = h.Client.Get(ctx, types.NamespacedName{Name: tmpl.GetName()}, tmpl) if err != nil { log.Error(err, "failed to create addon", "user", user.Name, "addon", tmpl.GetName()) return admission.Denied(fmt.Sprintf("failed to create addon %s :%v", tmpl.GetName(), err)) } - // check label - label := tmpl.GetLabels() - if label == nil { + // check if template type is useraddon + typ := kubeutil.GetLabel(tmpl, cosmov1alpha1.TemplateLabelKeyType) + if typ != cosmov1alpha1.TemplateLabelEnumTypeUserAddon { log.Info("template is not labeled as useraddon", "user", user.Name, "addon", tmpl.GetName()) return admission.Denied(fmt.Sprintf("failed to create addon %s: template is not labeled as useraddon", tmpl.GetName())) } - if t, ok := label[cosmov1alpha1.TemplateLabelKeyType]; !ok || t != cosmov1alpha1.TemplateLabelEnumTypeUserAddon { - log.Info("template is not labeled as useraddon", "user", user.Name, "addon", tmpl.GetName()) - return admission.Denied(fmt.Sprintf("failed to create addon %s: template is not labeled as useraddon", tmpl.GetName())) + + // check user has role for the addon + if ok := kosmo.IsAllowedToUseTemplate(ctx, user, tmpl); !ok { + requiredRoles := kubeutil.GetAnnotation(tmpl, cosmov1alpha1.TemplateAnnKeyUserRoles) + log.Info("user has no valid roles for template", "user", user.Name, "addon", tmpl.GetName(), "requiredRoles", requiredRoles) + return admission.Denied(fmt.Sprintf("addon '%s' is only for roles '%s'", tmpl.GetName(), requiredRoles)) } - // TODO - // // dryrun create or update addon - // inst := useraddon.EmptyInstanceObject(addon, user.GetName()) - // if _, err := kubeutil.DryrunCreateOrUpdate(ctx, h.Client, inst, func() error { - // return useraddon.PatchUserAddonInstanceAsDesired(inst, addon, *user, nil) - // }); err != nil { - // return admission.Denied(fmt.Sprintf("failed to create or update addon %v", err)) - // } + // check user has required addon + if ok := kosmo.HasRequiredAddons(ctx, user, tmpl); !ok { + requiredAddons := kubeutil.GetAnnotation(tmpl, cosmov1alpha1.TemplateAnnKeyRequiredAddons) + log.Info("user does not have required addons for template", "user", user.Name, "addon", tmpl.GetName(), "requiredAddons", requiredAddons) + return admission.Denied(fmt.Sprintf("addon '%s' requires addon '%s'", tmpl.GetName(), requiredAddons)) + } } } diff --git a/internal/webhooks/user_webhook_test.go b/internal/webhooks/user_webhook_test.go index 4cd84021..e4358393 100644 --- a/internal/webhooks/user_webhook_test.go +++ b/internal/webhooks/user_webhook_test.go @@ -37,6 +37,30 @@ var _ = Describe("User webhook", func() { }, } + specificRolesUserAddon := cosmov1alpha1.Template{ + ObjectMeta: metav1.ObjectMeta{ + Name: "specific-roles-user-addon-test", + Labels: map[string]string{ + cosmov1alpha1.TemplateLabelKeyType: cosmov1alpha1.TemplateLabelEnumTypeUserAddon, + }, + Annotations: map[string]string{ + cosmov1alpha1.TemplateAnnKeyUserRoles: "specific-role", + }, + }, + } + + requireSpecificRoleUserAddon := cosmov1alpha1.Template{ + ObjectMeta: metav1.ObjectMeta{ + Name: "require-specific-roles-user-addon-test", + Labels: map[string]string{ + cosmov1alpha1.TemplateLabelKeyType: cosmov1alpha1.TemplateLabelEnumTypeUserAddon, + }, + Annotations: map[string]string{ + cosmov1alpha1.TemplateAnnKeyRequiredAddons: specificRolesUserAddon.GetName(), + }, + }, + } + notUserAddon := cosmov1alpha1.Template{ ObjectMeta: metav1.ObjectMeta{ Name: "notUserAddonTest", @@ -54,6 +78,12 @@ var _ = Describe("User webhook", func() { err = k8sClient.Create(ctx, &defaultUserAddon) Expect(err).ShouldNot(HaveOccurred()) + err = k8sClient.Create(ctx, &specificRolesUserAddon) + Expect(err).ShouldNot(HaveOccurred()) + + err = k8sClient.Create(ctx, &requireSpecificRoleUserAddon) + Expect(err).ShouldNot(HaveOccurred()) + user := cosmov1alpha1.User{} user.SetName("testuser1") user.Spec = cosmov1alpha1.UserSpec{ @@ -209,6 +239,77 @@ var _ = Describe("User webhook", func() { Expect(err).Should(HaveOccurred()) }) }) + + Context("when creating user with required role for addon", func() { + It("should pass", func() { + ctx := context.Background() + + user := cosmov1alpha1.User{} + user.SetName("testuser7") + user.Spec = cosmov1alpha1.UserSpec{ + AuthType: cosmov1alpha1.UserAuthTypePasswordSecert, + Roles: []cosmov1alpha1.UserRole{{Name: "specific-role"}}, + Addons: []cosmov1alpha1.UserAddon{ + {Template: cosmov1alpha1.UserAddonTemplateRef{Name: specificRolesUserAddon.GetName()}}, + }, + } + err := k8sClient.Create(ctx, &user) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when creating user without required role for addon", func() { + It("should deny", func() { + ctx := context.Background() + + user := cosmov1alpha1.User{} + user.SetName("testuser7-2") + user.Spec = cosmov1alpha1.UserSpec{ + AuthType: cosmov1alpha1.UserAuthTypePasswordSecert, + Addons: []cosmov1alpha1.UserAddon{ + {Template: cosmov1alpha1.UserAddonTemplateRef{Name: specificRolesUserAddon.GetName()}}, + }, + } + err := k8sClient.Create(ctx, &user) + Expect(err).Should(HaveOccurred()) + }) + }) + + Context("when creating user with required addon for addon", func() { + It("should pass", func() { + ctx := context.Background() + + user := cosmov1alpha1.User{} + user.SetName("testuser8") + user.Spec = cosmov1alpha1.UserSpec{ + AuthType: cosmov1alpha1.UserAuthTypePasswordSecert, + Roles: []cosmov1alpha1.UserRole{{Name: "specific-role"}}, + Addons: []cosmov1alpha1.UserAddon{ + {Template: cosmov1alpha1.UserAddonTemplateRef{Name: specificRolesUserAddon.GetName()}}, + {Template: cosmov1alpha1.UserAddonTemplateRef{Name: requireSpecificRoleUserAddon.GetName()}}, + }, + } + err := k8sClient.Create(ctx, &user) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when creating user without required addon for addon", func() { + It("should deny", func() { + ctx := context.Background() + + user := cosmov1alpha1.User{} + user.SetName("testuser8-2") + user.Spec = cosmov1alpha1.UserSpec{ + AuthType: cosmov1alpha1.UserAuthTypePasswordSecert, + Addons: []cosmov1alpha1.UserAddon{ + {Template: cosmov1alpha1.UserAddonTemplateRef{Name: requireSpecificRoleUserAddon.GetName()}}, + }, + } + err := k8sClient.Create(ctx, &user) + Expect(err).Should(HaveOccurred()) + }) + }) }) func Test_validName(t *testing.T) { diff --git a/internal/webhooks/workspace_webhook.go b/internal/webhooks/workspace_webhook.go index cd55fe6f..cad68965 100644 --- a/internal/webhooks/workspace_webhook.go +++ b/internal/webhooks/workspace_webhook.go @@ -21,6 +21,7 @@ import ( cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" "github.com/cosmo-workspace/cosmo/pkg/clog" "github.com/cosmo-workspace/cosmo/pkg/instance" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" "github.com/cosmo-workspace/cosmo/pkg/kubeutil" "github.com/cosmo-workspace/cosmo/pkg/template" "github.com/cosmo-workspace/cosmo/pkg/workspace" @@ -235,6 +236,12 @@ func (h *WorkspaceValidationWebhookHandler) Handle(ctx context.Context, req admi return admission.Errored(http.StatusForbidden, err) } + err = h.validateTemplatePermission(ctx, ws) + if err != nil { + log.Error(err, "validation failed") + return admission.Errored(http.StatusForbidden, err) + } + return admission.Allowed("Validation OK") } @@ -253,6 +260,38 @@ func (h *WorkspaceValidationWebhookHandler) validateWorkspace(ctx context.Contex return nil } +func (h *WorkspaceValidationWebhookHandler) validateTemplatePermission(ctx context.Context, ws *cosmov1alpha1.Workspace) error { + // fetch user + var user cosmov1alpha1.User + user.SetName(cosmov1alpha1.UserNameByNamespace(ws.GetNamespace())) + err := h.Client.Get(ctx, types.NamespacedName{Name: user.GetName()}, &user) + if err != nil { + return fmt.Errorf("failed to fetch user %s :%w", user.GetName(), err) + } + + // fetch template + var tmpl cosmov1alpha1.Template + tmpl.SetName(ws.Spec.Template.Name) + err = h.Client.Get(ctx, types.NamespacedName{Name: tmpl.GetName()}, &tmpl) + if err != nil { + return fmt.Errorf("failed to fetch template %s :%w", tmpl.GetName(), err) + } + + // check user has role for the addon + if ok := kosmo.IsAllowedToUseTemplate(ctx, &user, &tmpl); !ok { + requiredRoles := kubeutil.GetAnnotation(&tmpl, cosmov1alpha1.TemplateAnnKeyUserRoles) + return fmt.Errorf("template '%s' is only for roles '%s'", tmpl.GetName(), requiredRoles) + } + + // check user has required addon + if ok := kosmo.HasRequiredAddons(ctx, &user, &tmpl); !ok { + requiredAddons := kubeutil.GetAnnotation(&tmpl, cosmov1alpha1.TemplateAnnKeyRequiredAddons) + return fmt.Errorf("template '%s' requires useraddon '%s'", tmpl.GetName(), requiredAddons) + } + + return nil +} + func (h *WorkspaceValidationWebhookHandler) InjectDecoder(d *admission.Decoder) error { h.decoder = d return nil diff --git a/internal/webhooks/workspace_webhook_test.go b/internal/webhooks/workspace_webhook_test.go index 4364013d..03d3dc30 100644 --- a/internal/webhooks/workspace_webhook_test.go +++ b/internal/webhooks/workspace_webhook_test.go @@ -203,6 +203,23 @@ spec: }, } + specificRoleTmpl := tmpl.DeepCopy() + specificRoleTmpl.SetName("specific-role-tmpl") + specificRoleTmpl.Annotations[cosmov1alpha1.TemplateAnnKeyUserRoles] = "specific-role" + + requireAddonTmpl := tmpl.DeepCopy() + requireAddonTmpl.SetName("require-addon-tmpl") + requireAddonTmpl.Annotations[cosmov1alpha1.TemplateAnnKeyRequiredAddons] = "required-addon" + + requireAddon := cosmov1alpha1.Template{ + ObjectMeta: metav1.ObjectMeta{ + Name: "required-addon", + Labels: map[string]string{ + cosmov1alpha1.TemplateLabelKeyType: cosmov1alpha1.TemplateLabelEnumTypeUserAddon, + }, + }, + } + Context("when creating workspace", func() { It("should pass with defaulting networking", func() { ctx := context.Background() @@ -215,6 +232,10 @@ spec: err = k8sClient.Create(ctx, &ns) Expect(err).ShouldNot(HaveOccurred()) + user := cosmov1alpha1.User{ObjectMeta: metav1.ObjectMeta{Name: cosmov1alpha1.UserNameByNamespace(ns.Name)}} + err = k8sClient.Create(ctx, &user) + Expect(err).ShouldNot(HaveOccurred()) + rep := pointer.Int64(1) ws := cosmov1alpha1.Workspace{ TypeMeta: metav1.TypeMeta{ @@ -407,6 +428,122 @@ spec: Expect(err).To(MatchSnapShot()) }) }) + + Context("when creating workspace with required role for template", func() { + It("should pass", func() { + ctx := context.Background() + var err error + + err = k8sClient.Create(ctx, specificRoleTmpl) + Expect(err).ShouldNot(HaveOccurred()) + + user := cosmov1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{Name: "testws-user-specific-role"}, + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{{Name: "specific-role"}}, + }, + } + err = k8sClient.Create(ctx, &user) + Expect(err).ShouldNot(HaveOccurred()) + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: cosmov1alpha1.UserNamespace(user.Name)}} + err = k8sClient.Create(ctx, &ns) + Expect(err).ShouldNot(HaveOccurred()) + + ws := cosmov1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testws8", + Namespace: cosmov1alpha1.UserNamespace(user.Name), + }, + Spec: cosmov1alpha1.WorkspaceSpec{ + Template: cosmov1alpha1.TemplateRef{Name: specificRoleTmpl.GetName()}, + Vars: map[string]string{"DOMAIN": "example.com"}, + }, + } + err = k8sClient.Create(ctx, &ws) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when creating workspace without required role for template", func() { + It("should deny", func() { + ctx := context.Background() + var err error + + ws := cosmov1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testws8-2", + Namespace: "cosmo-user-testuser-ws", + }, + Spec: cosmov1alpha1.WorkspaceSpec{ + Template: cosmov1alpha1.TemplateRef{Name: specificRoleTmpl.GetName()}, + Vars: map[string]string{"DOMAIN": "example.com"}, + }, + } + err = k8sClient.Create(ctx, &ws) + Expect(err).Should(HaveOccurred()) + }) + }) + + Context("when creating workspace with required addon for template", func() { + It("should pass", func() { + ctx := context.Background() + var err error + + err = k8sClient.Create(ctx, requireAddonTmpl) + Expect(err).ShouldNot(HaveOccurred()) + + err = k8sClient.Create(ctx, &requireAddon) + Expect(err).ShouldNot(HaveOccurred()) + + user := cosmov1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{Name: "testws-user-required-addon"}, + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{{Name: "specific-role"}}, + Addons: []cosmov1alpha1.UserAddon{{Template: cosmov1alpha1.UserAddonTemplateRef{Name: requireAddon.Name}}}, + }, + } + err = k8sClient.Create(ctx, &user) + Expect(err).ShouldNot(HaveOccurred()) + + ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: cosmov1alpha1.UserNamespace(user.Name)}} + err = k8sClient.Create(ctx, &ns) + Expect(err).ShouldNot(HaveOccurred()) + + ws := cosmov1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testws9", + Namespace: cosmov1alpha1.UserNamespace(user.Name), + }, + Spec: cosmov1alpha1.WorkspaceSpec{ + Template: cosmov1alpha1.TemplateRef{Name: requireAddonTmpl.GetName()}, + Vars: map[string]string{"DOMAIN": "example.com"}, + }, + } + err = k8sClient.Create(ctx, &ws) + Expect(err).ShouldNot(HaveOccurred()) + }) + }) + + Context("when creating workspace without required addon for template", func() { + It("should deny", func() { + ctx := context.Background() + var err error + + ws := cosmov1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testws9-2", + Namespace: cosmov1alpha1.UserNamespace("testws-user-specific-role"), + }, + Spec: cosmov1alpha1.WorkspaceSpec{ + Template: cosmov1alpha1.TemplateRef{Name: requireAddonTmpl.GetName()}, + Vars: map[string]string{"DOMAIN": "example.com"}, + }, + } + err = k8sClient.Create(ctx, &ws) + Expect(err).Should(HaveOccurred()) + }) + }) }) func TestNetworkRulesByService(t *testing.T) { diff --git a/pkg/kosmo/template.go b/pkg/kosmo/template.go index 0248f875..d4ed3895 100644 --- a/pkg/kosmo/template.go +++ b/pkg/kosmo/template.go @@ -12,49 +12,34 @@ import ( "github.com/cosmo-workspace/cosmo/pkg/kubeutil" ) -func filterTemplates(ctx context.Context, tmpls []cosmov1alpha1.TemplateObject, roles []cosmov1alpha1.UserRole) []cosmov1alpha1.TemplateObject { +func FilterTemplates(ctx context.Context, tmpls []cosmov1alpha1.TemplateObject, u *cosmov1alpha1.User) []cosmov1alpha1.TemplateObject { filteredTmpls := make([]cosmov1alpha1.TemplateObject, 0, len(tmpls)) for _, v := range tmpls { - if isAllowedToUseTemplate(ctx, v, roles) { + if IsAllowedToUseTemplate(ctx, u, v) { filteredTmpls = append(filteredTmpls, v) } } return filteredTmpls } -func isAllowedToUseTemplate(ctx context.Context, tmpl cosmov1alpha1.TemplateObject, roles []cosmov1alpha1.UserRole) bool { +func IsAllowedToUseTemplate(ctx context.Context, u *cosmov1alpha1.User, tmpl cosmov1alpha1.TemplateObject) bool { debugAll := clog.FromContext(ctx).DebugAll() ann := tmpl.GetAnnotations() - if ann == nil || cosmov1alpha1.HasPrivilegedRole(roles) { + if ann == nil || cosmov1alpha1.HasPrivilegedRole(u.Spec.Roles) { // all allowed debugAll.Info("all allowed", "tmpl", tmpl.GetName()) return true } forRoles := ann[cosmov1alpha1.TemplateAnnKeyUserRoles] - forbiddenRoles := ann[cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles] - - if forbiddenRoles != "" { - for _, forbiddenRole := range strings.Split(forbiddenRoles, ",") { - for _, role := range roles { - debugAll.Info("matching to forbiddenRole...", "forbiddenRole", forbiddenRole, "role", role.Name, "tmpl", tmpl.GetName()) - if matched, err := filepath.Match(forbiddenRole, role.Name); err == nil && matched { - // the role is forbidden - debugAll.Info("forbidden: roles matched to forbiddenRole", "forbiddenRole", forbiddenRole, "role", role.Name, "tmpl", tmpl.GetName()) - return false - } - } - } - } - if forRoles == "" { // all allowed - debugAll.Info("allowed: roles does not matched all forbiddenRoles and NO forRoles", "forbiddenRoles", forbiddenRoles, "forRoles", forRoles, "tmpl", tmpl.GetName()) + debugAll.Info("allowed: roles does not matched all forbiddenRoles and NO forRoles", "forRoles", forRoles, "tmpl", tmpl.GetName()) return true } for _, forRole := range strings.Split(forRoles, ",") { - for _, role := range roles { + for _, role := range u.Spec.Roles { debugAll.Info("matching to forRole...", "forRoles", forRoles, "role", role.Name, "tmpl", tmpl.GetName()) if matched, err := filepath.Match(forRole, role.Name); err == nil && matched { debugAll.Info("allowed: roles matched to forRole", "forRoles", forRoles, "role", role.Name, "tmpl", tmpl.GetName()) @@ -63,27 +48,45 @@ func isAllowedToUseTemplate(ctx context.Context, tmpl cosmov1alpha1.TemplateObje } } // the role does not match the specified roles - debugAll.Info("forbidden: roles does not match forRoles", forbiddenRoles, forRoles) + debugAll.Info("forbidden: roles does not match forRoles", forRoles) + return false +} + +func HasRequiredAddons(ctx context.Context, u *cosmov1alpha1.User, tmpl cosmov1alpha1.TemplateObject) bool { + debugAll := clog.FromContext(ctx).DebugAll() + + reqAddons := kubeutil.GetAnnotation(tmpl, cosmov1alpha1.TemplateAnnKeyRequiredAddons) + if reqAddons == "" { + return true + } + for _, requiredAddon := range strings.Split(reqAddons, ",") { + for _, addon := range u.Spec.Addons { + if requiredAddon == addon.Template.Name { + return true + } + } + } + debugAll.Info("user does not have required addon for template", "requiredAddons", reqAddons) return false } -func (c *Client) ListWorkspaceTemplates(ctx context.Context, roles []cosmov1alpha1.UserRole) ([]cosmov1alpha1.TemplateObject, error) { +func (c *Client) ListWorkspaceTemplates(ctx context.Context) ([]cosmov1alpha1.TemplateObject, error) { log := clog.FromContext(ctx).WithCaller() if tmpls, err := kubeutil.ListTemplateObjectsByType(ctx, c, []string{cosmov1alpha1.TemplateLabelEnumTypeWorkspace}); err != nil { log.Error(err, "failed to list WorkspaceTemplates") return nil, NewInternalServerError("failed to list WorkspaceTemplates", err) } else { - return filterTemplates(ctx, tmpls, roles), nil + return tmpls, nil } } -func (c *Client) ListUserAddonTemplates(ctx context.Context, roles []cosmov1alpha1.UserRole) ([]cosmov1alpha1.TemplateObject, error) { +func (c *Client) ListUserAddonTemplates(ctx context.Context) ([]cosmov1alpha1.TemplateObject, error) { log := clog.FromContext(ctx).WithCaller() if tmpls, err := kubeutil.ListTemplateObjectsByType(ctx, c, []string{cosmov1alpha1.TemplateLabelEnumTypeUserAddon}); err != nil { log.Error(err, "failed to list UserAddon Templates") return nil, NewInternalServerError("failed to list UserAddon Templates", err) } else { - return filterTemplates(ctx, tmpls, roles), nil + return tmpls, nil } } diff --git a/pkg/kosmo/template_test.go b/pkg/kosmo/template_test.go index 7eeb8976..0853203c 100644 --- a/pkg/kosmo/template_test.go +++ b/pkg/kosmo/template_test.go @@ -79,8 +79,8 @@ func TestClient_GetTemplate(t *testing.T) { func Test_isAllowedToUseTemplate(t *testing.T) { type args struct { - tmpl cosmov1alpha1.TemplateObject - roles []cosmov1alpha1.UserRole + tmpl cosmov1alpha1.TemplateObject + user *cosmov1alpha1.User } tests := []struct { name string @@ -95,28 +95,15 @@ func Test_isAllowedToUseTemplate(t *testing.T) { Name: "hogwarts-common", }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor-developer"}, - }, - }, - want: true, - }, - { - name: "forbidden if role is matched to forbidden role", - args: args{ - tmpl: &cosmov1alpha1.Template{ - ObjectMeta: metav1.ObjectMeta{ - Name: "sword-of-gryffindor", - Annotations: map[string]string{ - cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles: "slytherin", + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "gryffindor-developer"}, }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "slytherin"}, - }, }, - want: false, + want: true, }, { name: "forbidden if role is not matched to allowed role", @@ -129,28 +116,13 @@ func Test_isAllowedToUseTemplate(t *testing.T) { }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "slytherin"}, - }, - }, - want: false, - }, - { - name: "forbidden if role is matched to allowed role but also matched to forbidden role", - args: args{ - tmpl: &cosmov1alpha1.Template{ - ObjectMeta: metav1.ObjectMeta{ - Name: "sword-of-gryffindor", - Annotations: map[string]string{ - cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles: "slytherin", - cosmov1alpha1.TemplateAnnKeyUserRoles: "gryffindor", + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "slytherin"}, }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "slytherin"}, - {Name: "gryffindor"}, - }, }, want: false, }, @@ -165,8 +137,12 @@ func Test_isAllowedToUseTemplate(t *testing.T) { }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor-developer"}, + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "gryffindor-developer"}, + }, + }, }, }, want: true, @@ -182,8 +158,12 @@ func Test_isAllowedToUseTemplate(t *testing.T) { }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "slytherin"}, + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "slytherin"}, + }, + }, }, }, want: true, @@ -199,52 +179,20 @@ func Test_isAllowedToUseTemplate(t *testing.T) { }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor"}, - }, - }, - want: false, - }, - { - name: "forbidden if both allowed role wildcard and forbidden role matches", - args: args{ - tmpl: &cosmov1alpha1.Template{ - ObjectMeta: metav1.ObjectMeta{ - Name: "sword-of-gryffindor", - Annotations: map[string]string{ - cosmov1alpha1.TemplateAnnKeyUserRoles: "gryffindor-*", - cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles: "gryffindor-faker", + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "gryffindor"}, }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor-faker"}, - }, - }, - want: false, - }, - { - name: "forbidden if both allowed role wildcard and forbidden wildcard matches", - args: args{ - tmpl: &cosmov1alpha1.Template{ - ObjectMeta: metav1.ObjectMeta{ - Name: "sword-of-gryffindor", - Annotations: map[string]string{ - cosmov1alpha1.TemplateAnnKeyUserRoles: "gryffindor-*", - cosmov1alpha1.TemplateAnnKeyForbiddenUserRoles: "gryffindor-f*", - }, - }, - }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor-faker"}, - }, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isAllowedToUseTemplate(context.TODO(), tt.args.tmpl, tt.args.roles); got != tt.want { + if got := IsAllowedToUseTemplate(context.TODO(), tt.args.user, tt.args.tmpl); got != tt.want { t.Errorf("isAllowedToUseTemplate() = %v, want %v", got, tt.want) } }) @@ -254,7 +202,7 @@ func Test_isAllowedToUseTemplate(t *testing.T) { func Test_filterTemplates(t *testing.T) { type args struct { tmpls []cosmov1alpha1.TemplateObject - roles []cosmov1alpha1.UserRole + user *cosmov1alpha1.User } tests := []struct { name string @@ -288,8 +236,12 @@ func Test_filterTemplates(t *testing.T) { }, }, }, - roles: []cosmov1alpha1.UserRole{ - {Name: "gryffindor-developer"}, + user: &cosmov1alpha1.User{ + Spec: cosmov1alpha1.UserSpec{ + Roles: []cosmov1alpha1.UserRole{ + {Name: "gryffindor-developer"}, + }, + }, }, }, want: []cosmov1alpha1.TemplateObject{ @@ -312,7 +264,7 @@ func Test_filterTemplates(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := filterTemplates(context.TODO(), tt.args.tmpls, tt.args.roles); !reflect.DeepEqual(got, tt.want) { + if got := FilterTemplates(context.TODO(), tt.args.tmpls, tt.args.user); !reflect.DeepEqual(got, tt.want) { t.Errorf("filterTemplates() = %v, want %v", got, tt.want) t.Errorf(cmp.Diff(got, tt.want)) } diff --git a/pkg/kosmo/test/test_util.go b/pkg/kosmo/test/test_util.go index 7ea18c74..09f805b4 100644 --- a/pkg/kosmo/test/test_util.go +++ b/pkg/kosmo/test/test_util.go @@ -64,6 +64,28 @@ spec: }, time.Second*5, time.Millisecond*100).Should(Succeed()) } +func (c *TestUtil) CreateTemplateForUserRole(templateType, templateName, userrole string) { + ctx := context.Background() + tmpl := cosmov1alpha1.Template{ + ObjectMeta: metav1.ObjectMeta{ + Name: templateName, + Labels: map[string]string{ + cosmov1alpha1.TemplateLabelKeyType: templateType, + }, + Annotations: map[string]string{ + cosmov1alpha1.TemplateAnnKeyUserRoles: userrole, + }, + }, + } + err := c.kosmoClient.Create(ctx, &tmpl) + Expect(err).ShouldNot(HaveOccurred()) + + Eventually(func() error { + err := c.kosmoClient.Get(ctx, client.ObjectKey{Name: templateName}, &cosmov1alpha1.Template{}) + return err + }, time.Second*5, time.Millisecond*100).Should(Succeed()) +} + func (c *TestUtil) CreateClusterTemplate(templateType string, templateName string) { ctx := context.Background() tmpl := cosmov1alpha1.ClusterTemplate{ diff --git a/pkg/kubeutil/utils.go b/pkg/kubeutil/utils.go index 6e861dbc..1a31d455 100644 --- a/pkg/kubeutil/utils.go +++ b/pkg/kubeutil/utils.go @@ -94,3 +94,29 @@ func PodStatusReason(pod corev1.Pod) string { return reason } + +type AnnotationHolder interface { + GetAnnotations() map[string]string + SetAnnotations(map[string]string) +} + +func GetAnnotation(obj AnnotationHolder, key string) string { + ann := obj.GetAnnotations() + if ann == nil { + return "" + } + return ann[key] +} + +type LabelHolder interface { + GetLabels() map[string]string + SetLabels(map[string]string) +} + +func GetLabel(obj LabelHolder, key string) string { + l := obj.GetLabels() + if l == nil { + return "" + } + return l[key] +} diff --git a/pkg/useraddon/useraddon.go b/pkg/useraddon/useraddon.go index 055227a9..6494db89 100644 --- a/pkg/useraddon/useraddon.go +++ b/pkg/useraddon/useraddon.go @@ -74,6 +74,7 @@ func PatchUserAddonInstanceAsDesired(inst cosmov1alpha1.InstanceObject, addon co addon.Vars = make(map[string]string) } addon.Vars[template.DefaultVarsNamespace] = cosmov1alpha1.UserNamespace(user.Name) + addon.Vars[cosmov1alpha1.TemplateVarUser] = user.Name addon.Vars[cosmov1alpha1.TemplateVarUserName] = user.Name inst.GetSpec().Vars = addon.Vars diff --git a/pkg/useraddon/useraddon_test.go b/pkg/useraddon/useraddon_test.go index b28c312a..2bf9bb1f 100644 --- a/pkg/useraddon/useraddon_test.go +++ b/pkg/useraddon/useraddon_test.go @@ -247,6 +247,7 @@ func TestPatchUserAddonInstanceAsDesired(t *testing.T) { }, Vars: map[string]string{ cosmov1alpha1.TemplateVarUserName: "tom", + cosmov1alpha1.TemplateVarUser: "tom", template.DefaultVarsNamespace: "cosmo-user-tom", "VAR1": "VAL1", }, @@ -299,6 +300,7 @@ func TestPatchUserAddonInstanceAsDesired(t *testing.T) { }, Vars: map[string]string{ cosmov1alpha1.TemplateVarUserName: "tom", + cosmov1alpha1.TemplateVarUser: "tom", template.DefaultVarsNamespace: "cosmo-user-tom", }, }, diff --git a/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/template_service.connect.go b/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/template_service.connect.go index 08c4d820..bd8a975c 100644 --- a/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/template_service.connect.go +++ b/proto/gen/dashboard/v1alpha1/dashboardv1alpha1connect/template_service.connect.go @@ -13,7 +13,6 @@ import ( errors "errors" connect_go "github.com/bufbuild/connect-go" v1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" - emptypb "google.golang.org/protobuf/types/known/emptypb" http "net/http" strings "strings" ) @@ -49,9 +48,9 @@ const ( // TemplateServiceClient is a client for the dashboard.v1alpha1.TemplateService service. type TemplateServiceClient interface { // List templates typed useraddon - GetUserAddonTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) + GetUserAddonTemplates(context.Context, *connect_go.Request[v1alpha1.GetUserAddonTemplatesRequest]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) // List templates typed workspace - GetWorkspaceTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) + GetWorkspaceTemplates(context.Context, *connect_go.Request[v1alpha1.GetWorkspaceTemplatesRequest]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) } // NewTemplateServiceClient constructs a client for the dashboard.v1alpha1.TemplateService service. @@ -64,12 +63,12 @@ type TemplateServiceClient interface { func NewTemplateServiceClient(httpClient connect_go.HTTPClient, baseURL string, opts ...connect_go.ClientOption) TemplateServiceClient { baseURL = strings.TrimRight(baseURL, "/") return &templateServiceClient{ - getUserAddonTemplates: connect_go.NewClient[emptypb.Empty, v1alpha1.GetUserAddonTemplatesResponse]( + getUserAddonTemplates: connect_go.NewClient[v1alpha1.GetUserAddonTemplatesRequest, v1alpha1.GetUserAddonTemplatesResponse]( httpClient, baseURL+TemplateServiceGetUserAddonTemplatesProcedure, opts..., ), - getWorkspaceTemplates: connect_go.NewClient[emptypb.Empty, v1alpha1.GetWorkspaceTemplatesResponse]( + getWorkspaceTemplates: connect_go.NewClient[v1alpha1.GetWorkspaceTemplatesRequest, v1alpha1.GetWorkspaceTemplatesResponse]( httpClient, baseURL+TemplateServiceGetWorkspaceTemplatesProcedure, opts..., @@ -79,26 +78,26 @@ func NewTemplateServiceClient(httpClient connect_go.HTTPClient, baseURL string, // templateServiceClient implements TemplateServiceClient. type templateServiceClient struct { - getUserAddonTemplates *connect_go.Client[emptypb.Empty, v1alpha1.GetUserAddonTemplatesResponse] - getWorkspaceTemplates *connect_go.Client[emptypb.Empty, v1alpha1.GetWorkspaceTemplatesResponse] + getUserAddonTemplates *connect_go.Client[v1alpha1.GetUserAddonTemplatesRequest, v1alpha1.GetUserAddonTemplatesResponse] + getWorkspaceTemplates *connect_go.Client[v1alpha1.GetWorkspaceTemplatesRequest, v1alpha1.GetWorkspaceTemplatesResponse] } // GetUserAddonTemplates calls dashboard.v1alpha1.TemplateService.GetUserAddonTemplates. -func (c *templateServiceClient) GetUserAddonTemplates(ctx context.Context, req *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) { +func (c *templateServiceClient) GetUserAddonTemplates(ctx context.Context, req *connect_go.Request[v1alpha1.GetUserAddonTemplatesRequest]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) { return c.getUserAddonTemplates.CallUnary(ctx, req) } // GetWorkspaceTemplates calls dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates. -func (c *templateServiceClient) GetWorkspaceTemplates(ctx context.Context, req *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) { +func (c *templateServiceClient) GetWorkspaceTemplates(ctx context.Context, req *connect_go.Request[v1alpha1.GetWorkspaceTemplatesRequest]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) { return c.getWorkspaceTemplates.CallUnary(ctx, req) } // TemplateServiceHandler is an implementation of the dashboard.v1alpha1.TemplateService service. type TemplateServiceHandler interface { // List templates typed useraddon - GetUserAddonTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) + GetUserAddonTemplates(context.Context, *connect_go.Request[v1alpha1.GetUserAddonTemplatesRequest]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) // List templates typed workspace - GetWorkspaceTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) + GetWorkspaceTemplates(context.Context, *connect_go.Request[v1alpha1.GetWorkspaceTemplatesRequest]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) } // NewTemplateServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -124,10 +123,10 @@ func NewTemplateServiceHandler(svc TemplateServiceHandler, opts ...connect_go.Ha // UnimplementedTemplateServiceHandler returns CodeUnimplemented from all methods. type UnimplementedTemplateServiceHandler struct{} -func (UnimplementedTemplateServiceHandler) GetUserAddonTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) { +func (UnimplementedTemplateServiceHandler) GetUserAddonTemplates(context.Context, *connect_go.Request[v1alpha1.GetUserAddonTemplatesRequest]) (*connect_go.Response[v1alpha1.GetUserAddonTemplatesResponse], error) { return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.TemplateService.GetUserAddonTemplates is not implemented")) } -func (UnimplementedTemplateServiceHandler) GetWorkspaceTemplates(context.Context, *connect_go.Request[emptypb.Empty]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) { +func (UnimplementedTemplateServiceHandler) GetWorkspaceTemplates(context.Context, *connect_go.Request[v1alpha1.GetWorkspaceTemplatesRequest]) (*connect_go.Response[v1alpha1.GetWorkspaceTemplatesResponse], error) { return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates is not implemented")) } diff --git a/proto/gen/dashboard/v1alpha1/template.pb.go b/proto/gen/dashboard/v1alpha1/template.pb.go index f91e28b2..d4b1d2e1 100644 --- a/proto/gen/dashboard/v1alpha1/template.pb.go +++ b/proto/gen/dashboard/v1alpha1/template.pb.go @@ -89,6 +89,7 @@ type Template struct { RequiredVars []*TemplateRequiredVars `protobuf:"bytes,3,rep,name=required_vars,json=requiredVars,proto3" json:"required_vars,omitempty"` IsDefaultUserAddon *bool `protobuf:"varint,4,opt,name=is_default_user_addon,json=isDefaultUserAddon,proto3,oneof" json:"is_default_user_addon,omitempty"` IsClusterScope bool `protobuf:"varint,5,opt,name=is_cluster_scope,json=isClusterScope,proto3" json:"is_cluster_scope,omitempty"` + RequiredUseraddons []string `protobuf:"bytes,6,rep,name=required_useraddons,json=requiredUseraddons,proto3" json:"required_useraddons,omitempty"` } func (x *Template) Reset() { @@ -158,6 +159,13 @@ func (x *Template) GetIsClusterScope() bool { return false } +func (x *Template) GetRequiredUseraddons() []string { + if x != nil { + return x.RequiredUseraddons + } + return nil +} + var File_dashboard_v1alpha1_template_proto protoreflect.FileDescriptor var file_dashboard_v1alpha1_template_proto_rawDesc = []byte{ @@ -170,7 +178,7 @@ var file_dashboard_v1alpha1_template_proto_rawDesc = []byte{ 0x09, 0x52, 0x07, 0x76, 0x61, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, - 0x8b, 0x02, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0xbc, 0x02, 0x0a, 0x08, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, @@ -185,23 +193,26 @@ var file_dashboard_v1alpha1_template_proto_rawDesc = []byte{ 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x53, 0x63, - 0x6f, 0x70, 0x65, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x6f, 0x6e, 0x42, 0xe1, 0x01, - 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0d, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, - 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, - 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, - 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x61, 0x64, 0x64, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x61, 0x64, + 0x64, 0x6f, 0x6e, 0x73, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x6f, 0x6e, 0x42, 0xe1, + 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0d, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, + 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/gen/dashboard/v1alpha1/template_service.pb.go b/proto/gen/dashboard/v1alpha1/template_service.pb.go index 6b310367..a883cccc 100644 --- a/proto/gen/dashboard/v1alpha1/template_service.pb.go +++ b/proto/gen/dashboard/v1alpha1/template_service.pb.go @@ -13,7 +13,7 @@ package dashboardv1alpha1 import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" + _ "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" ) @@ -25,6 +25,53 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type GetUserAddonTemplatesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UseRoleFilter *bool `protobuf:"varint,1,opt,name=use_role_filter,json=useRoleFilter,proto3,oneof" json:"use_role_filter,omitempty"` +} + +func (x *GetUserAddonTemplatesRequest) Reset() { + *x = GetUserAddonTemplatesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetUserAddonTemplatesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserAddonTemplatesRequest) ProtoMessage() {} + +func (x *GetUserAddonTemplatesRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserAddonTemplatesRequest.ProtoReflect.Descriptor instead. +func (*GetUserAddonTemplatesRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetUserAddonTemplatesRequest) GetUseRoleFilter() bool { + if x != nil && x.UseRoleFilter != nil { + return *x.UseRoleFilter + } + return false +} + type GetUserAddonTemplatesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -37,7 +84,7 @@ type GetUserAddonTemplatesResponse struct { func (x *GetUserAddonTemplatesResponse) Reset() { *x = GetUserAddonTemplatesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[0] + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -50,7 +97,7 @@ func (x *GetUserAddonTemplatesResponse) String() string { func (*GetUserAddonTemplatesResponse) ProtoMessage() {} func (x *GetUserAddonTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[0] + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -63,7 +110,7 @@ func (x *GetUserAddonTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserAddonTemplatesResponse.ProtoReflect.Descriptor instead. func (*GetUserAddonTemplatesResponse) Descriptor() ([]byte, []int) { - return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{0} + return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{1} } func (x *GetUserAddonTemplatesResponse) GetMessage() string { @@ -80,6 +127,53 @@ func (x *GetUserAddonTemplatesResponse) GetItems() []*Template { return nil } +type GetWorkspaceTemplatesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UseRoleFilter *bool `protobuf:"varint,1,opt,name=use_role_filter,json=useRoleFilter,proto3,oneof" json:"use_role_filter,omitempty"` +} + +func (x *GetWorkspaceTemplatesRequest) Reset() { + *x = GetWorkspaceTemplatesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetWorkspaceTemplatesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetWorkspaceTemplatesRequest) ProtoMessage() {} + +func (x *GetWorkspaceTemplatesRequest) ProtoReflect() protoreflect.Message { + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetWorkspaceTemplatesRequest.ProtoReflect.Descriptor instead. +func (*GetWorkspaceTemplatesRequest) Descriptor() ([]byte, []int) { + return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{2} +} + +func (x *GetWorkspaceTemplatesRequest) GetUseRoleFilter() bool { + if x != nil && x.UseRoleFilter != nil { + return *x.UseRoleFilter + } + return false +} + type GetWorkspaceTemplatesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -92,7 +186,7 @@ type GetWorkspaceTemplatesResponse struct { func (x *GetWorkspaceTemplatesResponse) Reset() { *x = GetWorkspaceTemplatesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[1] + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -105,7 +199,7 @@ func (x *GetWorkspaceTemplatesResponse) String() string { func (*GetWorkspaceTemplatesResponse) ProtoMessage() {} func (x *GetWorkspaceTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[1] + mi := &file_dashboard_v1alpha1_template_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -118,7 +212,7 @@ func (x *GetWorkspaceTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkspaceTemplatesResponse.ProtoReflect.Descriptor instead. func (*GetWorkspaceTemplatesResponse) Descriptor() ([]byte, []int) { - return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{1} + return file_dashboard_v1alpha1_template_service_proto_rawDescGZIP(), []int{3} } func (x *GetWorkspaceTemplatesResponse) GetMessage() string { @@ -146,49 +240,65 @@ var file_dashboard_v1alpha1_template_service_proto_rawDesc = []byte{ 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x6d, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x54, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x73, 0x68, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x6d, - 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, 0x74, 0x65, - 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x32, 0xd9, 0x01, - 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x62, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, - 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x31, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, - 0x64, 0x64, 0x6f, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x31, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xe8, 0x01, 0x0a, 0x16, 0x63, 0x6f, - 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x42, 0x14, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, - 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, - 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5f, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2b, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, + 0x5f, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x22, 0x6d, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, + 0x5f, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2b, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, + 0x5f, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x22, 0x6d, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x32, + 0x8d, 0x02, 0x0a, 0x0f, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x7c, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, + 0x64, 0x6f, 0x6e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x64, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, + 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x64, 0x64, 0x6f, 0x6e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x7c, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0xe8, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x14, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, + 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, + 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -203,20 +313,21 @@ func file_dashboard_v1alpha1_template_service_proto_rawDescGZIP() []byte { return file_dashboard_v1alpha1_template_service_proto_rawDescData } -var file_dashboard_v1alpha1_template_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_dashboard_v1alpha1_template_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_dashboard_v1alpha1_template_service_proto_goTypes = []interface{}{ - (*GetUserAddonTemplatesResponse)(nil), // 0: dashboard.v1alpha1.GetUserAddonTemplatesResponse - (*GetWorkspaceTemplatesResponse)(nil), // 1: dashboard.v1alpha1.GetWorkspaceTemplatesResponse - (*Template)(nil), // 2: dashboard.v1alpha1.Template - (*emptypb.Empty)(nil), // 3: google.protobuf.Empty + (*GetUserAddonTemplatesRequest)(nil), // 0: dashboard.v1alpha1.GetUserAddonTemplatesRequest + (*GetUserAddonTemplatesResponse)(nil), // 1: dashboard.v1alpha1.GetUserAddonTemplatesResponse + (*GetWorkspaceTemplatesRequest)(nil), // 2: dashboard.v1alpha1.GetWorkspaceTemplatesRequest + (*GetWorkspaceTemplatesResponse)(nil), // 3: dashboard.v1alpha1.GetWorkspaceTemplatesResponse + (*Template)(nil), // 4: dashboard.v1alpha1.Template } var file_dashboard_v1alpha1_template_service_proto_depIdxs = []int32{ - 2, // 0: dashboard.v1alpha1.GetUserAddonTemplatesResponse.items:type_name -> dashboard.v1alpha1.Template - 2, // 1: dashboard.v1alpha1.GetWorkspaceTemplatesResponse.items:type_name -> dashboard.v1alpha1.Template - 3, // 2: dashboard.v1alpha1.TemplateService.GetUserAddonTemplates:input_type -> google.protobuf.Empty - 3, // 3: dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates:input_type -> google.protobuf.Empty - 0, // 4: dashboard.v1alpha1.TemplateService.GetUserAddonTemplates:output_type -> dashboard.v1alpha1.GetUserAddonTemplatesResponse - 1, // 5: dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates:output_type -> dashboard.v1alpha1.GetWorkspaceTemplatesResponse + 4, // 0: dashboard.v1alpha1.GetUserAddonTemplatesResponse.items:type_name -> dashboard.v1alpha1.Template + 4, // 1: dashboard.v1alpha1.GetWorkspaceTemplatesResponse.items:type_name -> dashboard.v1alpha1.Template + 0, // 2: dashboard.v1alpha1.TemplateService.GetUserAddonTemplates:input_type -> dashboard.v1alpha1.GetUserAddonTemplatesRequest + 2, // 3: dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates:input_type -> dashboard.v1alpha1.GetWorkspaceTemplatesRequest + 1, // 4: dashboard.v1alpha1.TemplateService.GetUserAddonTemplates:output_type -> dashboard.v1alpha1.GetUserAddonTemplatesResponse + 3, // 5: dashboard.v1alpha1.TemplateService.GetWorkspaceTemplates:output_type -> dashboard.v1alpha1.GetWorkspaceTemplatesResponse 4, // [4:6] is the sub-list for method output_type 2, // [2:4] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name @@ -232,7 +343,7 @@ func file_dashboard_v1alpha1_template_service_proto_init() { file_dashboard_v1alpha1_template_proto_init() if !protoimpl.UnsafeEnabled { file_dashboard_v1alpha1_template_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetUserAddonTemplatesResponse); i { + switch v := v.(*GetUserAddonTemplatesRequest); i { case 0: return &v.state case 1: @@ -244,6 +355,30 @@ func file_dashboard_v1alpha1_template_service_proto_init() { } } file_dashboard_v1alpha1_template_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetUserAddonTemplatesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_template_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetWorkspaceTemplatesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dashboard_v1alpha1_template_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetWorkspaceTemplatesResponse); i { case 0: return &v.state @@ -256,13 +391,15 @@ func file_dashboard_v1alpha1_template_service_proto_init() { } } } + file_dashboard_v1alpha1_template_service_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_dashboard_v1alpha1_template_service_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_dashboard_v1alpha1_template_service_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/dashboard/v1alpha1/template_service.pb.validate.go b/proto/gen/dashboard/v1alpha1/template_service.pb.validate.go index 68809700..f30c9b86 100644 --- a/proto/gen/dashboard/v1alpha1/template_service.pb.validate.go +++ b/proto/gen/dashboard/v1alpha1/template_service.pb.validate.go @@ -35,6 +35,113 @@ var ( _ = sort.Sort ) +// Validate checks the field values on GetUserAddonTemplatesRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *GetUserAddonTemplatesRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on GetUserAddonTemplatesRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// GetUserAddonTemplatesRequestMultiError, or nil if none found. +func (m *GetUserAddonTemplatesRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *GetUserAddonTemplatesRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.UseRoleFilter != nil { + // no validation rules for UseRoleFilter + } + + if len(errors) > 0 { + return GetUserAddonTemplatesRequestMultiError(errors) + } + + return nil +} + +// GetUserAddonTemplatesRequestMultiError is an error wrapping multiple +// validation errors returned by GetUserAddonTemplatesRequest.ValidateAll() if +// the designated constraints aren't met. +type GetUserAddonTemplatesRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m GetUserAddonTemplatesRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m GetUserAddonTemplatesRequestMultiError) AllErrors() []error { return m } + +// GetUserAddonTemplatesRequestValidationError is the validation error returned +// by GetUserAddonTemplatesRequest.Validate if the designated constraints +// aren't met. +type GetUserAddonTemplatesRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e GetUserAddonTemplatesRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e GetUserAddonTemplatesRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e GetUserAddonTemplatesRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e GetUserAddonTemplatesRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e GetUserAddonTemplatesRequestValidationError) ErrorName() string { + return "GetUserAddonTemplatesRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e GetUserAddonTemplatesRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sGetUserAddonTemplatesRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = GetUserAddonTemplatesRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = GetUserAddonTemplatesRequestValidationError{} + // Validate checks the field values on GetUserAddonTemplatesResponse with the // rules defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. @@ -174,6 +281,113 @@ var _ interface { ErrorName() string } = GetUserAddonTemplatesResponseValidationError{} +// Validate checks the field values on GetWorkspaceTemplatesRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *GetWorkspaceTemplatesRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on GetWorkspaceTemplatesRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// GetWorkspaceTemplatesRequestMultiError, or nil if none found. +func (m *GetWorkspaceTemplatesRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *GetWorkspaceTemplatesRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.UseRoleFilter != nil { + // no validation rules for UseRoleFilter + } + + if len(errors) > 0 { + return GetWorkspaceTemplatesRequestMultiError(errors) + } + + return nil +} + +// GetWorkspaceTemplatesRequestMultiError is an error wrapping multiple +// validation errors returned by GetWorkspaceTemplatesRequest.ValidateAll() if +// the designated constraints aren't met. +type GetWorkspaceTemplatesRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m GetWorkspaceTemplatesRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m GetWorkspaceTemplatesRequestMultiError) AllErrors() []error { return m } + +// GetWorkspaceTemplatesRequestValidationError is the validation error returned +// by GetWorkspaceTemplatesRequest.Validate if the designated constraints +// aren't met. +type GetWorkspaceTemplatesRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e GetWorkspaceTemplatesRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e GetWorkspaceTemplatesRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e GetWorkspaceTemplatesRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e GetWorkspaceTemplatesRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e GetWorkspaceTemplatesRequestValidationError) ErrorName() string { + return "GetWorkspaceTemplatesRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e GetWorkspaceTemplatesRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sGetWorkspaceTemplatesRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = GetWorkspaceTemplatesRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = GetWorkspaceTemplatesRequestValidationError{} + // Validate checks the field values on GetWorkspaceTemplatesResponse with the // rules defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. diff --git a/proto/gen/index.md b/proto/gen/index.md index 5dd21036..a6a85719 100644 --- a/proto/gen/index.md +++ b/proto/gen/index.md @@ -15,7 +15,9 @@ - [TemplateRequiredVars](#dashboard-v1alpha1-TemplateRequiredVars) - [dashboard/v1alpha1/template_service.proto](#dashboard_v1alpha1_template_service-proto) + - [GetUserAddonTemplatesRequest](#dashboard-v1alpha1-GetUserAddonTemplatesRequest) - [GetUserAddonTemplatesResponse](#dashboard-v1alpha1-GetUserAddonTemplatesResponse) + - [GetWorkspaceTemplatesRequest](#dashboard-v1alpha1-GetWorkspaceTemplatesRequest) - [GetWorkspaceTemplatesResponse](#dashboard-v1alpha1-GetWorkspaceTemplatesResponse) - [TemplateService](#dashboard-v1alpha1-TemplateService) @@ -172,6 +174,7 @@ | required_vars | [TemplateRequiredVars](#dashboard-v1alpha1-TemplateRequiredVars) | repeated | | | is_default_user_addon | [bool](#bool) | optional | | | is_cluster_scope | [bool](#bool) | | | +| required_useraddons | [string](#string) | repeated | | @@ -210,6 +213,21 @@ + + +### GetUserAddonTemplatesRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| use_role_filter | [bool](#bool) | optional | | + + + + + + ### GetUserAddonTemplatesResponse @@ -226,6 +244,21 @@ + + +### GetWorkspaceTemplatesRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| use_role_filter | [bool](#bool) | optional | | + + + + + + ### GetWorkspaceTemplatesResponse @@ -255,8 +288,8 @@ | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| -| GetUserAddonTemplates | [.google.protobuf.Empty](#google-protobuf-Empty) | [GetUserAddonTemplatesResponse](#dashboard-v1alpha1-GetUserAddonTemplatesResponse) | List templates typed useraddon | -| GetWorkspaceTemplates | [.google.protobuf.Empty](#google-protobuf-Empty) | [GetWorkspaceTemplatesResponse](#dashboard-v1alpha1-GetWorkspaceTemplatesResponse) | List templates typed workspace | +| GetUserAddonTemplates | [GetUserAddonTemplatesRequest](#dashboard-v1alpha1-GetUserAddonTemplatesRequest) | [GetUserAddonTemplatesResponse](#dashboard-v1alpha1-GetUserAddonTemplatesResponse) | List templates typed useraddon | +| GetWorkspaceTemplates | [GetWorkspaceTemplatesRequest](#dashboard-v1alpha1-GetWorkspaceTemplatesRequest) | [GetWorkspaceTemplatesResponse](#dashboard-v1alpha1-GetWorkspaceTemplatesResponse) | List templates typed workspace | diff --git a/proto/proto/dashboard-apis/dashboard/v1alpha1/template.proto b/proto/proto/dashboard-apis/dashboard/v1alpha1/template.proto index 215b36d8..9fe6e6e4 100644 --- a/proto/proto/dashboard-apis/dashboard/v1alpha1/template.proto +++ b/proto/proto/dashboard-apis/dashboard/v1alpha1/template.proto @@ -22,4 +22,6 @@ message Template { optional bool is_default_user_addon = 4; bool is_cluster_scope = 5; + + repeated string required_useraddons = 6; } diff --git a/proto/proto/dashboard-apis/dashboard/v1alpha1/template_service.proto b/proto/proto/dashboard-apis/dashboard/v1alpha1/template_service.proto index 40a10519..a978c1e0 100644 --- a/proto/proto/dashboard-apis/dashboard/v1alpha1/template_service.proto +++ b/proto/proto/dashboard-apis/dashboard/v1alpha1/template_service.proto @@ -12,18 +12,26 @@ import "dashboard/v1alpha1/template.proto"; service TemplateService { // List templates typed useraddon - rpc GetUserAddonTemplates(google.protobuf.Empty) + rpc GetUserAddonTemplates(GetUserAddonTemplatesRequest) returns (GetUserAddonTemplatesResponse); // List templates typed workspace - rpc GetWorkspaceTemplates(google.protobuf.Empty) + rpc GetWorkspaceTemplates(GetWorkspaceTemplatesRequest) returns (GetWorkspaceTemplatesResponse); } +message GetUserAddonTemplatesRequest { + optional bool use_role_filter = 1; +} + message GetUserAddonTemplatesResponse { string message = 1; repeated Template items = 2; } +message GetWorkspaceTemplatesRequest { + optional bool use_role_filter = 1; +} + message GetWorkspaceTemplatesResponse { string message = 1; repeated Template items = 2; diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_pb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_pb.ts index 476841a7..40a9eeb1 100644 --- a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_pb.ts +++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_pb.ts @@ -82,6 +82,11 @@ export class Template extends Message