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 {
*/
isClusterScope = false;
+ /**
+ * @generated from field: repeated string required_useraddons = 6;
+ */
+ requiredUseraddons: string[] = [];
+
constructor(data?: PartialMessage) {
super();
proto3.util.initPartial(data, this);
@@ -95,6 +100,7 @@ export class Template extends Message {
{ no: 3, name: "required_vars", kind: "message", T: TemplateRequiredVars, repeated: true },
{ no: 4, name: "is_default_user_addon", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true },
{ no: 5, name: "is_cluster_scope", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
+ { no: 6, name: "required_useraddons", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial): Template {
diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_connectweb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_connectweb.ts
index 80055852..d7437cb7 100644
--- a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_connectweb.ts
+++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_connectweb.ts
@@ -7,8 +7,8 @@
/* eslint-disable */
// @ts-nocheck
-import { Empty, MethodKind } from "@bufbuild/protobuf";
-import { GetUserAddonTemplatesResponse, GetWorkspaceTemplatesResponse } from "./template_service_pb.js";
+import { GetUserAddonTemplatesRequest, GetUserAddonTemplatesResponse, GetWorkspaceTemplatesRequest, GetWorkspaceTemplatesResponse } from "./template_service_pb.js";
+import { MethodKind } from "@bufbuild/protobuf";
/**
* @generated from service dashboard.v1alpha1.TemplateService
@@ -23,7 +23,7 @@ export const TemplateService = {
*/
getUserAddonTemplates: {
name: "GetUserAddonTemplates",
- I: Empty,
+ I: GetUserAddonTemplatesRequest,
O: GetUserAddonTemplatesResponse,
kind: MethodKind.Unary,
},
@@ -34,7 +34,7 @@ export const TemplateService = {
*/
getWorkspaceTemplates: {
name: "GetWorkspaceTemplates",
- I: Empty,
+ I: GetWorkspaceTemplatesRequest,
O: GetWorkspaceTemplatesResponse,
kind: MethodKind.Unary,
},
diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_pb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_pb.ts
index 336790ae..bf6b38a9 100644
--- a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_pb.ts
+++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/template_service_pb.ts
@@ -11,6 +11,43 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM
import { Message, proto3 } from "@bufbuild/protobuf";
import { Template } from "./template_pb.js";
+/**
+ * @generated from message dashboard.v1alpha1.GetUserAddonTemplatesRequest
+ */
+export class GetUserAddonTemplatesRequest extends Message {
+ /**
+ * @generated from field: optional bool use_role_filter = 1;
+ */
+ useRoleFilter?: boolean;
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "dashboard.v1alpha1.GetUserAddonTemplatesRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "use_role_filter", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): GetUserAddonTemplatesRequest {
+ return new GetUserAddonTemplatesRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): GetUserAddonTemplatesRequest {
+ return new GetUserAddonTemplatesRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): GetUserAddonTemplatesRequest {
+ return new GetUserAddonTemplatesRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: GetUserAddonTemplatesRequest | PlainMessage | undefined, b: GetUserAddonTemplatesRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(GetUserAddonTemplatesRequest, a, b);
+ }
+}
+
/**
* @generated from message dashboard.v1alpha1.GetUserAddonTemplatesResponse
*/
@@ -54,6 +91,43 @@ export class GetUserAddonTemplatesResponse extends Message {
+ /**
+ * @generated from field: optional bool use_role_filter = 1;
+ */
+ useRoleFilter?: boolean;
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "dashboard.v1alpha1.GetWorkspaceTemplatesRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "use_role_filter", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): GetWorkspaceTemplatesRequest {
+ return new GetWorkspaceTemplatesRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): GetWorkspaceTemplatesRequest {
+ return new GetWorkspaceTemplatesRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): GetWorkspaceTemplatesRequest {
+ return new GetWorkspaceTemplatesRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: GetWorkspaceTemplatesRequest | PlainMessage | undefined, b: GetWorkspaceTemplatesRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(GetWorkspaceTemplatesRequest, a, b);
+ }
+}
+
/**
* @generated from message dashboard.v1alpha1.GetWorkspaceTemplatesResponse
*/
diff --git a/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx b/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx
index df9eb891..b8c63dea 100644
--- a/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx
+++ b/web/dashboard-ui/src/views/organisms/UserAddonsChangeDialog.tsx
@@ -45,7 +45,7 @@ export const UserAddonChangeDialog: React.FC<{ onClose: () => void, user: User }
})
const templ = useTemplates();
- useEffect(() => { templ.getUserAddonTemplates(); }, []); // eslint-disable-line
+ useEffect(() => { templ.getAllUserAddonTemplates(); }, []); // eslint-disable-line
useEffect(() => {
const tt = templ.templates.map(t => ({ template: t, enable: false, vars: [] }));
replaceAddons(tt);
diff --git a/web/dashboard-ui/src/views/organisms/UserModule.tsx b/web/dashboard-ui/src/views/organisms/UserModule.tsx
index 0bdf4771..2bb6ee36 100644
--- a/web/dashboard-ui/src/views/organisms/UserModule.tsx
+++ b/web/dashboard-ui/src/views/organisms/UserModule.tsx
@@ -222,16 +222,24 @@ export const useTemplates = () => {
const templateService = useTemplateService();
const { handleError } = useHandleError();
- const getUserAddonTemplates = () => {
+ const getAllUserAddonTemplates = () => {
console.log('getUserAddonTemplates');
return templateService.getUserAddonTemplates({})
.then(result => { setTemplates(result.items.sort((a, b) => (a.name < b.name) ? -1 : 1)); })
.catch(error => { handleError(error) });
}
+ const getUserAddonTemplates = () => {
+ console.log('getUserAddonTemplates');
+ return templateService.getUserAddonTemplates({ useRoleFilter: true })
+ .then(result => { setTemplates(result.items.sort((a, b) => (a.name < b.name) ? -1 : 1)); })
+ .catch(error => { handleError(error) });
+ }
+
return ({
templates,
getUserAddonTemplates,
+ getAllUserAddonTemplates,
});
}
diff --git a/web/dashboard-ui/src/views/organisms/WorkspaceActionDialog.tsx b/web/dashboard-ui/src/views/organisms/WorkspaceActionDialog.tsx
index 733d3c93..6a39ed52 100644
--- a/web/dashboard-ui/src/views/organisms/WorkspaceActionDialog.tsx
+++ b/web/dashboard-ui/src/views/organisms/WorkspaceActionDialog.tsx
@@ -4,7 +4,7 @@ import {
IconButton, InputAdornment, MenuItem, Stack, TextField, Tooltip
} from "@mui/material";
import { useEffect, useState } from "react";
-import { useForm, UseFormRegisterReturn } from "react-hook-form";
+import { UseFormRegisterReturn, useForm } from "react-hook-form";
import { DialogContext } from "../../components/ContextProvider";
import { Template } from "../../proto/gen/dashboard/v1alpha1/template_pb";
import { Workspace } from "../../proto/gen/dashboard/v1alpha1/workspace_pb";
@@ -118,7 +118,20 @@ export const WorkspaceCreateDialog: React.VFC<{ onClose: () => void }> = ({ onCl
required: { value: true, message: "Required" },
}));
- const isNoTemplates = templ.templates.length === 0
+ const hasRequiredAddons = (t: Template): boolean => {
+ if (t.requiredUseraddons.length === 0) {
+ return true;
+ }
+ for (const requiredAddon of t.requiredUseraddons) {
+ if (user.addons.map(a => a.template).includes(requiredAddon)) {
+ return true;
+ }
+ }
+ console.log('user "%s" does not have required addons "%s" for template "%s"', user.name, t.requiredUseraddons, t.name)
+ return false;
+ }
+
+ const isNoTemplates = templ.templates.filter(hasRequiredAddons).length === 0
return (