feat: generate RBAC scope name constants (#19896)

# Generate RBAC scope name constants

This PR adds a new generated file `coderd/rbac/scopes_constants_gen.go` that contains typed constants for all RBAC scope names in the format `Scope<Resource><Action>`. For example, `ScopeWorkspaceRead` for the scope "workspace:read".

These constants make it easier to reference specific scopes in code without using string literals, improving type safety and making refactoring easier.

The PR:
- Adds a new template file `scripts/typegen/scopenames.gotmpl`
- Updates the typegen script to support generating scope name constants
- Updates the Makefile to include the new generated file in build targets
This commit is contained in:
Thomas Kosiewski
2025-09-24 18:40:36 +02:00
committed by GitHub
parent 5ff503b8fc
commit adb7521066
5 changed files with 545 additions and 0 deletions
+37
View File
@@ -18,6 +18,7 @@ import (
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/codersdk"
)
@@ -31,6 +32,9 @@ var codersdkTemplate string
//go:embed typescript.tstmpl
var typescriptTemplate string
//go:embed scopenames.gotmpl
var scopenamesTemplate string
//go:embed countries.tstmpl
var countriesTemplate string
@@ -96,6 +100,8 @@ func generateRBAC(tmpl string) ([]byte, error) {
// No typescript formatting
return src, nil
}
case "scopenames":
source = scopenamesTemplate
default:
return nil, xerrors.Errorf("%q is not a valid RBAC template target", tmpl)
}
@@ -225,6 +231,37 @@ func generateRbacObjects(templateSource string) ([]byte, error) {
"actionsList": func() []ActionDetails {
return actionList
},
"actionsOf": func(d Definition) []string {
// Extract and sort action string keys for deterministic output.
list := make([]string, 0, len(d.Actions))
for a := range d.Actions {
list = append(list, string(a))
}
slices.Sort(list)
return list
},
"allCaseList": func(defs []Definition) string {
// Build a multi-line comma-separated list of all scope constants (including builtins)
// suitable for use in a `case ...:` clause, without a trailing comma.
var names []string
// Builtins first, sourced dynamically from the rbac package to avoid drift.
for _, n := range rbac.BuiltinScopeNames() {
// Use typed string literals to avoid relying on constant identifiers.
names = append(names, fmt.Sprintf("ScopeName(%q)", string(n)))
}
for _, d := range defs {
res := pascalCaseName[string](d.Type)
acts := make([]string, 0, len(d.Actions))
for a := range d.Actions {
acts = append(acts, string(a))
}
slices.Sort(acts)
for _, a := range acts {
names = append(names, "Scope"+res+pascalCaseName[string](a))
}
}
return strings.Join(names, ",\n\t\t")
},
"actionEnum": func(action policy.Action) string {
x++
v, ok := actionMap[string(action)]
+36
View File
@@ -0,0 +1,36 @@
// Code generated by: go run ./scripts/typegen rbac scopenames; DO NOT EDIT.
package rbac
// ScopeName constants generated from policy.RBACPermissions.
// These represent low-level "<resource>:<action>" scope names.
// Built-in non-low-level scopes like "all" and "application_connect" remain
// declared in code, not here, to avoid duplication.
const (
{{- range $def := . }}
{{- $Res := pascalCaseName $def.Type }}
{{- range $act := actionsOf $def }}
Scope{{$Res}}{{ pascalCaseName $act }} ScopeName = "{{ $def.Type }}:{{ $act }}"
{{- end }}
{{- end }}
)
// Valid reports whether the ScopeName matches one of the known scope values.
// This includes both builtin scope names and generated low-level scopes.
// Builtins are sourced from rbac.BuiltinScopeNames() at generation time to
// ensure changes in rbac/scopes.go remain in sync here.
func (e ScopeName) Valid() bool {
switch e {
case {{ allCaseList . }}:
return true
}
return false
}
// AllScopeNameValues returns a slice containing all known scope values,
// including builtin and generated low-level scopes.
func AllScopeNameValues() []ScopeName {
return []ScopeName{
{{ allCaseList . }},
}
}