Identity: Add read-only identity apiserver (#90418)

This commit is contained in:
Ryan McKinley 2024-07-26 17:09:08 +03:00 committed by GitHub
parent be7b1ce2df
commit ec6c6bd6c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1764 additions and 269 deletions

View File

@ -7332,9 +7332,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"] [0, 0, 0, "Do not use any type assertions.", "1"]
], ],
"public/app/types/teams.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`TeamDTO\`)", "0"]
],
"public/app/types/unified-alerting-dto.ts:5381": [ "public/app/types/unified-alerting-dto.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],

View File

@ -1,17 +0,0 @@
package kind
name: "Team"
maturity: "merged"
description: "A team is a named grouping of Grafana users to which access control rules may be assigned."
lineage: schemas: [{
version: [0, 0]
schema: {
spec: {
// Name of the team.
name: string
// Email of the team.
email?: string
} @cuetsy(kind="interface")
}
}]

View File

@ -145,6 +145,3 @@ export type {
BuiltinRoleRef, BuiltinRoleRef,
RoleBindingSubject RoleBindingSubject
} from './raw/rolebinding/x/rolebinding_types.gen'; } from './raw/rolebinding/x/rolebinding_types.gen';
// Raw generated types from Team kind.
export type { Team } from './raw/team/x/team_types.gen';

View File

@ -1,20 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
//
// Generated by:
// kinds/gen.go
// Using jennies:
// TSTypesJenny
// LatestMajorsOrXJenny
//
// Run 'make gen-cue' from repository root to regenerate.
export interface Team {
/**
* Email of the team.
*/
email?: string;
/**
* Name of the team.
*/
name: string;
}

View File

@ -0,0 +1,6 @@
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +k8s:defaulter-gen=TypeMeta
// +groupName=identity.grafana.app
package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/identity/v0alpha1"

View File

@ -0,0 +1,68 @@
package v0alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
const (
GROUP = "identity.grafana.app"
VERSION = "v0alpha1"
APIVERSION = GROUP + "/" + VERSION
)
var UserResourceInfo = common.NewResourceInfo(GROUP, VERSION,
"users", "user", "User",
func() runtime.Object { return &User{} },
func() runtime.Object { return &UserList{} },
)
var TeamResourceInfo = common.NewResourceInfo(GROUP, VERSION,
"teams", "team", "Team",
func() runtime.Object { return &Team{} },
func() runtime.Object { return &TeamList{} },
)
var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION,
"serviceaccounts", "serviceaccount", "ServiceAccount",
func() runtime.Object { return &ServiceAccount{} },
func() runtime.Object { return &ServiceAccountList{} },
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
// SchemaBuilder is used by standard codegen
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
localSchemeBuilder.Register(func(s *runtime.Scheme) error {
return AddKnownTypes(s, VERSION)
})
}
// Adds the list of known types to the given scheme.
func AddKnownTypes(scheme *runtime.Scheme, version string) error {
scheme.AddKnownTypes(schema.GroupVersion{Group: GROUP, Version: version},
&User{},
&UserList{},
&ServiceAccount{},
&ServiceAccountList{},
&Team{},
&TeamList{},
&IdentityDisplayList{},
)
// metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}

View File

@ -0,0 +1,91 @@
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type User struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec UserSpec `json:"spec,omitempty"`
}
type UserSpec struct {
Name string `json:"name,omitempty"`
Login string `json:"login,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type UserList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []User `json:"items,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Team struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TeamSpec `json:"spec,omitempty"`
}
type TeamSpec struct {
Title string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TeamList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Team `json:"items,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ServiceAccount struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ServiceAccountSpec `json:"spec,omitempty"`
}
type ServiceAccountSpec struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ServiceAccountList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ServiceAccount `json:"items,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type IdentityDisplayList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []IdentityDisplay `json:"items,omitempty"`
}
type IdentityDisplay struct {
IdentityType string `json:"type"` // The namespaced UID, eg `user|api-key|...`
UID string `json:"uid"` // The namespaced UID, eg `xyz`
Display string `json:"display"`
AvatarURL string `json:"avatarURL,omitempty"`
// Legacy internal ID -- usage of this value should be phased out
LegacyID int64 `json:"legacyId,omitempty"`
}

View File

