mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
d0db9ec88f
# Canonicalize API Key Scopes This PR introduces canonical API key scopes with a `coder:` namespace prefix to avoid collisions with low-level resource:action names. It: 1. Renames special API key scopes in the database: - `all` → `coder:all` - `application_connect` → `coder:application_connect` 2. Adds support for a new `scopes` field in the API key creation request, allowing multiple scopes to be specified while maintaining backward compatibility with the singular `scope` field. 3. Updates the API documentation to reflect these changes, including the new endpoint for listing public API key scopes. 4. Ensures backward compatibility by mapping between legacy and canonical scope names in relevant code paths.
121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/format"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
)
|
|
|
|
func main() {
|
|
out, err := generate()
|
|
if err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "generate apikey scopes: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if _, err := fmt.Print(string(out)); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "write output: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func generate() ([]byte, error) {
|
|
names := rbac.ExternalScopeNames()
|
|
slices.Sort(names)
|
|
|
|
var b bytes.Buffer
|
|
if _, err := b.WriteString("// Code generated by scripts/apikeyscopesgen. DO NOT EDIT.\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := b.WriteString("package codersdk\n\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
// NOTE: Keep all APIKeyScope constants in a single generated file.
|
|
// Some tooling (e.g. swaggo) can behave non-deterministically when
|
|
// enums are spread across multiple files:
|
|
// https://github.com/swaggo/swag/issues/2038
|
|
// We generate everything into codersdk/apikey_scopes_gen.go as the
|
|
// single source of truth so doc generation remains stable.
|
|
|
|
// Constants
|
|
if _, err := b.WriteString("const (\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
// Always include legacy/deprecated aliases for backward compatibility.
|
|
// These are kept in generated code to ensure consistent availability
|
|
// across releases even if hand-written files change.
|
|
if _, err := b.WriteString("\t// Deprecated: use codersdk.APIKeyScopeCoderAll instead.\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := b.WriteString("\tAPIKeyScopeAll APIKeyScope = \"all\"\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := b.WriteString("\t// Deprecated: use codersdk.APIKeyScopeCoderApplicationConnect instead.\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := b.WriteString("\tAPIKeyScopeApplicationConnect APIKeyScope = \"application_connect\"\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, n := range names {
|
|
res, act := splitRA(n)
|
|
if act == policy.WildcardSymbol {
|
|
act = "All"
|
|
}
|
|
constName := fmt.Sprintf("APIKeyScope%s%s", pascal(res), pascal(act))
|
|
if _, err := fmt.Fprintf(&b, "\t%s APIKeyScope = \"%s\"\n", constName, n); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := b.WriteString(")\n\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Slices
|
|
if _, err := b.WriteString("// PublicAPIKeyScopes lists all public low-level API key scopes.\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := b.WriteString("var PublicAPIKeyScopes = []APIKeyScope{\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, n := range names {
|
|
res, act := splitRA(n)
|
|
if act == policy.WildcardSymbol {
|
|
act = "All"
|
|
}
|
|
constName := fmt.Sprintf("APIKeyScope%s%s", pascal(res), pascal(act))
|
|
if _, err := fmt.Fprintf(&b, "\t%s,\n", constName); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := b.WriteString("}\n\n"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return format.Source(b.Bytes())
|
|
}
|
|
|
|
func splitRA(name string) (resource string, action string) {
|
|
parts := strings.SplitN(name, ":", 2)
|
|
if len(parts) != 2 {
|
|
return name, ""
|
|
}
|
|
return parts[0], parts[1]
|
|
}
|
|
|
|
func pascal(s string) string {
|
|
// Replace non-identifier separators with spaces, then Title-case and strip.
|
|
s = strings.ReplaceAll(s, "_", " ")
|
|
s = strings.ReplaceAll(s, "-", " ")
|
|
s = strings.ReplaceAll(s, ":", " ")
|
|
words := strings.Fields(s)
|
|
for i := range words {
|
|
words[i] = strings.ToUpper(words[i][:1]) + words[i][1:]
|
|
}
|
|
return strings.Join(words, "")
|
|
}
|