diff --git a/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md b/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md
new file mode 100644
index 00000000000..2382ac5361c
--- /dev/null
+++ b/docs/sources/developers/kinds/core/accesspolicy/schema-reference.md
@@ -0,0 +1,126 @@
+---
+keywords:
+ - grafana
+ - schema
+title: AccessPolicy kind
+---
+> Both documentation generation and kinds schemas are in active development and subject to change without prior notice.
+
+## AccessPolicy
+
+#### Maturity: [merged](../../../maturity/#merged)
+#### Version: 0.0
+
+Access rules for a scope+role. NOTE there is a unique constraint on role+scope
+
+| Property | Type | Required | Default | Description |
+|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. |
+| `spec` | [object](#spec) | **Yes** | | |
+| `status` | [object](#status) | **Yes** | | |
+
+### Metadata
+
+metadata contains embedded CommonMetadata and can be extended with custom string fields
+TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
+without external reference as using the CommonMetadata reference breaks thema codegen.
+
+It extends [_kubeObjectMetadata](#_kubeobjectmetadata).
+
+| Property | Type | Required | Default | Description |
+|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| `createdBy` | string | **Yes** | | |
+| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata |
+| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `updateTimestamp` | string | **Yes** | | |
+| `updatedBy` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+
+### _kubeObjectMetadata
+
+_kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+As it is also intended to be generic enough to function with any API Server.
+
+| Property | Type | Required | Default | Description |
+|---------------------|-------------------|----------|---------|-------------|
+| `creationTimestamp` | string | **Yes** | | |
+| `finalizers` | string[] | **Yes** | | |
+| `labels` | map[string]string | **Yes** | | |
+| `resourceVersion` | string | **Yes** | | |
+| `uid` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | |
+
+### ExtraFields
+
+extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Spec
+
+| Property | Type | Required | Default | Description |
+|----------|-----------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------|
+| `role` | [RoleRef](#roleref) | **Yes** | | |
+| `rules` | [AccessRule](#accessrule)[] | **Yes** | | The set of rules to apply. Note that * is required to modify
access policy rules, and that "none" will reject all actions |
+| `scope` | [ResourceRef](#resourceref) | **Yes** | | |
+
+### AccessRule
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| `kind` | string | **Yes** | | The kind this rule applies to (dashboars, alert, etc) |
+| `verb` | string | **Yes** | | READ, WRITE, CREATE, DELETE, ...
should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete" |
+| `target` | string | No | | Specific sub-elements like "alert.rules" or "dashboard.permissions"???? |
+
+### ResourceRef
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|-------------|
+| `kind` | string | **Yes** | | |
+| `name` | string | **Yes** | | |
+
+### RoleRef
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `kind` | string | **Yes** | | Policies can apply to roles, teams, or users
Applying policies to individual users is supported, but discouraged
Possible values are: `Role`, `BuiltinRole`, `Team`, `User`. |
+| `name` | string | **Yes** | | |
+| `xname` | string | **Yes** | | |
+
+### Status
+
+| Property | Type | Required | Default | Description |
+|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use |
+| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. |
+
+### AdditionalFields
+
+additionalFields is reserved for future use
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Status.#OperatorState
+
+| Property | Type | Required | Default | Description |
+|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated |
+| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. |
+| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format |
+| `details` | [object](#details) | No | | details contains any extra information that is operator-specific |
+
+### Details
+
+details contains any extra information that is operator-specific
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+
diff --git a/docs/sources/developers/kinds/core/role/schema-reference.md b/docs/sources/developers/kinds/core/role/schema-reference.md
new file mode 100644
index 00000000000..d37932fa9af
--- /dev/null
+++ b/docs/sources/developers/kinds/core/role/schema-reference.md
@@ -0,0 +1,105 @@
+---
+keywords:
+ - grafana
+ - schema
+title: Role kind
+---
+> Both documentation generation and kinds schemas are in active development and subject to change without prior notice.
+
+## Role
+
+#### Maturity: [merged](../../../maturity/#merged)
+#### Version: 0.0
+
+Roles represent a set of users+teams that should share similar access
+
+| Property | Type | Required | Default | Description |
+|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. |
+| `spec` | [object](#spec) | **Yes** | | |
+| `status` | [object](#status) | **Yes** | | |
+
+### Metadata
+
+metadata contains embedded CommonMetadata and can be extended with custom string fields
+TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
+without external reference as using the CommonMetadata reference breaks thema codegen.
+
+It extends [_kubeObjectMetadata](#_kubeobjectmetadata).
+
+| Property | Type | Required | Default | Description |
+|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| `createdBy` | string | **Yes** | | |
+| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata |
+| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `updateTimestamp` | string | **Yes** | | |
+| `updatedBy` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+
+### _kubeObjectMetadata
+
+_kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+As it is also intended to be generic enough to function with any API Server.
+
+| Property | Type | Required | Default | Description |
+|---------------------|-------------------|----------|---------|-------------|
+| `creationTimestamp` | string | **Yes** | | |
+| `finalizers` | string[] | **Yes** | | |
+| `labels` | map[string]string | **Yes** | | |
+| `resourceVersion` | string | **Yes** | | |
+| `uid` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | |
+
+### ExtraFields
+
+extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Spec
+
+| Property | Type | Required | Default | Description |
+|---------------|---------|----------|---------|-----------------------------------------------------------|
+| `hidden` | boolean | **Yes** | | Do not show this role |
+| `name` | string | **Yes** | | The role identifier `managed:builtins:editor:permissions` |
+| `description` | string | No | | Role description |
+| `displayName` | string | No | | Optional display |
+| `groupName` | string | No | | Name of the team. |
+
+### Status
+
+| Property | Type | Required | Default | Description |
+|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use |
+| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. |
+
+### AdditionalFields
+
+additionalFields is reserved for future use
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Status.#OperatorState
+
+| Property | Type | Required | Default | Description |
+|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated |
+| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. |
+| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format |
+| `details` | [object](#details) | No | | details contains any extra information that is operator-specific |
+
+### Details
+
+details contains any extra information that is operator-specific
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+
diff --git a/docs/sources/developers/kinds/core/rolebinding/schema-reference.md b/docs/sources/developers/kinds/core/rolebinding/schema-reference.md
new file mode 100644
index 00000000000..9e99fa9eba6
--- /dev/null
+++ b/docs/sources/developers/kinds/core/rolebinding/schema-reference.md
@@ -0,0 +1,131 @@
+---
+keywords:
+ - grafana
+ - schema
+title: RoleBinding kind
+---
+> Both documentation generation and kinds schemas are in active development and subject to change without prior notice.
+
+## RoleBinding
+
+#### Maturity: [merged](../../../maturity/#merged)
+#### Version: 0.0
+
+Role bindings links a user|team to a configured role
+
+| Property | Type | Required | Default | Description |
+|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. |
+| `spec` | [object](#spec) | **Yes** | | |
+| `status` | [object](#status) | **Yes** | | |
+
+### Metadata
+
+metadata contains embedded CommonMetadata and can be extended with custom string fields
+TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
+without external reference as using the CommonMetadata reference breaks thema codegen.
+
+It extends [_kubeObjectMetadata](#_kubeobjectmetadata).
+
+| Property | Type | Required | Default | Description |
+|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| `createdBy` | string | **Yes** | | |
+| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata |
+| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+| `updateTimestamp` | string | **Yes** | | |
+| `updatedBy` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* |
+
+### _kubeObjectMetadata
+
+_kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+As it is also intended to be generic enough to function with any API Server.
+
+| Property | Type | Required | Default | Description |
+|---------------------|-------------------|----------|---------|-------------|
+| `creationTimestamp` | string | **Yes** | | |
+| `finalizers` | string[] | **Yes** | | |
+| `labels` | map[string]string | **Yes** | | |
+| `resourceVersion` | string | **Yes** | | |
+| `uid` | string | **Yes** | | |
+| `deletionTimestamp` | string | No | | |
+
+### ExtraFields
+
+extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Spec
+
+| Property | Type | Required | Default | Description |
+|-----------|-------------------------------------------|----------|---------|----------------------------|
+| `role` | [object](#role) | **Yes** | | The role we are discussing |
+| `subject` | [RoleBindingSubject](#rolebindingsubject) | **Yes** | | |
+
+### RoleBindingSubject
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|--------------------------------------|
+| `kind` | string | **Yes** | | Possible values are: `Team`, `User`. |
+| `name` | string | **Yes** | | The team/user identifier name |
+
+### Role
+
+The role we are discussing
+
+| Property | Type | Required | Default | Description |
+|----------|-----------------------------------------------------------------------------------------|----------|---------|-------------|
+| `object` | Possible types are: [BuiltinRoleRef](#builtinroleref), [CustomRoleRef](#customroleref). | | |
+
+### BuiltinRoleRef
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|---------------------------------------------------|
+| `kind` | string | **Yes** | | Possible values are: `BuiltinRole`. |
+| `name` | string | **Yes** | | Possible values are: `viewer`, `editor`, `admin`. |
+
+### CustomRoleRef
+
+| Property | Type | Required | Default | Description |
+|----------|--------|----------|---------|------------------------------|
+| `kind` | string | **Yes** | | Possible values are: `Role`. |
+| `name` | string | **Yes** | | |
+
+### Status
+
+| Property | Type | Required | Default | Description |
+|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use |
+| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. |
+
+### AdditionalFields
+
+additionalFields is reserved for future use
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+### Status.#OperatorState
+
+| Property | Type | Required | Default | Description |
+|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated |
+| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. |
+| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format |
+| `details` | [object](#details) | No | | details contains any extra information that is operator-specific |
+
+### Details
+
+details contains any extra information that is operator-specific
+
+| Property | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+
+
diff --git a/kinds/accesspolicy/access_policy_kind.cue b/kinds/accesspolicy/access_policy_kind.cue
new file mode 100644
index 00000000000..685efe2ec9f
--- /dev/null
+++ b/kinds/accesspolicy/access_policy_kind.cue
@@ -0,0 +1,51 @@
+package kind
+
+name: "AccessPolicy"
+maturity: "merged"
+description: "Access rules for a scope+role. NOTE there is a unique constraint on role+scope"
+pluralName: "AccessPolicies"
+machineName: "accesspolicy"
+pluralMachineName: "accesspolicies"
+
+lineage: schemas: [{
+ version: [0, 0]
+ schema: {
+ spec: {
+ // The scope where these policies should apply
+ scope: #ResourceRef
+
+ // The role that must apply this policy
+ role: #RoleRef
+
+ // The set of rules to apply. Note that * is required to modify
+ // access policy rules, and that "none" will reject all actions
+ rules: [...#AccessRule]
+ } @cuetsy(kind="interface")
+
+ #RoleRef: {
+ // Policies can apply to roles, teams, or users
+ // Applying policies to individual users is supported, but discouraged
+ kind: "Role" | "BuiltinRole" | "Team" | "User"
+ name: string
+ xname: string // temporary
+ } @cuetsy(kind="interface")
+
+ #ResourceRef: {
+ kind: string // explicit resource or folder will cascade
+ name: string
+ } @cuetsy(kind="interface")
+
+ #AccessRule: {
+ // The kind this rule applies to (dashboars, alert, etc)
+ kind: "*" | string
+
+ // READ, WRITE, CREATE, DELETE, ...
+ // should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete"
+ verb: "*" | "none" | string
+
+ // Specific sub-elements like "alert.rules" or "dashboard.permissions"????
+ target?: string
+ } @cuetsy(kind="interface")
+ }
+},
+]
diff --git a/kinds/role/role_kind.cue b/kinds/role/role_kind.cue
new file mode 100644
index 00000000000..6e80d2abbde
--- /dev/null
+++ b/kinds/role/role_kind.cue
@@ -0,0 +1,25 @@
+package kind
+
+name: "Role"
+maturity: "merged"
+description: "Roles represent a set of users+teams that should share similar access"
+
+lineage: schemas: [{
+ version: [0, 0]
+ schema: {
+ spec: {
+ // The role identifier `managed:builtins:editor:permissions`
+ name: string
+ // Optional display
+ displayName?: string
+ // Name of the team.
+ groupName?: string
+ // Role description
+ description?: string
+
+ // Do not show this role
+ hidden: bool | false
+ } @cuetsy(kind="interface")
+ }
+},
+]
diff --git a/kinds/rolebinding/role_binding_kind.cue b/kinds/rolebinding/role_binding_kind.cue
new file mode 100644
index 00000000000..c76a633a721
--- /dev/null
+++ b/kinds/rolebinding/role_binding_kind.cue
@@ -0,0 +1,36 @@
+package kind
+
+name: "RoleBinding"
+maturity: "merged"
+description: "Role bindings links a user|team to a configured role"
+
+lineage: schemas: [{
+ version: [0, 0]
+ schema: {
+ spec: {
+ // The role we are discussing
+ role: #BuiltinRoleRef | #CustomRoleRef
+
+ // The team or user that has the specified role
+ subject: #RoleBindingSubject
+ } @cuetsy(kind="interface")
+
+ #CustomRoleRef: {
+ kind: "Role"
+ name: string
+ } @cuetsy(kind="interface")
+
+ #BuiltinRoleRef: {
+ kind: "BuiltinRole"
+ name: "viewer" | "editor" | "admin"
+ } @cuetsy(kind="interface")
+
+ #RoleBindingSubject: {
+ kind: "Team" | "User"
+
+ // The team/user identifier name
+ name: string
+ } @cuetsy(kind="interface")
+ }
+},
+]
diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts
index 8f3939960e5..638cc3daae0 100644
--- a/packages/grafana-schema/src/index.gen.ts
+++ b/packages/grafana-schema/src/index.gen.ts
@@ -7,6 +7,17 @@
//
// Run 'make gen-cue' from repository root to regenerate.
+// Raw generated types from AccessPolicy kind.
+export type {
+ AccessPolicy,
+ RoleRef,
+ ResourceRef,
+ AccessRule
+} from './raw/accesspolicy/x/accesspolicy_types.gen';
+
+// Raw generated enums and default consts from accesspolicy kind.
+export { defaultAccessPolicy } from './raw/accesspolicy/x/accesspolicy_types.gen';
+
// Raw generated types from Dashboard kind.
export type {
AnnotationTarget,
@@ -129,6 +140,17 @@ export type {
// Raw generated types from PublicDashboard kind.
export type { PublicDashboard } from './raw/publicdashboard/x/publicdashboard_types.gen';
+// Raw generated types from Role kind.
+export type { Role } from './raw/role/x/role_types.gen';
+
+// Raw generated types from RoleBinding kind.
+export type {
+ RoleBinding,
+ CustomRoleRef,
+ BuiltinRoleRef,
+ RoleBindingSubject
+} from './raw/rolebinding/x/rolebinding_types.gen';
+
// Raw generated types from ServiceAccount kind.
export type {
ServiceAccount,
diff --git a/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts b/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts
new file mode 100644
index 00000000000..2d27585b992
--- /dev/null
+++ b/packages/grafana-schema/src/raw/accesspolicy/x/accesspolicy_types.gen.ts
@@ -0,0 +1,60 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// TSResourceJenny
+// LatestMajorsOrXJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+export interface RoleRef {
+ /**
+ * Policies can apply to roles, teams, or users
+ * Applying policies to individual users is supported, but discouraged
+ */
+ kind: ('Role' | 'BuiltinRole' | 'Team' | 'User');
+ name: string;
+ xname: string; // temporary
+}
+
+export interface ResourceRef {
+ kind: string; // explicit resource or folder will cascade
+ name: string;
+}
+
+export interface AccessRule {
+ /**
+ * The kind this rule applies to (dashboars, alert, etc)
+ */
+ kind: ('*' | string);
+ /**
+ * Specific sub-elements like "alert.rules" or "dashboard.permissions"????
+ */
+ target?: string;
+ /**
+ * READ, WRITE, CREATE, DELETE, ...
+ * should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete"
+ */
+ verb: ('*' | 'none' | string);
+}
+
+export interface AccessPolicy {
+ /**
+ * The role that must apply this policy
+ */
+ role: RoleRef;
+ /**
+ * The set of rules to apply. Note that * is required to modify
+ * access policy rules, and that "none" will reject all actions
+ */
+ rules: Array;
+ /**
+ * The scope where these policies should apply
+ */
+ scope: ResourceRef;
+}
+
+export const defaultAccessPolicy: Partial = {
+ rules: [],
+};
diff --git a/packages/grafana-schema/src/raw/role/x/role_types.gen.ts b/packages/grafana-schema/src/raw/role/x/role_types.gen.ts
new file mode 100644
index 00000000000..781665e9250
--- /dev/null
+++ b/packages/grafana-schema/src/raw/role/x/role_types.gen.ts
@@ -0,0 +1,32 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// TSResourceJenny
+// LatestMajorsOrXJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+export interface Role {
+ /**
+ * Role description
+ */
+ description?: string;
+ /**
+ * Optional display
+ */
+ displayName?: string;
+ /**
+ * Name of the team.
+ */
+ groupName?: string;
+ /**
+ * Do not show this role
+ */
+ hidden: (boolean | false);
+ /**
+ * The role identifier `managed:builtins:editor:permissions`
+ */
+ name: string;
+}
diff --git a/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts b/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts
new file mode 100644
index 00000000000..a41535cc5df
--- /dev/null
+++ b/packages/grafana-schema/src/raw/rolebinding/x/rolebinding_types.gen.ts
@@ -0,0 +1,38 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// TSResourceJenny
+// LatestMajorsOrXJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+export interface CustomRoleRef {
+ kind: 'Role';
+ name: string;
+}
+
+export interface BuiltinRoleRef {
+ kind: 'BuiltinRole';
+ name: ('viewer' | 'editor' | 'admin');
+}
+
+export interface RoleBindingSubject {
+ kind: ('Team' | 'User');
+ /**
+ * The team/user identifier name
+ */
+ name: string;
+}
+
+export interface RoleBinding {
+ /**
+ * The role we are discussing
+ */
+ role: (BuiltinRoleRef | CustomRoleRef);
+ /**
+ * The team or user that has the specified role
+ */
+ subject: RoleBindingSubject;
+}
diff --git a/pkg/kinds/accesspolicy/accesspolicy_gen.go b/pkg/kinds/accesspolicy/accesspolicy_gen.go
new file mode 100644
index 00000000000..b7cba028b33
--- /dev/null
+++ b/pkg/kinds/accesspolicy/accesspolicy_gen.go
@@ -0,0 +1,40 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoTypesJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package accesspolicy
+
+import (
+ "github.com/grafana/grafana/pkg/kinds"
+)
+
+// Resource is the kubernetes style representation of AccessPolicy. (TODO be better)
+type K8sResource = kinds.GrafanaResource[Spec, Status]
+
+// NewResource creates a new instance of the resource with a given name (UID)
+func NewK8sResource(name string, s *Spec) K8sResource {
+ return K8sResource{
+ Kind: "AccessPolicy",
+ APIVersion: "v0.0-alpha",
+ Metadata: kinds.GrafanaResourceMetadata{
+ Name: name,
+ Annotations: make(map[string]string),
+ Labels: make(map[string]string),
+ },
+ Spec: s,
+ }
+}
+
+
+// Resource is the wire representation of AccessPolicy.
+// It currently will soon be merged into the k8s flavor (TODO be better)
+type Resource struct {
+ Metadata Metadata `json:"metadata"`
+ Spec Spec `json:"spec"`
+ Status Status `json:"status"`
+}
diff --git a/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go b/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go
new file mode 100644
index 00000000000..200ad2653d0
--- /dev/null
+++ b/pkg/kinds/accesspolicy/accesspolicy_kind_gen.go
@@ -0,0 +1,79 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// CoreKindJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package accesspolicy
+
+import (
+ "github.com/grafana/kindsys"
+ "github.com/grafana/thema"
+ "github.com/grafana/thema/vmux"
+
+ "github.com/grafana/grafana/pkg/cuectx"
+)
+
+// rootrel is the relative path from the grafana repository root to the
+// directory containing the .cue files in which this kind is defined. Necessary
+// for runtime errors related to the definition and/or lineage to provide
+// a real path to the correct .cue file.
+const rootrel string = "kinds/accesspolicy"
+
+// TODO standard generated docs
+type Kind struct {
+ kindsys.Core
+ lin thema.ConvergentLineage[*Resource]
+ jcodec vmux.Codec
+ valmux vmux.ValueMux[*Resource]
+}
+
+// type guard - ensure generated Kind type satisfies the kindsys.Core interface
+var _ kindsys.Core = &Kind{}
+
+// TODO standard generated docs
+func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
+ def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ k := &Kind{}
+ k.Core, err = kindsys.BindCore(rt, def, opts...)
+ if err != nil {
+ return nil, err
+ }
+ // Get the thema.Schema that the meta says is in the current version (which
+ // codegen ensures is always the latest)
+ cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion)
+ tsch, err := thema.BindType(cursch, &Resource{})
+ if err != nil {
+ // Should be unreachable, modulo bugs in the Thema->Go code generator
+ return nil, err
+ }
+
+ k.jcodec = vmux.NewJSONCodec("accesspolicy.json")
+ k.lin = tsch.ConvergentLineage()
+ k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
+ return k, nil
+}
+
+// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType])
+// to the the AccessPolicy [Resource] type generated from the current schema, v0.0.
+func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] {
+ return k.lin
+}
+
+// JSONValueMux is a version multiplexer that maps a []byte containing JSON data
+// at any schematized dashboard version to an instance of AccessPolicy [Resource].
+//
+// Validation and translation errors emitted from this func will identify the
+// input bytes as "dashboard.json".
+//
+// This is a thin wrapper around Thema's [vmux.ValueMux].
+func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) {
+ return k.valmux(b)
+}
diff --git a/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go b/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go
new file mode 100644
index 00000000000..42ed53ded9d
--- /dev/null
+++ b/pkg/kinds/accesspolicy/accesspolicy_metadata_gen.go
@@ -0,0 +1,42 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package accesspolicy
+
+import (
+ "time"
+)
+
+// Metadata defines model for Metadata.
+type Metadata struct {
+ CreatedBy string `json:"createdBy"`
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+
+ // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+ ExtraFields map[string]interface{} `json:"extraFields"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+ UpdateTimestamp time.Time `json:"updateTimestamp"`
+ UpdatedBy string `json:"updatedBy"`
+}
+
+// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+// It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+// As it is also intended to be generic enough to function with any API Server.
+type KubeObjectMetadata struct {
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+}
diff --git a/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go b/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go
new file mode 100644
index 00000000000..4aaafebe939
--- /dev/null
+++ b/pkg/kinds/accesspolicy/accesspolicy_spec_gen.go
@@ -0,0 +1,60 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package accesspolicy
+
+// Defines values for RoleRefKind.
+const (
+ RoleRefKindBuiltinRole RoleRefKind = "BuiltinRole"
+ RoleRefKindRole RoleRefKind = "Role"
+ RoleRefKindTeam RoleRefKind = "Team"
+ RoleRefKindUser RoleRefKind = "User"
+)
+
+// AccessRule defines model for AccessRule.
+type AccessRule struct {
+ // The kind this rule applies to (dashboars, alert, etc)
+ Kind string `json:"kind"`
+
+ // Specific sub-elements like "alert.rules" or "dashboard.permissions"????
+ Target *string `json:"target,omitempty"`
+
+ // READ, WRITE, CREATE, DELETE, ...
+ // should move to k8s style verbs like: "get", "list", "watch", "create", "update", "patch", "delete"
+ Verb string `json:"verb"`
+}
+
+// ResourceRef defines model for ResourceRef.
+type ResourceRef struct {
+ Kind string `json:"kind"`
+ Name string `json:"name"`
+}
+
+// RoleRef defines model for RoleRef.
+type RoleRef struct {
+ // Policies can apply to roles, teams, or users
+ // Applying policies to individual users is supported, but discouraged
+ Kind RoleRefKind `json:"kind"`
+ Name string `json:"name"`
+ Xname string `json:"xname"`
+}
+
+// Policies can apply to roles, teams, or users
+// Applying policies to individual users is supported, but discouraged
+type RoleRefKind string
+
+// Spec defines model for Spec.
+type Spec struct {
+ Role RoleRef `json:"role"`
+
+ // The set of rules to apply. Note that * is required to modify
+ // access policy rules, and that "none" will reject all actions
+ Rules []AccessRule `json:"rules"`
+ Scope ResourceRef `json:"scope"`
+}
diff --git a/pkg/kinds/accesspolicy/accesspolicy_status_gen.go b/pkg/kinds/accesspolicy/accesspolicy_status_gen.go
new file mode 100644
index 00000000000..90860518a49
--- /dev/null
+++ b/pkg/kinds/accesspolicy/accesspolicy_status_gen.go
@@ -0,0 +1,74 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package accesspolicy
+
+// Defines values for OperatorStateState.
+const (
+ OperatorStateStateFailed OperatorStateState = "failed"
+ OperatorStateStateInProgress OperatorStateState = "in_progress"
+ OperatorStateStateSuccess OperatorStateState = "success"
+)
+
+// Defines values for StatusOperatorStateState.
+const (
+ StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
+ StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
+ StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
+)
+
+// OperatorState defines model for OperatorState.
+type OperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State OperatorStateState `json:"state"`
+}
+
+// OperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type OperatorStateState string
+
+// Status defines model for Status.
+type Status struct {
+ // additionalFields is reserved for future use
+ AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
+
+ // operatorStates is a map of operator ID to operator state evaluations.
+ // Any operator which consumes this kind SHOULD add its state evaluation information to this field.
+ OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
+}
+
+// StatusOperatorState defines model for status.#OperatorState.
+type StatusOperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State StatusOperatorStateState `json:"state"`
+}
+
+// StatusOperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type StatusOperatorStateState string
diff --git a/pkg/kinds/accesspolicy/utils.go b/pkg/kinds/accesspolicy/utils.go
new file mode 100644
index 00000000000..c043af90761
--- /dev/null
+++ b/pkg/kinds/accesspolicy/utils.go
@@ -0,0 +1,99 @@
+package accesspolicy
+
+import (
+ "sort"
+
+ "github.com/grafana/grafana/pkg/util"
+)
+
+const PermissionsTarget = "permissions"
+const AllowAll = "*"
+const AllowNone = "none"
+
+func ReduceRules(rules []AccessRule) []AccessRule {
+ type verbs struct {
+ Verb map[string][]string
+ Terminal string
+ }
+
+ kinds := make(map[string]*verbs)
+ for _, rule := range rules {
+ if rule.Kind == "" || rule.Verb == "" {
+ continue // invalid
+ }
+
+ // flip write permission to *
+ if rule.Target != nil && *rule.Target == PermissionsTarget {
+ if rule.Verb == "write" {
+ rule.Verb = AllowAll
+ }
+ }
+ kind, ok := kinds[rule.Kind]
+ if !ok {
+ kind = &verbs{
+ Verb: make(map[string][]string),
+ }
+ kinds[rule.Kind] = kind
+ }
+
+ terminal := rule.Verb == AllowAll || rule.Verb == AllowNone
+ if terminal {
+ if rule.Kind == AllowAll {
+ return []AccessRule{rule}
+ }
+ kind.Terminal = rule.Verb
+ } else if kind.Terminal == "" {
+ targets, ok := kind.Verb[rule.Verb]
+ if !ok {
+ targets = []string{}
+ }
+ if rule.Target != nil && !contains(targets, *rule.Target) {
+ targets = append(targets, *rule.Target)
+ sort.Strings(targets)
+ }
+ kind.Verb[rule.Verb] = targets
+ }
+ }
+
+ results := make([]AccessRule, 0)
+ for _, kind := range getSortedKeys(kinds) {
+ verb := kinds[kind]
+ if verb.Terminal != "" {
+ results = append(results, AccessRule{Kind: kind, Verb: verb.Terminal})
+ } else {
+ for _, v := range getSortedKeys(verb.Verb) {
+ targets := verb.Verb[v]
+ if len(targets) == 0 {
+ results = append(results, AccessRule{Kind: kind, Verb: v})
+ } else {
+ for _, t := range targets {
+ results = append(results, AccessRule{
+ Kind: kind,
+ Verb: v,
+ Target: util.Pointer(t),
+ })
+ }
+ }
+ }
+ }
+ }
+ return results
+}
+
+func getSortedKeys[T any](vals map[string]T) []string {
+ keys := make([]string, 0, len(vals))
+ for k := range vals {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+func contains[T comparable](s []T, e T) bool {
+ for _, v := range s {
+ if v == e {
+ return true
+ }
+ }
+ return false
+}
diff --git a/pkg/kinds/accesspolicy/utils_test.go b/pkg/kinds/accesspolicy/utils_test.go
new file mode 100644
index 00000000000..314a8229346
--- /dev/null
+++ b/pkg/kinds/accesspolicy/utils_test.go
@@ -0,0 +1,68 @@
+package accesspolicy
+
+import (
+ "encoding/json"
+ "fmt"
+ "testing"
+
+ "github.com/grafana/grafana/pkg/util"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRuleReducer(t *testing.T) {
+ t.Run("Check write pointer becomes star", func(t *testing.T) {
+ rules := ReduceRules([]AccessRule{
+ {Kind: "dashboard", Verb: "read"},
+ {Kind: "dashboard", Verb: "write", Target: util.Pointer("permissions")},
+ {Kind: "dashboard", Verb: "read"},
+ })
+ require.Len(t, rules, 1)
+ require.Equal(t, rules[0], AccessRule{Kind: "dashboard", Verb: "*"})
+ })
+
+ t.Run("Check sort", func(t *testing.T) {
+ rules := ReduceRules([]AccessRule{
+ {Kind: "x", Verb: "b"},
+ {Kind: "x", Verb: "a"},
+ {Kind: "x", Verb: "a"}, // ignore duplicates
+ {Kind: "x", Verb: "a"}, // ignore duplicates
+ {Kind: "x", Verb: "a"}, // ignore duplicates
+ {Kind: "x", Verb: "a"},
+ {Kind: "z", Verb: "b"},
+ {Kind: "AAA", Verb: ""}, // ignore
+ {Kind: "", Verb: "XXX"}, // ignore
+ {Kind: "z", Verb: "a"},
+ {Kind: "y", Verb: "b"},
+ {Kind: "y", Verb: "a"},
+ })
+ out, err := json.MarshalIndent(rules, "", " ")
+ fmt.Printf("%s", string(out))
+ require.NoError(t, err)
+ require.JSONEq(t, `[
+ {
+ "kind": "x",
+ "verb": "a"
+ },
+ {
+ "kind": "x",
+ "verb": "b"
+ },
+ {
+ "kind": "y",
+ "verb": "a"
+ },
+ {
+ "kind": "y",
+ "verb": "b"
+ },
+ {
+ "kind": "z",
+ "verb": "a"
+ },
+ {
+ "kind": "z",
+ "verb": "b"
+ }
+ ]`, string(out))
+ })
+}
diff --git a/pkg/kinds/role/role_gen.go b/pkg/kinds/role/role_gen.go
new file mode 100644
index 00000000000..20d99cabb1c
--- /dev/null
+++ b/pkg/kinds/role/role_gen.go
@@ -0,0 +1,40 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoTypesJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package role
+
+import (
+ "github.com/grafana/grafana/pkg/kinds"
+)
+
+// Resource is the kubernetes style representation of Role. (TODO be better)
+type K8sResource = kinds.GrafanaResource[Spec, Status]
+
+// NewResource creates a new instance of the resource with a given name (UID)
+func NewK8sResource(name string, s *Spec) K8sResource {
+ return K8sResource{
+ Kind: "Role",
+ APIVersion: "v0.0-alpha",
+ Metadata: kinds.GrafanaResourceMetadata{
+ Name: name,
+ Annotations: make(map[string]string),
+ Labels: make(map[string]string),
+ },
+ Spec: s,
+ }
+}
+
+
+// Resource is the wire representation of Role.
+// It currently will soon be merged into the k8s flavor (TODO be better)
+type Resource struct {
+ Metadata Metadata `json:"metadata"`
+ Spec Spec `json:"spec"`
+ Status Status `json:"status"`
+}
diff --git a/pkg/kinds/role/role_kind_gen.go b/pkg/kinds/role/role_kind_gen.go
new file mode 100644
index 00000000000..cc297cb0807
--- /dev/null
+++ b/pkg/kinds/role/role_kind_gen.go
@@ -0,0 +1,79 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// CoreKindJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package role
+
+import (
+ "github.com/grafana/kindsys"
+ "github.com/grafana/thema"
+ "github.com/grafana/thema/vmux"
+
+ "github.com/grafana/grafana/pkg/cuectx"
+)
+
+// rootrel is the relative path from the grafana repository root to the
+// directory containing the .cue files in which this kind is defined. Necessary
+// for runtime errors related to the definition and/or lineage to provide
+// a real path to the correct .cue file.
+const rootrel string = "kinds/role"
+
+// TODO standard generated docs
+type Kind struct {
+ kindsys.Core
+ lin thema.ConvergentLineage[*Resource]
+ jcodec vmux.Codec
+ valmux vmux.ValueMux[*Resource]
+}
+
+// type guard - ensure generated Kind type satisfies the kindsys.Core interface
+var _ kindsys.Core = &Kind{}
+
+// TODO standard generated docs
+func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
+ def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ k := &Kind{}
+ k.Core, err = kindsys.BindCore(rt, def, opts...)
+ if err != nil {
+ return nil, err
+ }
+ // Get the thema.Schema that the meta says is in the current version (which
+ // codegen ensures is always the latest)
+ cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion)
+ tsch, err := thema.BindType(cursch, &Resource{})
+ if err != nil {
+ // Should be unreachable, modulo bugs in the Thema->Go code generator
+ return nil, err
+ }
+
+ k.jcodec = vmux.NewJSONCodec("role.json")
+ k.lin = tsch.ConvergentLineage()
+ k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
+ return k, nil
+}
+
+// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType])
+// to the the Role [Resource] type generated from the current schema, v0.0.
+func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] {
+ return k.lin
+}
+
+// JSONValueMux is a version multiplexer that maps a []byte containing JSON data
+// at any schematized dashboard version to an instance of Role [Resource].
+//
+// Validation and translation errors emitted from this func will identify the
+// input bytes as "dashboard.json".
+//
+// This is a thin wrapper around Thema's [vmux.ValueMux].
+func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) {
+ return k.valmux(b)
+}
diff --git a/pkg/kinds/role/role_metadata_gen.go b/pkg/kinds/role/role_metadata_gen.go
new file mode 100644
index 00000000000..b75ecfdea96
--- /dev/null
+++ b/pkg/kinds/role/role_metadata_gen.go
@@ -0,0 +1,42 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package role
+
+import (
+ "time"
+)
+
+// Metadata defines model for Metadata.
+type Metadata struct {
+ CreatedBy string `json:"createdBy"`
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+
+ // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+ ExtraFields map[string]interface{} `json:"extraFields"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+ UpdateTimestamp time.Time `json:"updateTimestamp"`
+ UpdatedBy string `json:"updatedBy"`
+}
+
+// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+// It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+// As it is also intended to be generic enough to function with any API Server.
+type KubeObjectMetadata struct {
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+}
diff --git a/pkg/kinds/role/role_spec_gen.go b/pkg/kinds/role/role_spec_gen.go
new file mode 100644
index 00000000000..ca4c2329268
--- /dev/null
+++ b/pkg/kinds/role/role_spec_gen.go
@@ -0,0 +1,28 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package role
+
+// Spec defines model for Spec.
+type Spec struct {
+ // Role description
+ Description *string `json:"description,omitempty"`
+
+ // Optional display
+ DisplayName *string `json:"displayName,omitempty"`
+
+ // Name of the team.
+ GroupName *string `json:"groupName,omitempty"`
+
+ // Do not show this role
+ Hidden bool `json:"hidden"`
+
+ // The role identifier `managed:builtins:editor:permissions`
+ Name string `json:"name"`
+}
diff --git a/pkg/kinds/role/role_status_gen.go b/pkg/kinds/role/role_status_gen.go
new file mode 100644
index 00000000000..8ffe1202dd5
--- /dev/null
+++ b/pkg/kinds/role/role_status_gen.go
@@ -0,0 +1,74 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package role
+
+// Defines values for OperatorStateState.
+const (
+ OperatorStateStateFailed OperatorStateState = "failed"
+ OperatorStateStateInProgress OperatorStateState = "in_progress"
+ OperatorStateStateSuccess OperatorStateState = "success"
+)
+
+// Defines values for StatusOperatorStateState.
+const (
+ StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
+ StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
+ StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
+)
+
+// OperatorState defines model for OperatorState.
+type OperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State OperatorStateState `json:"state"`
+}
+
+// OperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type OperatorStateState string
+
+// Status defines model for Status.
+type Status struct {
+ // additionalFields is reserved for future use
+ AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
+
+ // operatorStates is a map of operator ID to operator state evaluations.
+ // Any operator which consumes this kind SHOULD add its state evaluation information to this field.
+ OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
+}
+
+// StatusOperatorState defines model for status.#OperatorState.
+type StatusOperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State StatusOperatorStateState `json:"state"`
+}
+
+// StatusOperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type StatusOperatorStateState string
diff --git a/pkg/kinds/rolebinding/rolebinding_gen.go b/pkg/kinds/rolebinding/rolebinding_gen.go
new file mode 100644
index 00000000000..ae30f61ade3
--- /dev/null
+++ b/pkg/kinds/rolebinding/rolebinding_gen.go
@@ -0,0 +1,40 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoTypesJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package rolebinding
+
+import (
+ "github.com/grafana/grafana/pkg/kinds"
+)
+
+// Resource is the kubernetes style representation of RoleBinding. (TODO be better)
+type K8sResource = kinds.GrafanaResource[Spec, Status]
+
+// NewResource creates a new instance of the resource with a given name (UID)
+func NewK8sResource(name string, s *Spec) K8sResource {
+ return K8sResource{
+ Kind: "RoleBinding",
+ APIVersion: "v0.0-alpha",
+ Metadata: kinds.GrafanaResourceMetadata{
+ Name: name,
+ Annotations: make(map[string]string),
+ Labels: make(map[string]string),
+ },
+ Spec: s,
+ }
+}
+
+
+// Resource is the wire representation of RoleBinding.
+// It currently will soon be merged into the k8s flavor (TODO be better)
+type Resource struct {
+ Metadata Metadata `json:"metadata"`
+ Spec Spec `json:"spec"`
+ Status Status `json:"status"`
+}
diff --git a/pkg/kinds/rolebinding/rolebinding_kind_gen.go b/pkg/kinds/rolebinding/rolebinding_kind_gen.go
new file mode 100644
index 00000000000..603b1d96747
--- /dev/null
+++ b/pkg/kinds/rolebinding/rolebinding_kind_gen.go
@@ -0,0 +1,79 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// CoreKindJenny
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package rolebinding
+
+import (
+ "github.com/grafana/kindsys"
+ "github.com/grafana/thema"
+ "github.com/grafana/thema/vmux"
+
+ "github.com/grafana/grafana/pkg/cuectx"
+)
+
+// rootrel is the relative path from the grafana repository root to the
+// directory containing the .cue files in which this kind is defined. Necessary
+// for runtime errors related to the definition and/or lineage to provide
+// a real path to the correct .cue file.
+const rootrel string = "kinds/rolebinding"
+
+// TODO standard generated docs
+type Kind struct {
+ kindsys.Core
+ lin thema.ConvergentLineage[*Resource]
+ jcodec vmux.Codec
+ valmux vmux.ValueMux[*Resource]
+}
+
+// type guard - ensure generated Kind type satisfies the kindsys.Core interface
+var _ kindsys.Core = &Kind{}
+
+// TODO standard generated docs
+func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
+ def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ k := &Kind{}
+ k.Core, err = kindsys.BindCore(rt, def, opts...)
+ if err != nil {
+ return nil, err
+ }
+ // Get the thema.Schema that the meta says is in the current version (which
+ // codegen ensures is always the latest)
+ cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion)
+ tsch, err := thema.BindType(cursch, &Resource{})
+ if err != nil {
+ // Should be unreachable, modulo bugs in the Thema->Go code generator
+ return nil, err
+ }
+
+ k.jcodec = vmux.NewJSONCodec("rolebinding.json")
+ k.lin = tsch.ConvergentLineage()
+ k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
+ return k, nil
+}
+
+// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType])
+// to the the RoleBinding [Resource] type generated from the current schema, v0.0.
+func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] {
+ return k.lin
+}
+
+// JSONValueMux is a version multiplexer that maps a []byte containing JSON data
+// at any schematized dashboard version to an instance of RoleBinding [Resource].
+//
+// Validation and translation errors emitted from this func will identify the
+// input bytes as "dashboard.json".
+//
+// This is a thin wrapper around Thema's [vmux.ValueMux].
+func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) {
+ return k.valmux(b)
+}
diff --git a/pkg/kinds/rolebinding/rolebinding_metadata_gen.go b/pkg/kinds/rolebinding/rolebinding_metadata_gen.go
new file mode 100644
index 00000000000..fcdc39659c5
--- /dev/null
+++ b/pkg/kinds/rolebinding/rolebinding_metadata_gen.go
@@ -0,0 +1,42 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package rolebinding
+
+import (
+ "time"
+)
+
+// Metadata defines model for Metadata.
+type Metadata struct {
+ CreatedBy string `json:"createdBy"`
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+
+ // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata
+ ExtraFields map[string]interface{} `json:"extraFields"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+ UpdateTimestamp time.Time `json:"updateTimestamp"`
+ UpdatedBy string `json:"updatedBy"`
+}
+
+// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field.
+// It is not exhaustive and only includes fields which may be relevant to a kind's implementation,
+// As it is also intended to be generic enough to function with any API Server.
+type KubeObjectMetadata struct {
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+ Finalizers []string `json:"finalizers"`
+ Labels map[string]string `json:"labels"`
+ ResourceVersion string `json:"resourceVersion"`
+ Uid string `json:"uid"`
+}
diff --git a/pkg/kinds/rolebinding/rolebinding_spec_gen.go b/pkg/kinds/rolebinding/rolebinding_spec_gen.go
new file mode 100644
index 00000000000..75155bd7e50
--- /dev/null
+++ b/pkg/kinds/rolebinding/rolebinding_spec_gen.go
@@ -0,0 +1,72 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package rolebinding
+
+// Defines values for BuiltinRoleRefKind.
+const (
+ BuiltinRoleRefKindBuiltinRole BuiltinRoleRefKind = "BuiltinRole"
+)
+
+// Defines values for BuiltinRoleRefName.
+const (
+ BuiltinRoleRefNameAdmin BuiltinRoleRefName = "admin"
+ BuiltinRoleRefNameEditor BuiltinRoleRefName = "editor"
+ BuiltinRoleRefNameViewer BuiltinRoleRefName = "viewer"
+)
+
+// Defines values for CustomRoleRefKind.
+const (
+ CustomRoleRefKindRole CustomRoleRefKind = "Role"
+)
+
+// Defines values for SubjectKind.
+const (
+ SubjectKindTeam SubjectKind = "Team"
+ SubjectKindUser SubjectKind = "User"
+)
+
+// BuiltinRoleRef defines model for BuiltinRoleRef.
+type BuiltinRoleRef struct {
+ Kind BuiltinRoleRefKind `json:"kind"`
+ Name BuiltinRoleRefName `json:"name"`
+}
+
+// BuiltinRoleRefKind defines model for BuiltinRoleRef.Kind.
+type BuiltinRoleRefKind string
+
+// BuiltinRoleRefName defines model for BuiltinRoleRef.Name.
+type BuiltinRoleRefName string
+
+// CustomRoleRef defines model for CustomRoleRef.
+type CustomRoleRef struct {
+ Kind CustomRoleRefKind `json:"kind"`
+ Name string `json:"name"`
+}
+
+// CustomRoleRefKind defines model for CustomRoleRef.Kind.
+type CustomRoleRefKind string
+
+// Subject defines model for Subject.
+type Subject struct {
+ Kind SubjectKind `json:"kind"`
+
+ // The team/user identifier name
+ Name string `json:"name"`
+}
+
+// SubjectKind defines model for Subject.Kind.
+type SubjectKind string
+
+// Spec defines model for Spec.
+type Spec struct {
+ // The role we are discussing
+ Role interface{} `json:"role"`
+ Subject Subject `json:"subject"`
+}
diff --git a/pkg/kinds/rolebinding/rolebinding_status_gen.go b/pkg/kinds/rolebinding/rolebinding_status_gen.go
new file mode 100644
index 00000000000..b63f86e6883
--- /dev/null
+++ b/pkg/kinds/rolebinding/rolebinding_status_gen.go
@@ -0,0 +1,74 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+//
+// Generated by:
+// kinds/gen.go
+// Using jennies:
+// GoResourceTypes
+//
+// Run 'make gen-cue' from repository root to regenerate.
+
+package rolebinding
+
+// Defines values for OperatorStateState.
+const (
+ OperatorStateStateFailed OperatorStateState = "failed"
+ OperatorStateStateInProgress OperatorStateState = "in_progress"
+ OperatorStateStateSuccess OperatorStateState = "success"
+)
+
+// Defines values for StatusOperatorStateState.
+const (
+ StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
+ StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
+ StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
+)
+
+// OperatorState defines model for OperatorState.
+type OperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State OperatorStateState `json:"state"`
+}
+
+// OperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type OperatorStateState string
+
+// Status defines model for Status.
+type Status struct {
+ // additionalFields is reserved for future use
+ AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
+
+ // operatorStates is a map of operator ID to operator state evaluations.
+ // Any operator which consumes this kind SHOULD add its state evaluation information to this field.
+ OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
+}
+
+// StatusOperatorState defines model for status.#OperatorState.
+type StatusOperatorState struct {
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State StatusOperatorStateState `json:"state"`
+}
+
+// StatusOperatorStateState state describes the state of the lastEvaluation.
+// It is limited to three possible states for machine evaluation.
+type StatusOperatorStateState string
diff --git a/pkg/registry/corekind/base_gen.go b/pkg/registry/corekind/base_gen.go
index 95fc4a49b75..f8feea1a13a 100644
--- a/pkg/registry/corekind/base_gen.go
+++ b/pkg/registry/corekind/base_gen.go
@@ -12,12 +12,15 @@ package corekind
import (
"fmt"
+ "github.com/grafana/grafana/pkg/kinds/accesspolicy"
"github.com/grafana/grafana/pkg/kinds/dashboard"
"github.com/grafana/grafana/pkg/kinds/folder"
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/kinds/playlist"
"github.com/grafana/grafana/pkg/kinds/preferences"
"github.com/grafana/grafana/pkg/kinds/publicdashboard"
+ "github.com/grafana/grafana/pkg/kinds/role"
+ "github.com/grafana/grafana/pkg/kinds/rolebinding"
"github.com/grafana/grafana/pkg/kinds/serviceaccount"
"github.com/grafana/grafana/pkg/kinds/team"
"github.com/grafana/kindsys"
@@ -41,28 +44,39 @@ import (
// kind-schematized type.
type Base struct {
all []kindsys.Core
+ accesspolicy *accesspolicy.Kind
dashboard *dashboard.Kind
folder *folder.Kind
librarypanel *librarypanel.Kind
playlist *playlist.Kind
preferences *preferences.Kind
publicdashboard *publicdashboard.Kind
+ role *role.Kind
+ rolebinding *rolebinding.Kind
serviceaccount *serviceaccount.Kind
team *team.Kind
}
// type guards
var (
+ _ kindsys.Core = &accesspolicy.Kind{}
_ kindsys.Core = &dashboard.Kind{}
_ kindsys.Core = &folder.Kind{}
_ kindsys.Core = &librarypanel.Kind{}
_ kindsys.Core = &playlist.Kind{}
_ kindsys.Core = &preferences.Kind{}
_ kindsys.Core = &publicdashboard.Kind{}
+ _ kindsys.Core = &role.Kind{}
+ _ kindsys.Core = &rolebinding.Kind{}
_ kindsys.Core = &serviceaccount.Kind{}
_ kindsys.Core = &team.Kind{}
)
+// AccessPolicy returns the [kindsys.Interface] implementation for the accesspolicy kind.
+func (b *Base) AccessPolicy() *accesspolicy.Kind {
+ return b.accesspolicy
+}
+
// Dashboard returns the [kindsys.Interface] implementation for the dashboard kind.
func (b *Base) Dashboard() *dashboard.Kind {
return b.dashboard
@@ -93,6 +107,16 @@ func (b *Base) PublicDashboard() *publicdashboard.Kind {
return b.publicdashboard
}
+// Role returns the [kindsys.Interface] implementation for the role kind.
+func (b *Base) Role() *role.Kind {
+ return b.role
+}
+
+// RoleBinding returns the [kindsys.Interface] implementation for the rolebinding kind.
+func (b *Base) RoleBinding() *rolebinding.Kind {
+ return b.rolebinding
+}
+
// ServiceAccount returns the [kindsys.Interface] implementation for the serviceaccount kind.
func (b *Base) ServiceAccount() *serviceaccount.Kind {
return b.serviceaccount
@@ -107,6 +131,12 @@ func doNewBase(rt *thema.Runtime) *Base {
var err error
reg := &Base{}
+ reg.accesspolicy, err = accesspolicy.NewKind(rt)
+ if err != nil {
+ panic(fmt.Sprintf("error while initializing the accesspolicy Kind: %s", err))
+ }
+ reg.all = append(reg.all, reg.accesspolicy)
+
reg.dashboard, err = dashboard.NewKind(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing the dashboard Kind: %s", err))
@@ -143,6 +173,18 @@ func doNewBase(rt *thema.Runtime) *Base {
}
reg.all = append(reg.all, reg.publicdashboard)
+ reg.role, err = role.NewKind(rt)
+ if err != nil {
+ panic(fmt.Sprintf("error while initializing the role Kind: %s", err))
+ }
+ reg.all = append(reg.all, reg.role)
+
+ reg.rolebinding, err = rolebinding.NewKind(rt)
+ if err != nil {
+ panic(fmt.Sprintf("error while initializing the rolebinding Kind: %s", err))
+ }
+ reg.all = append(reg.all, reg.rolebinding)
+
reg.serviceaccount, err = serviceaccount.NewKind(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing the serviceaccount Kind: %s", err))
diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go
index 29a693a1fdd..45a81415a0d 100644
--- a/pkg/services/accesscontrol/database/database_test.go
+++ b/pkg/services/accesscontrol/database/database_test.go
@@ -3,6 +3,7 @@ package database
import (
"context"
"fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -32,6 +33,7 @@ type getUserPermissionsTestCase struct {
teamPermissions []string
builtinPermissions []string
expected int
+ policyCount int
}
func TestAccessControlStore_GetUserPermissions(t *testing.T) {
@@ -44,6 +46,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 7,
+ policyCount: 7,
},
{
desc: "Should not get admin roles",
@@ -53,6 +56,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 5,
+ policyCount: 7,
},
{
desc: "Should work without org role",
@@ -62,6 +66,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 5,
+ policyCount: 7,
},
{
desc: "should only get br permissions for anonymous user",
@@ -72,6 +77,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 2,
+ policyCount: 7,
},
}
for _, tt := range tests {
@@ -132,6 +138,17 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
require.NoError(t, err)
assert.Len(t, permissions, tt.expected)
+
+ policies, err := GetAccessPolicies(context.Background(), user.OrgID, store.sql.GetSqlxSession(),
+ func(ctx context.Context, orgID int64, scope string) ([]string, error) {
+ return strings.Split(scope, ":"), nil
+ })
+ require.NoError(t, err)
+ assert.Len(t, policies, tt.policyCount)
+
+ for idx, p := range policies {
+ fmt.Printf("POLICIES[%d] %+v\n", idx, p.Spec)
+ }
})
}
}
diff --git a/pkg/services/accesscontrol/database/policies.go b/pkg/services/accesscontrol/database/policies.go
new file mode 100644
index 00000000000..b31329886e5
--- /dev/null
+++ b/pkg/services/accesscontrol/database/policies.go
@@ -0,0 +1,142 @@
+package database
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/grafana/grafana/pkg/kinds/accesspolicy"
+ "github.com/grafana/grafana/pkg/services/accesscontrol"
+ "github.com/grafana/grafana/pkg/services/sqlstore/session"
+ "github.com/grafana/grafana/pkg/util"
+)
+
+func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, resolver accesscontrol.ScopeAttributeResolverFunc) ([]accesspolicy.Resource, error) {
+ type permissionInfo struct {
+ RoleUID string
+ RoleName string
+ Scope string
+ Action string
+ Created time.Time
+ Updated time.Time
+ }
+ info := &permissionInfo{}
+ policies := make([]accesspolicy.Resource, 0)
+ current := &accesspolicy.Resource{}
+ prevKey := ""
+ rows, err := sql.Query(ctx, `SELECT
+ role.uid as role_uid,
+ role.name as role_name,
+ scope,
+ action,
+ permission.created,
+ permission.updated
+ FROM permission
+ JOIN role ON permission.role_id = role.id
+ WHERE org_id=?
+ ORDER BY role.id ASC, scope ASC, action ASC`, orgID)
+ if err != nil {
+ return nil, err
+ }
+
+ created := time.Now()
+ updated := time.Now()
+
+ for rows.Next() {
+ err = rows.Scan(
+ &info.RoleUID,
+ &info.RoleName,
+ &info.Scope,
+ &info.Action,
+ &info.Created,
+ &info.Updated,
+ )
+ if err != nil {
+ return policies, err
+ }
+
+ key := info.RoleUID + "/" + info.Scope
+ if key != prevKey {
+ created = info.Created
+ updated = info.Updated
+ if len(current.Spec.Rules) > 0 {
+ current.Spec.Rules = accesspolicy.ReduceRules(current.Spec.Rules)
+ policies = append(policies, *current)
+ }
+ scope, err := resolver(ctx, orgID, info.Scope)
+ if err != nil {
+ return policies, err
+ }
+ if len(scope) != 3 {
+ return policies, fmt.Errorf("expected three part scope")
+ }
+
+ current = &accesspolicy.Resource{
+ Metadata: accesspolicy.Metadata{
+ CreationTimestamp: created,
+ UpdateTimestamp: updated,
+ },
+ Spec: accesspolicy.Spec{
+ Role: accesspolicy.RoleRef{
+ Kind: accesspolicy.RoleRefKindRole,
+ Name: info.RoleUID,
+ Xname: info.RoleName,
+ },
+ Scope: accesspolicy.ResourceRef{
+ Kind: scope[0],
+ Name: scope[2],
+ },
+ Rules: make([]accesspolicy.AccessRule, 0),
+ },
+ }
+ // When the value is not a UID, set the prefix to $ -- an invalid name
+ if scope[1] != "uid" {
+ current.Spec.Scope.Name = fmt.Sprintf("$%s:%s", scope[1], scope[2])
+ }
+
+ // Skip role+role binding for direct users
+ if strings.HasPrefix(info.RoleName, "managed:users:") {
+ current.Spec.Role.Kind = accesspolicy.RoleRefKindUser
+ current.Spec.Role.Name = "$TODO:" + info.RoleName
+ }
+
+ prevKey = key
+ }
+
+ if info.Created.Before(created) {
+ created = info.Created
+ current.Metadata.CreationTimestamp = created
+ }
+ if info.Updated.After(updated) {
+ updated = info.Updated
+ current.Metadata.UpdateTimestamp = updated
+ }
+
+ action := strings.Split(info.Action, ":")
+ if len(action) != 2 {
+ return policies, fmt.Errorf("expected two part action")
+ }
+ parts := strings.SplitN(action[0], ".", 2)
+ rule := accesspolicy.AccessRule{
+ Verb: action[1],
+ Kind: parts[0],
+ }
+ if len(parts) > 1 {
+ rule.Target = util.Pointer(parts[1])
+ }
+
+ // // When the scope is dashboards or teams
+ // // ... hymmm ... this would imply permissions
+ // if rule.Kind == current.Spec.Scope.Kind {
+ // rule.Kind = "*"
+ // }
+
+ current.Spec.Rules = append(current.Spec.Rules, rule)
+ }
+ if current.Spec.Scope.Name != "" {
+ current.Spec.Rules = accesspolicy.ReduceRules(current.Spec.Rules)
+ policies = append(policies, *current)
+ }
+ return policies, err
+}
diff --git a/public/app/core/components/AppChrome/AppChromeUpdate.tsx b/public/app/core/components/AppChrome/AppChromeUpdate.tsx
index 7a654d5fc74..2f1596debd5 100644
--- a/public/app/core/components/AppChrome/AppChromeUpdate.tsx
+++ b/public/app/core/components/AppChrome/AppChromeUpdate.tsx
@@ -13,7 +13,7 @@ export const AppChromeUpdate = React.memo(({ actions }: Ap
const { chrome } = useGrafana();
// We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered
- // This prevents flickering actions when going from one dashbaord to another for example
+ // This prevents flickering actions when going from one dashboard to another for example
useLayoutEffect(() => {
chrome.update({ actions });
});
diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx
index 67cb493f3da..3f080d90bef 100644
--- a/public/app/core/components/Page/Page.tsx
+++ b/public/app/core/components/Page/Page.tsx
@@ -37,7 +37,7 @@ export const Page: PageType = ({
const pageHeaderNav = pageNav ?? navModel?.node;
// We use useLayoutEffect here to make sure that the chrome is updated before the page is rendered
- // This prevents flickering sectionNav when going from dashbaord to settings for example
+ // This prevents flickering sectionNav when going from dashboard to settings for example
useLayoutEffect(() => {
if (navModel) {
chrome.update({
diff --git a/public/app/features/dashboard/utils/panelMerge.test.ts b/public/app/features/dashboard/utils/panelMerge.test.ts
index 45a8195a12a..d20092310dc 100644
--- a/public/app/features/dashboard/utils/panelMerge.test.ts
+++ b/public/app/features/dashboard/utils/panelMerge.test.ts
@@ -4,7 +4,7 @@ import { FieldColorModeId, ThresholdsMode } from '@grafana/schema/src';
import { DashboardModel } from '../state/DashboardModel';
import { createDashboardModelFixture, createPanelJSONFixture } from '../state/__fixtures__/dashboardFixtures';
-describe('Merge dashbaord panels', () => {
+describe('Merge dashboard panels', () => {
describe('simple changes', () => {
let dashboard: DashboardModel;
let rawPanels: PanelModel[];
diff --git a/public/app/features/storage/storage.ts b/public/app/features/storage/storage.ts
index f527f7c7091..d164577d4a8 100644
--- a/public/app/features/storage/storage.ts
+++ b/public/app/features/storage/storage.ts
@@ -18,7 +18,7 @@ export interface GrafanaStorage {
/** Called before save */
getOptions: (path: string) => Promise;
- /** Saves dashbaords */
+ /** Saves dashboards */
write: (path: string, options: WriteValueRequest) => Promise;
}