@ -0,0 +1,287 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by deepcopy-gen. DO NOT EDIT.
package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IdentityDisplay) DeepCopyInto(out *IdentityDisplay) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityDisplay.
func (in *IdentityDisplay) DeepCopy() *IdentityDisplay {
if in == nil {
return nil
}
out := new(IdentityDisplay)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IdentityDisplayList) DeepCopyInto(out *IdentityDisplayList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]IdentityDisplay, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityDisplayList.
func (in *IdentityDisplayList) DeepCopy() *IdentityDisplayList {
if in == nil {
return nil
}
out := new(IdentityDisplayList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *IdentityDisplayList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount.
func (in *ServiceAccount) DeepCopy() *ServiceAccount {
if in == nil {
return nil
}
out := new(ServiceAccount)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceAccount) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountList) DeepCopyInto(out *ServiceAccountList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ServiceAccount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountList.
func (in *ServiceAccountList) DeepCopy() *ServiceAccountList {
if in == nil {
return nil
}
out := new(ServiceAccountList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ServiceAccountList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountSpec) DeepCopyInto(out *ServiceAccountSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountSpec.
func (in *ServiceAccountSpec) DeepCopy() *ServiceAccountSpec {
if in == nil {
return nil
}
out := new(ServiceAccountSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Team) DeepCopyInto(out *Team) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Team.
func (in *Team) DeepCopy() *Team {
if in == nil {
return nil
}
out := new(Team)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Team) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeamList) DeepCopyInto(out *TeamList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Team, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamList.
func (in *TeamList) DeepCopy() *TeamList {
if in == nil {
return nil
}
out := new(TeamList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TeamList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeamSpec) DeepCopyInto(out *TeamSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeamSpec.
func (in *TeamSpec) DeepCopy() *TeamSpec {
if in == nil {
return nil
}
out := new(TeamSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *User) DeepCopyInto(out *User) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User.
func (in *User) DeepCopy() *User {
if in == nil {
return nil
}
out := new(User)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *User) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserList) DeepCopyInto(out *UserList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]User, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList.
func (in *UserList) DeepCopy() *UserList {
if in == nil {
return nil
}
out := new(UserList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *UserList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserSpec) DeepCopyInto(out *UserSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec.
func (in *UserSpec) DeepCopy() *UserSpec {
if in == nil {
return nil
}
out := new(UserSpec)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,19 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by defaulter-gen. DO NOT EDIT.
package v0alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -0,0 +1,490 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by openapi-gen. DO NOT EDIT.
// This file was autogenerated by openapi-gen. Do not edit it manually!
package v0alpha1
import (
common "k8s.io/kube-openapi/pkg/common"
spec "k8s.io/kube-openapi/pkg/validation/spec"
)
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplayList": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayList(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount": schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountList": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team": schema_apimachinery_apis_identity_v0alpha1_Team(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamList": schema_apimachinery_apis_identity_v0alpha1_TeamList(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec": schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User": schema_apimachinery_apis_identity_v0alpha1_User(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserList": schema_apimachinery_apis_identity_v0alpha1_UserList(ref),
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec": schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref),
}
}
func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"uid": {
SchemaProps: spec.SchemaProps{
Description: "The namespaced UID, eg `user|api-key|...`",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"display": {
SchemaProps: spec.SchemaProps{
Description: "The namespaced UID, eg `xyz`",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"avatarURL": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"legacyId": {
SchemaProps: spec.SchemaProps{
Description: "Legacy internal ID -- usage of this value should be phased out",
Type: []string{"integer"},
Format: "int64",
},
},
},
Required: []string{"type", "uid", "display"},
},
},
}
}
func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"email": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"emailVerified": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"disabled": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
}
}
func schema_apimachinery_apis_identity_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"email": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_apimachinery_apis_identity_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"login": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"email": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"emailVerified": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"disabled": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
}
}

View File

@ -0,0 +1,3 @@
API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,IdentityType
API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,LegacyID
API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,TeamSpec,Title

View File

@ -1,43 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
//
// Generated by:
// kinds/gen.go
// Using jennies:
// K8ResourcesJenny
//
// Run 'make gen-cue' from repository root to regenerate.
package team
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/kinds"
)
// Resource is the kubernetes style representation of Team. (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{
TypeMeta: v1.TypeMeta{
Kind: "Team",
APIVersion: "v0-0-alpha",
},
ObjectMeta: v1.ObjectMeta{
Name: name,
Annotations: make(map[string]string),
Labels: make(map[string]string),
},
Spec: s,
}
}
// Resource is the wire representation of Team.
// 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"`
}

View File

@ -1,42 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
//
// Generated by:
// kinds/gen.go
// Using jennies:
// K8ResourcesJenny
//
// Run 'make gen-cue' from repository root to regenerate.
package team
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]any `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"`
}

View File

@ -1,19 +0,0 @@
// 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 team
// Spec defines model for Spec.
type Spec struct {
// Email of the team.
Email *string `json:"email,omitempty"`
// Name of the team.
Name string `json:"name"`
}

View File

@ -1,74 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
//
// Generated by:
// kinds/gen.go
// Using jennies:
// K8ResourcesJenny
//
// Run 'make gen-cue' from repository root to regenerate.
package team
// 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]any `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]any `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]any `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

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/datasource"
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
"github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/registry/apis/identity"
"github.com/grafana/grafana/pkg/registry/apis/peakq" "github.com/grafana/grafana/pkg/registry/apis/peakq"
"github.com/grafana/grafana/pkg/registry/apis/playlist" "github.com/grafana/grafana/pkg/registry/apis/playlist"
"github.com/grafana/grafana/pkg/registry/apis/query" "github.com/grafana/grafana/pkg/registry/apis/query"
@ -32,6 +33,7 @@ func ProvideRegistryServiceSink(
_ *datasource.DataSourceAPIBuilder, _ *datasource.DataSourceAPIBuilder,
_ *folders.FolderAPIBuilder, _ *folders.FolderAPIBuilder,
_ *peakq.PeakQAPIBuilder, _ *peakq.PeakQAPIBuilder,
_ *identity.IdentityAPIBuilder,
_ *scope.ScopeAPIBuilder, _ *scope.ScopeAPIBuilder,
_ *query.QueryAPIBuilder, _ *query.QueryAPIBuilder,
_ *notifications.NotificationsAPIBuilder, _ *notifications.NotificationsAPIBuilder,

View File

@ -0,0 +1,130 @@
package identity
import (
"context"
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/user"
)
var (
_ rest.Scoper = (*legacyServiceAccountStorage)(nil)
_ rest.SingularNameProvider = (*legacyServiceAccountStorage)(nil)
_ rest.Getter = (*legacyServiceAccountStorage)(nil)
_ rest.Lister = (*legacyServiceAccountStorage)(nil)
_ rest.Storage = (*legacyServiceAccountStorage)(nil)
)
type legacyServiceAccountStorage struct {
service user.Service
tableConverter rest.TableConvertor
resourceInfo common.ResourceInfo
}
func (s *legacyServiceAccountStorage) New() runtime.Object {
return s.resourceInfo.NewFunc()
}
func (s *legacyServiceAccountStorage) Destroy() {}
func (s *legacyServiceAccountStorage) NamespaceScoped() bool {
return true // namespace == org
}
func (s *legacyServiceAccountStorage) GetSingularName() string {
return s.resourceInfo.GetSingularName()
}
func (s *legacyServiceAccountStorage) NewList() runtime.Object {
return s.resourceInfo.NewListFunc()
}
func (s *legacyServiceAccountStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
func (s *legacyServiceAccountStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
query := &user.ListUsersCommand{
OrgID: ns.OrgID,
Limit: options.Limit,
IsServiceAccount: true,
}
if options.Continue != "" {
query.ContinueID, err = strconv.ParseInt(options.Continue, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid continue token")
}
}
found, err := s.service.List(ctx, query)
if err != nil {
return nil, err
}
list := &identity.ServiceAccountList{}
for _, item := range found.Users {
list.Items = append(list.Items, *toSAItem(item, ns.Value))
}
if found.ContinueID > 0 {
list.ListMeta.Continue = strconv.FormatInt(found.ContinueID, 10)
}
if found.RV > 0 {
list.ListMeta.ResourceVersion = strconv.FormatInt(found.RV, 10)
}
return list, err
}
func toSAItem(u *user.User, ns string) *identity.ServiceAccount {
item := &identity.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: u.UID,
Namespace: ns,
ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()),
CreationTimestamp: metav1.NewTime(u.Created),
},
Spec: identity.ServiceAccountSpec{
Name: u.Name,
Email: u.Email,
EmailVerified: u.EmailVerified,
Disabled: u.IsDisabled,
},
}
obj, _ := utils.MetaAccessor(item)
obj.SetUpdatedTimestamp(&u.Updated)
obj.SetOriginInfo(&utils.ResourceOriginInfo{
Name: "SQL",
Path: strconv.FormatInt(u.ID, 10),
})
return item
}
func (s *legacyServiceAccountStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
found, err := s.service.GetByUID(ctx, &user.GetUserByUIDQuery{
OrgID: ns.OrgID,
UID: name,
})
if found == nil || err != nil {
return nil, s.resourceInfo.NewNotFound(name)
}
if !found.IsServiceAccount {
return nil, s.resourceInfo.NewNotFound(name) // looking up the wrong type
}
return toUserItem(found, ns.Value), nil
}

View File

@ -0,0 +1,119 @@
package identity
import (
"context"
"strconv"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/team"
)
var (
_ rest.Scoper = (*legacyTeamStorage)(nil)
_ rest.SingularNameProvider = (*legacyTeamStorage)(nil)
_ rest.Getter = (*legacyTeamStorage)(nil)
_ rest.Lister = (*legacyTeamStorage)(nil)
_ rest.Storage = (*legacyTeamStorage)(nil)
)
type legacyTeamStorage struct {
service team.Service
tableConverter rest.TableConvertor
resourceInfo common.ResourceInfo
}
func (s *legacyTeamStorage) New() runtime.Object {
return s.resourceInfo.NewFunc()
}
func (s *legacyTeamStorage) Destroy() {}
func (s *legacyTeamStorage) NamespaceScoped() bool {
return true // namespace == org
}
func (s *legacyTeamStorage) GetSingularName() string {
return s.resourceInfo.GetSingularName()
}
func (s *legacyTeamStorage) NewList() runtime.Object {
return s.resourceInfo.NewListFunc()
}
func (s *legacyTeamStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
func (s *legacyTeamStorage) doList(ctx context.Context, ns string, query *team.ListTeamsCommand) (*identity.TeamList, error) {
if query.Limit < 1 {
query.Limit = 100
}
teams, err := s.service.ListTeams(ctx, query)
if err != nil {
return nil, err
}
list := &identity.TeamList{}
for _, team := range teams {
item := identity.Team{
ObjectMeta: metav1.ObjectMeta{
Name: team.UID,
Namespace: ns,
CreationTimestamp: metav1.NewTime(team.Created),
ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10),
},
Spec: identity.TeamSpec{
Title: team.Name,
Email: team.Email,
},
}
meta, err := utils.MetaAccessor(&item)
if err != nil {
return nil, err
}
meta.SetUpdatedTimestamp(&team.Updated)
meta.SetOriginInfo(&utils.ResourceOriginInfo{
Name: "SQL",
Path: strconv.FormatInt(team.ID, 10),
})
list.Items = append(list.Items, item)
}
return list, nil
}
func (s *legacyTeamStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return s.doList(ctx, ns.Value, &team.ListTeamsCommand{
Limit: int(options.Limit),
OrgID: ns.OrgID,
})
}
func (s *legacyTeamStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
rsp, err := s.doList(ctx, ns.Value, &team.ListTeamsCommand{
Limit: 1,
OrgID: ns.OrgID,
UID: name,
})
if err != nil {
return nil, err
}
if len(rsp.Items) > 0 {
return &rsp.Items[0], nil
}
return nil, s.resourceInfo.NewNotFound(name)
}

View File

@ -0,0 +1,130 @@
package identity
import (
"context"
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/user"
)
var (
_ rest.Scoper = (*legacyUserStorage)(nil)
_ rest.SingularNameProvider = (*legacyUserStorage)(nil)
_ rest.Getter = (*legacyUserStorage)(nil)
_ rest.Lister = (*legacyUserStorage)(nil)
_ rest.Storage = (*legacyUserStorage)(nil)
)
type legacyUserStorage struct {
service user.Service
tableConverter rest.TableConvertor
resourceInfo common.ResourceInfo
}
func (s *legacyUserStorage) New() runtime.Object {
return s.resourceInfo.NewFunc()
}
func (s *legacyUserStorage) Destroy() {}
func (s *legacyUserStorage) NamespaceScoped() bool {
return true // namespace == org
}
func (s *legacyUserStorage) GetSingularName() string {
return s.resourceInfo.GetSingularName()
}
func (s *legacyUserStorage) NewList() runtime.Object {
return s.resourceInfo.NewListFunc()
}
func (s *legacyUserStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
}
func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
query := &user.ListUsersCommand{
OrgID: ns.OrgID,
Limit: options.Limit,
}
if options.Continue != "" {
query.ContinueID, err = strconv.ParseInt(options.Continue, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid continue token")
}
}
found, err := s.service.List(ctx, query)
if err != nil {
return nil, err
}
list := &identity.UserList{}
for _, item := range found.Users {
list.Items = append(list.Items, *toUserItem(item, ns.Value))
}
if found.ContinueID > 0 {
list.ListMeta.Continue = strconv.FormatInt(found.ContinueID, 10)
}
if found.RV > 0 {
list.ListMeta.ResourceVersion = strconv.FormatInt(found.RV, 10)
}
return list, err
}
func toUserItem(u *user.User, ns string) *identity.User {
item := &identity.User{
ObjectMeta: metav1.ObjectMeta{
Name: u.UID,
Namespace: ns,
ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()),
CreationTimestamp: metav1.NewTime(u.Created),
},
Spec: identity.UserSpec{
Name: u.Name,
Login: u.Login,
Email: u.Email,
EmailVerified: u.EmailVerified,
Disabled: u.IsDisabled,
},
}
obj, _ := utils.MetaAccessor(item)
obj.SetUpdatedTimestamp(&u.Updated)
obj.SetOriginInfo(&utils.ResourceOriginInfo{
Name: "SQL",
Path: strconv.FormatInt(u.ID, 10),
})
return item
}
func (s *legacyUserStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
found, err := s.service.GetByUID(ctx, &user.GetUserByUIDQuery{
OrgID: ns.OrgID,
UID: name,
})
if found == nil || err != nil {
return nil, s.resourceInfo.NewNotFound(name)
}
if found.IsServiceAccount {
return nil, s.resourceInfo.NewNotFound(name) // looking up the wrong type
}
return toUserItem(found, ns.Value), nil
}

View File

@ -0,0 +1,196 @@
package identity
import (
"context"
"fmt"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1"
identityapi "github.com/grafana/grafana/pkg/apimachinery/identity"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
)
var _ builder.APIGroupBuilder = (*IdentityAPIBuilder)(nil)
// This is used just so wire has something unique to return
type IdentityAPIBuilder struct {
svcTeam team.Service
svcUser user.Service
}
func RegisterAPIService(
features featuremgmt.FeatureToggles,
apiregistration builder.APIRegistrar,
svcTeam team.Service,
svcUser user.Service,
) *IdentityAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis
}
builder := &IdentityAPIBuilder{
svcTeam: svcTeam,
svcUser: svcUser,
}
apiregistration.RegisterAPI(builder)
return builder
}
func (b *IdentityAPIBuilder) GetGroupVersion() schema.GroupVersion {
return identity.SchemeGroupVersion
}
func (b *IdentityAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
if err := identity.AddKnownTypes(scheme, identity.VERSION); err != nil {
return err
}
// Link this version to the internal representation.
// This is used for server-side-apply (PATCH), and avoids the error:
// "no kind is registered for the type"
if err := identity.AddKnownTypes(scheme, runtime.APIVersionInternal); err != nil {
return err
}
// If multiple versions exist, then register conversions from zz_generated.conversion.go
// if err := playlist.RegisterConversions(scheme); err != nil {
// return err
// }
metav1.AddToGroupVersion(scheme, identity.SchemeGroupVersion)
return scheme.SetVersionPriority(identity.SchemeGroupVersion)
}
func (b *IdentityAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
dualWriteBuilder grafanarest.DualWriteBuilder,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identity.GROUP, scheme, metav1.ParameterCodec, codecs)
storage := map[string]rest.Storage{}
team := identity.TeamResourceInfo
teamStore := &legacyTeamStorage{
service: b.svcTeam,
resourceInfo: team,
tableConverter: gapiutil.NewTableConverter(
team.GroupResource(),
[]metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Title", Type: "string", Format: "string", Description: "The team name"},
{Name: "Email", Type: "string", Format: "string", Description: "team email"},
{Name: "Created At", Type: "date"},
},
func(obj any) ([]interface{}, error) {
m, ok := obj.(*identity.Team)
if !ok {
return nil, fmt.Errorf("expected playlist")
}
return []interface{}{
m.Name,
m.Spec.Title,
m.Spec.Email,
m.CreationTimestamp.UTC().Format(time.RFC3339),
}, nil
},
),
}
storage[team.StoragePath()] = teamStore
user := identity.UserResourceInfo
userStore := &legacyUserStorage{
service: b.svcUser,
resourceInfo: user,
tableConverter: gapiutil.NewTableConverter(
user.GroupResource(),
[]metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Login", Type: "string", Format: "string", Description: "The user login"},
{Name: "Email", Type: "string", Format: "string", Description: "The user email"},
{Name: "Created At", Type: "date"},
},
func(obj any) ([]interface{}, error) {
u, ok := obj.(*identity.User)
if ok {
return []interface{}{
u.Name,
u.Spec.Login,
u.Spec.Email,
u.CreationTimestamp.UTC().Format(time.RFC3339),
}, nil
}
return nil, fmt.Errorf("expected user")
},
),
}
storage[user.StoragePath()] = userStore
sa := identity.ServiceAccountResourceInfo
saStore := &legacyServiceAccountStorage{
service: b.svcUser,
resourceInfo: sa,
tableConverter: gapiutil.NewTableConverter(
user.GroupResource(),
[]metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Account", Type: "string", Format: "string", Description: "The service account email"},
{Name: "Email", Type: "string", Format: "string", Description: "The user email"},
{Name: "Created At", Type: "date"},
},
func(obj any) ([]interface{}, error) {
u, ok := obj.(*identity.ServiceAccount)
if ok {
return []interface{}{
u.Name,
u.Spec.Name,
u.Spec.Email,
u.CreationTimestamp.UTC().Format(time.RFC3339),
}, nil
}
return nil, fmt.Errorf("expected user")
},
),
}
storage[sa.StoragePath()] = saStore
apiGroupInfo.VersionedResourcesStorageMap[identity.VERSION] = storage
return &apiGroupInfo, nil
}
func (b *IdentityAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return identity.GetOpenAPIDefinitions
}
func (b *IdentityAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil // no custom API routes
}
func (b *IdentityAPIBuilder) GetAuthorizer() authorizer.Authorizer {
return authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
user, err := identityapi.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "no identity found", err
}
if user.GetIsGrafanaAdmin() {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "only grafana admins have access for now", nil
})
}

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/datasource"
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
"github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/registry/apis/identity"
"github.com/grafana/grafana/pkg/registry/apis/peakq" "github.com/grafana/grafana/pkg/registry/apis/peakq"
"github.com/grafana/grafana/pkg/registry/apis/playlist" "github.com/grafana/grafana/pkg/registry/apis/playlist"
"github.com/grafana/grafana/pkg/registry/apis/query" "github.com/grafana/grafana/pkg/registry/apis/query"
@ -32,6 +33,7 @@ var WireSet = wire.NewSet(
featuretoggle.RegisterAPIService, featuretoggle.RegisterAPIService,
datasource.RegisterAPIService, datasource.RegisterAPIService,
folders.RegisterAPIService, folders.RegisterAPIService,
identity.RegisterAPIService,
peakq.RegisterAPIService, peakq.RegisterAPIService,
service.RegisterAPIService, service.RegisterAPIService,
query.RegisterAPIService, query.RegisterAPIService,

View File

@ -93,15 +93,6 @@ func GetCoreKinds() ([]CoreKind, error) {
CueFile: rolebindingCue, CueFile: rolebindingCue,
}) })
teamCue, err := loadCueFile(ctx, filepath.Join(root, "./kinds/team/team_kind.cue"))
if err != nil {
return nil, err
}
kinds = append(kinds, CoreKind{
Name: "team",
CueFile: teamCue,
})
return kinds, nil return kinds, nil
} }

View File

@ -90,6 +90,13 @@ type SearchTeamsQuery struct {
HiddenUsers map[string]struct{} HiddenUsers map[string]struct{}
} }
type ListTeamsCommand struct {
Limit int
Start int
OrgID int64
UID string
}
type TeamDTO struct { type TeamDTO struct {
ID int64 `json:"id" xorm:"id"` ID int64 `json:"id" xorm:"id"`
UID string `json:"uid" xorm:"uid"` UID string `json:"uid" xorm:"uid"`

View File

@ -8,6 +8,7 @@ type Service interface {
CreateTeam(ctx context.Context, name, email string, orgID int64) (Team, error) CreateTeam(ctx context.Context, name, email string, orgID int64) (Team, error)
UpdateTeam(ctx context.Context, cmd *UpdateTeamCommand) error UpdateTeam(ctx context.Context, cmd *UpdateTeamCommand) error
DeleteTeam(ctx context.Context, cmd *DeleteTeamCommand) error DeleteTeam(ctx context.Context, cmd *DeleteTeamCommand) error
ListTeams(ctx context.Context, query *ListTeamsCommand) ([]*Team, error)
SearchTeams(ctx context.Context, query *SearchTeamsQuery) (SearchTeamQueryResult, error) SearchTeams(ctx context.Context, query *SearchTeamsQuery) (SearchTeamQueryResult, error)
GetTeamByID(ctx context.Context, query *GetTeamByIDQuery) (*TeamDTO, error) GetTeamByID(ctx context.Context, query *GetTeamByIDQuery) (*TeamDTO, error)
GetTeamsByUser(ctx context.Context, query *GetTeamsByUserQuery) ([]*TeamDTO, error) GetTeamsByUser(ctx context.Context, query *GetTeamsByUserQuery) ([]*TeamDTO, error)

View File

@ -20,6 +20,7 @@ type store interface {
Create(name, email string, orgID int64) (team.Team, error) Create(name, email string, orgID int64) (team.Team, error)
Update(ctx context.Context, cmd *team.UpdateTeamCommand) error Update(ctx context.Context, cmd *team.UpdateTeamCommand) error
Delete(ctx context.Context, cmd *team.DeleteTeamCommand) error Delete(ctx context.Context, cmd *team.DeleteTeamCommand) error
ListTeams(ctx context.Context, query *team.ListTeamsCommand) ([]*team.Team, error)
Search(ctx context.Context, query *team.SearchTeamsQuery) (team.SearchTeamQueryResult, error) Search(ctx context.Context, query *team.SearchTeamsQuery) (team.SearchTeamQueryResult, error)
GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error)
GetByUser(ctx context.Context, query *team.GetTeamsByUserQuery) ([]*team.TeamDTO, error) GetByUser(ctx context.Context, query *team.GetTeamsByUserQuery) ([]*team.TeamDTO, error)
@ -267,6 +268,21 @@ func (ss *xormStore) Search(ctx context.Context, query *team.SearchTeamsQuery) (
return queryResult, nil return queryResult, nil
} }
func (ss *xormStore) ListTeams(ctx context.Context, query *team.ListTeamsCommand) ([]*team.Team, error) {
results := make([]*team.Team, 0)
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
q := sess.Table("team")
q.Where("team.org_id=?", query.OrgID)
if query.UID != "" {
q.Where("team.uid=?", query.UID)
}
q.Limit(query.Limit, query.Start)
return q.Find(&results)
})
return results, err
}
func (ss *xormStore) GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) { func (ss *xormStore) GetByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) {
var queryResult *team.TeamDTO var queryResult *team.TeamDTO
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {

View File

@ -51,6 +51,14 @@ func (s *Service) DeleteTeam(ctx context.Context, cmd *team.DeleteTeamCommand) e
return s.store.Delete(ctx, cmd) return s.store.Delete(ctx, cmd)
} }
func (s *Service) ListTeams(ctx context.Context, query *team.ListTeamsCommand) ([]*team.Team, error) {
ctx, span := s.tracer.Start(ctx, "team.ListTeams", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID),
))
defer span.End()
return s.store.ListTeams(ctx, query)
}
func (s *Service) SearchTeams(ctx context.Context, query *team.SearchTeamsQuery) (team.SearchTeamQueryResult, error) { func (s *Service) SearchTeams(ctx context.Context, query *team.SearchTeamsQuery) (team.SearchTeamQueryResult, error) {
ctx, span := s.tracer.Start(ctx, "team.SearchTeams", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "team.SearchTeams", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID), attribute.Int64("orgID", query.OrgID),

View File

@ -36,6 +36,10 @@ func (s *FakeService) SearchTeams(ctx context.Context, query *team.SearchTeamsQu
return team.SearchTeamQueryResult{}, s.ExpectedError return team.SearchTeamQueryResult{}, s.ExpectedError
} }
func (s *FakeService) ListTeams(ctx context.Context, query *team.ListTeamsCommand) ([]*team.Team, error) {
return nil, s.ExpectedError
}
func (s *FakeService) GetTeamByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) { func (s *FakeService) GetTeamByID(ctx context.Context, query *team.GetTeamByIDQuery) (*team.TeamDTO, error) {
return s.ExpectedTeamDTO, s.ExpectedError return s.ExpectedTeamDTO, s.ExpectedError
} }

View File

@ -98,6 +98,19 @@ type UpdateUserLastSeenAtCommand struct {
OrgID int64 OrgID int64
} }
type ListUsersCommand struct {
OrgID int64
Limit int64
ContinueID int64
IsServiceAccount bool
}
type ListUserResult struct {
Users []*User
ContinueID int64
RV int64
}
type SearchUsersQuery struct { type SearchUsersQuery struct {
SignedInUser identity.Requester SignedInUser identity.Requester
OrgID int64 `xorm:"org_id"` OrgID int64 `xorm:"org_id"`
@ -120,7 +133,7 @@ type SearchUserQueryResult struct {
type UserSearchHitDTO struct { type UserSearchHitDTO struct {
ID int64 `json:"id" xorm:"id"` ID int64 `json:"id" xorm:"id"`
UID string `json:"uid" xorm:"id"` UID string `json:"uid" xorm:"uid"`
Name string `json:"name"` Name string `json:"name"`
Login string `json:"login"` Login string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
@ -206,6 +219,11 @@ type GetUserByIDQuery struct {
ID int64 ID int64
} }
type GetUserByUIDQuery struct {
OrgID int64
UID string
}
type StartVerifyEmailCommand struct { type StartVerifyEmailCommand struct {
User User User User
Email string Email string

View File

@ -13,8 +13,10 @@ type Service interface {
CreateServiceAccount(context.Context, *CreateUserCommand) (*User, error) CreateServiceAccount(context.Context, *CreateUserCommand) (*User, error)
Delete(context.Context, *DeleteUserCommand) error Delete(context.Context, *DeleteUserCommand) error
GetByID(context.Context, *GetUserByIDQuery) (*User, error) GetByID(context.Context, *GetUserByIDQuery) (*User, error)
GetByUID(context.Context, *GetUserByUIDQuery) (*User, error)
GetByLogin(context.Context, *GetUserByLoginQuery) (*User, error) GetByLogin(context.Context, *GetUserByLoginQuery) (*User, error)
GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error) GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error)
List(context.Context, *ListUsersCommand) (*ListUserResult, error)
Update(context.Context, *UpdateUserCommand) error Update(context.Context, *UpdateUserCommand) error
UpdateLastSeenAt(context.Context, *UpdateUserLastSeenAtCommand) error UpdateLastSeenAt(context.Context, *UpdateUserLastSeenAtCommand) error
GetSignedInUser(context.Context, *GetSignedInUserQuery) (*SignedInUser, error) GetSignedInUser(context.Context, *GetSignedInUserQuery) (*SignedInUser, error)

View File

@ -20,8 +20,10 @@ import (
type store interface { type store interface {
Insert(context.Context, *user.User) (int64, error) Insert(context.Context, *user.User) (int64, error)
GetByID(context.Context, int64) (*user.User, error) GetByID(context.Context, int64) (*user.User, error)
GetByUID(ctx context.Context, orgId int64, uid string) (*user.User, error)
GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error) GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error)
GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error) GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error)
List(context.Context, *user.ListUsersCommand) (*user.ListUserResult, error)
Delete(context.Context, int64) error Delete(context.Context, int64) error
LoginConflict(ctx context.Context, login, email string) error LoginConflict(ctx context.Context, login, email string) error
Update(context.Context, *user.UpdateUserCommand) error Update(context.Context, *user.UpdateUserCommand) error
@ -107,6 +109,24 @@ func (ss *sqlStore) GetByID(ctx context.Context, userID int64) (*user.User, erro
return &usr, err return &usr, err
} }
func (ss *sqlStore) GetByUID(ctx context.Context, orgId int64, uid string) (*user.User, error) {
var usr user.User
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
has, err := sess.Table("user").
Where("org_id = ? AND uid = ?", orgId, uid).
Get(&usr)
if err != nil {
return err
} else if !has {
return user.ErrUserNotFound
}
return nil
})
return &usr, err
}
func (ss *sqlStore) notServiceAccountFilter() string { func (ss *sqlStore) notServiceAccountFilter() string {
return fmt.Sprintf("%s.is_service_account = %s", return fmt.Sprintf("%s.is_service_account = %s",
ss.dialect.Quote("user"), ss.dialect.Quote("user"),
@ -506,7 +526,7 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*
sess.Limit(query.Limit, offset) sess.Limit(query.Limit, offset)
} }
sess.Cols("u.id", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module") sess.Cols("u.id", "u.uid", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module")
if len(query.SortOpts) > 0 { if len(query.SortOpts) > 0 {
for i := range query.SortOpts { for i := range query.SortOpts {
@ -559,6 +579,40 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*
return &result, err return &result, err
} }
func (ss *sqlStore) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
limit := int(query.Limit)
if limit <= 0 {
limit = 25
}
result := &user.ListUserResult{
Users: make([]*user.User, 0),
}
max := ""
err := ss.db.WithDbSession(ctx, func(dbSess *db.Session) error {
sess := dbSess.Table("user")
sess.Where("id >= ? AND is_service_account = ?", query.ContinueID, query.IsServiceAccount)
err := sess.OrderBy("id asc").Limit(limit + 1).Find(&result.Users)
if err != nil {
return err
}
// Set the revision version
_, err = dbSess.Table("user").Select("MAX(updated)").Get(&max)
return err
})
if max != "" {
t, err := time.Parse(time.DateTime, max)
if err == nil {
result.RV = t.UnixMilli()
}
}
if len(result.Users) > limit {
result.ContinueID = result.Users[limit].ID
result.Users = result.Users[:limit]
}
return result, err
}
func setOptional[T any](v *T, add func(v T)) { func setOptional[T any](v *T, add func(v T)) {
if v != nil { if v != nil {
add(*v) add(*v)

View File

@ -212,6 +212,16 @@ func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*u
return s.store.GetByID(ctx, query.ID) return s.store.GetByID(ctx, query.ID)
} }
func (s *Service) GetByUID(ctx context.Context, query *user.GetUserByUIDQuery) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.GetByUID", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID),
attribute.String("userUID", query.UID),
))
defer span.End()
return s.store.GetByUID(ctx, query.OrgID, query.UID)
}
func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.GetByLogin") ctx, span := s.tracer.Start(ctx, "user.GetByLogin")
defer span.End() defer span.End()
@ -368,6 +378,15 @@ func (s *Service) getSignedInUser(ctx context.Context, query *user.GetSignedInUs
return usr, err return usr, err
} }
func (s *Service) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
ctx, span := s.tracer.Start(ctx, "user.List", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID),
))
defer span.End()
return s.store.List(ctx, query)
}
func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID), attribute.Int64("orgID", query.OrgID),

View File

@ -291,6 +291,10 @@ func (f *FakeUserStore) GetByID(context.Context, int64) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError return f.ExpectedUser, f.ExpectedError
} }
func (f *FakeUserStore) GetByUID(context.Context, int64, string) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeUserStore) LoginConflict(context.Context, string, string) error { func (f *FakeUserStore) LoginConflict(context.Context, string, string) error {
return f.ExpectedError return f.ExpectedError
} }
@ -327,6 +331,10 @@ func (f *FakeUserStore) Search(ctx context.Context, query *user.SearchUsersQuery
return f.ExpectedSearchUserQueryResult, f.ExpectedError return f.ExpectedSearchUserQueryResult, f.ExpectedError
} }
func (f *FakeUserStore) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
return nil, f.ExpectedError
}
func (f *FakeUserStore) Count(ctx context.Context) (int64, error) { func (f *FakeUserStore) Count(ctx context.Context) (int64, error) {
return 0, nil return 0, nil
} }

View File

@ -12,6 +12,7 @@ type FakeUserService struct {
ExpectedError error ExpectedError error
ExpectedSetUsingOrgError error ExpectedSetUsingOrgError error
ExpectedSearchUsers user.SearchUserQueryResult ExpectedSearchUsers user.SearchUserQueryResult
ExpectedListUsers user.ListUserResult
ExpectedUserProfileDTO *user.UserProfileDTO ExpectedUserProfileDTO *user.UserProfileDTO
ExpectedUserProfileDTOs []*user.UserProfileDTO ExpectedUserProfileDTOs []*user.UserProfileDTO
ExpectedUsageStats map[string]any ExpectedUsageStats map[string]any
@ -53,6 +54,10 @@ func (f *FakeUserService) GetByID(ctx context.Context, query *user.GetUserByIDQu
return f.ExpectedUser, f.ExpectedError return f.ExpectedUser, f.ExpectedError
} }
func (f *FakeUserService) GetByUID(ctx context.Context, query *user.GetUserByUIDQuery) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeUserService) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { func (f *FakeUserService) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError return f.ExpectedUser, f.ExpectedError
} }
@ -93,6 +98,10 @@ func (f *FakeUserService) Search(ctx context.Context, query *user.SearchUsersQue
return &f.ExpectedSearchUsers, f.ExpectedError return &f.ExpectedSearchUsers, f.ExpectedError
} }
func (f *FakeUserService) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
return &f.ExpectedListUsers, f.ExpectedError
}
func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error {
if f.BatchDisableUsersFn != nil { if f.BatchDisableUsersFn != nil {
return f.BatchDisableUsersFn(ctx, cmd) return f.BatchDisableUsersFn(ctx, cmd)

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.2. DO NOT EDIT. // Code generated by mockery v2.43.2. DO NOT EDIT.
package usertest package usertest
@ -200,6 +200,36 @@ func (_m *MockService) GetByLogin(_a0 context.Context, _a1 *user.GetUserByLoginQ
return r0, r1 return r0, r1
} }
// GetByUID provides a mock function with given fields: _a0, _a1
func (_m *MockService) GetByUID(_a0 context.Context, _a1 *user.GetUserByUIDQuery) (*user.User, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for GetByUID")
}
var r0 *user.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByUIDQuery) (*user.User, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByUIDQuery) *user.User); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*user.User)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserByUIDQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProfile provides a mock function with given fields: _a0, _a1 // GetProfile provides a mock function with given fields: _a0, _a1
func (_m *MockService) GetProfile(_a0 context.Context, _a1 *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { func (_m *MockService) GetProfile(_a0 context.Context, _a1 *user.GetUserProfileQuery) (*user.UserProfileDTO, error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)
@ -260,36 +290,6 @@ func (_m *MockService) GetSignedInUser(_a0 context.Context, _a1 *user.GetSignedI
return r0, r1 return r0, r1
} }
// GetSignedInUserWithCacheCtx provides a mock function with given fields: _a0, _a1
func (_m *MockService) GetSignedInUserWithCacheCtx(_a0 context.Context, _a1 *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for GetSignedInUserWithCacheCtx")
}
var r0 *user.SignedInUser
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) *user.SignedInUser); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*user.SignedInUser)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *user.GetSignedInUserQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetUsageStats provides a mock function with given fields: ctx // GetUsageStats provides a mock function with given fields: ctx
func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{} { func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{} {
ret := _m.Called(ctx) ret := _m.Called(ctx)
@ -310,6 +310,36 @@ func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{}
return r0 return r0
} }
// List provides a mock function with given fields: _a0, _a1
func (_m *MockService) List(_a0 context.Context, _a1 *user.ListUsersCommand) (*user.ListUserResult, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for List")
}
var r0 *user.ListUserResult
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *user.ListUsersCommand) (*user.ListUserResult, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *user.ListUsersCommand) *user.ListUserResult); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*user.ListUserResult)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *user.ListUsersCommand) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Search provides a mock function with given fields: _a0, _a1 // Search provides a mock function with given fields: _a0, _a1
func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
ret := _m.Called(_a0, _a1) ret := _m.Called(_a0, _a1)

View File

@ -1,4 +1,4 @@
package playlist package peakq
import ( import (
"encoding/json" "encoding/json"
@ -16,7 +16,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m) testsuite.Run(m)
} }
func TestIntegrationFoldersApp(t *testing.T) { func TestIntegrationPeakQ(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }

View File

@ -1,10 +1,16 @@
import { Team as TeamDTO } from '@grafana/schema/src/raw/team/x/team_types.gen';
import { Role } from './accessControl'; import { Role } from './accessControl';
import { TeamPermissionLevel } from './acl'; import { TeamPermissionLevel } from './acl';
// The team resource export interface TeamDTO {
export { TeamDTO }; /**
* Email of the team.
*/
email?: string;
/**
* Name of the team.
*/
name: string;
}
// This is the team resource with permissions and metadata expanded // This is the team resource with permissions and metadata expanded
export interface Team { export interface Team {