diff --git a/apps/advisor/kinds/check.cue b/apps/advisor/kinds/check.cue
index fdf641abdf3..a564f888e2f 100644
--- a/apps/advisor/kinds/check.cue
+++ b/apps/advisor/kinds/check.cue
@@ -28,6 +28,10 @@ check: {
reason: string
// Action to take to resolve the error
action: string
+ // Step ID that the error is associated with
+ stepID: string
+ // Item ID that the error is associated with
+ itemID: string
}
#Report: {
// Number of elements analyzed
diff --git a/apps/advisor/kinds/checktype.cue b/apps/advisor/kinds/checktype.cue
new file mode 100644
index 00000000000..6a4bfa4a0df
--- /dev/null
+++ b/apps/advisor/kinds/checktype.cue
@@ -0,0 +1,26 @@
+package advisor
+
+checktype: {
+ kind: "CheckType"
+ pluralName: "CheckTypes"
+ current: "v0alpha1"
+ versions: {
+ "v0alpha1": {
+ codegen: {
+ frontend: false
+ backend: true
+ }
+ schema: {
+ #Step: {
+ title: string
+ description: string
+ stepID: string
+ }
+ spec: {
+ name: string
+ steps: [...#Step]
+ }
+ }
+ }
+ }
+}
diff --git a/apps/advisor/kinds/manifest.cue b/apps/advisor/kinds/manifest.cue
index 6f96066967d..75a0eee05ef 100644
--- a/apps/advisor/kinds/manifest.cue
+++ b/apps/advisor/kinds/manifest.cue
@@ -5,5 +5,6 @@ manifest: {
groupOverride: "advisor.grafana.app"
kinds: [
check,
+ checktype,
]
}
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go
index ac73ee86ec4..edc67575fe8 100644
--- a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go
@@ -10,6 +10,10 @@ type CheckReportError struct {
Reason string `json:"reason"`
// Action to take to resolve the error
Action string `json:"action"`
+ // Step ID that the error is associated with
+ StepID string `json:"stepID"`
+ // Item ID that the error is associated with
+ ItemID string `json:"itemID"`
}
// NewCheckReportError creates a new CheckReportError object.
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go
new file mode 100644
index 00000000000..9881ce9d96f
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go
@@ -0,0 +1,28 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "encoding/json"
+ "io"
+
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+// CheckTypeJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
+type CheckTypeJSONCodec struct{}
+
+// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
+func (*CheckTypeJSONCodec) Read(reader io.Reader, into resource.Object) error {
+ return json.NewDecoder(reader).Decode(into)
+}
+
+// Write writes JSON-encoded bytes into `writer` marshaled from `from`
+func (*CheckTypeJSONCodec) Write(writer io.Writer, from resource.Object) error {
+ return json.NewEncoder(writer).Encode(from)
+}
+
+// Interface compliance checks
+var _ resource.Codec = &CheckTypeJSONCodec{}
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go
new file mode 100644
index 00000000000..9998cffb37d
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go
@@ -0,0 +1,28 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+import (
+ time "time"
+)
+
+// 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.
+type CheckTypeMetadata struct {
+ UpdateTimestamp time.Time `json:"updateTimestamp"`
+ CreatedBy string `json:"createdBy"`
+ Uid string `json:"uid"`
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+ Finalizers []string `json:"finalizers"`
+ ResourceVersion string `json:"resourceVersion"`
+ Generation int64 `json:"generation"`
+ UpdatedBy string `json:"updatedBy"`
+ Labels map[string]string `json:"labels"`
+}
+
+// NewCheckTypeMetadata creates a new CheckTypeMetadata object.
+func NewCheckTypeMetadata() *CheckTypeMetadata {
+ return &CheckTypeMetadata{}
+}
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go
new file mode 100644
index 00000000000..80bd02b1cc8
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go
@@ -0,0 +1,266 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "fmt"
+ "github.com/grafana/grafana-app-sdk/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/types"
+ "time"
+)
+
+// +k8s:openapi-gen=true
+type CheckType struct {
+ metav1.TypeMeta `json:",inline" yaml:",inline"`
+ metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
+ Spec CheckTypeSpec `json:"spec" yaml:"spec"`
+ CheckTypeStatus CheckTypeStatus `json:"status" yaml:"status"`
+}
+
+func (o *CheckType) GetSpec() any {
+ return o.Spec
+}
+
+func (o *CheckType) SetSpec(spec any) error {
+ cast, ok := spec.(CheckTypeSpec)
+ if !ok {
+ return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
+ }
+ o.Spec = cast
+ return nil
+}
+
+func (o *CheckType) GetSubresources() map[string]any {
+ return map[string]any{
+ "status": o.CheckTypeStatus,
+ }
+}
+
+func (o *CheckType) GetSubresource(name string) (any, bool) {
+ switch name {
+ case "status":
+ return o.CheckTypeStatus, true
+ default:
+ return nil, false
+ }
+}
+
+func (o *CheckType) SetSubresource(name string, value any) error {
+ switch name {
+ case "status":
+ cast, ok := value.(CheckTypeStatus)
+ if !ok {
+ return fmt.Errorf("cannot set status type %#v, not of type CheckTypeStatus", value)
+ }
+ o.CheckTypeStatus = cast
+ return nil
+ default:
+ return fmt.Errorf("subresource '%s' does not exist", name)
+ }
+}
+
+func (o *CheckType) GetStaticMetadata() resource.StaticMetadata {
+ gvk := o.GroupVersionKind()
+ return resource.StaticMetadata{
+ Name: o.ObjectMeta.Name,
+ Namespace: o.ObjectMeta.Namespace,
+ Group: gvk.Group,
+ Version: gvk.Version,
+ Kind: gvk.Kind,
+ }
+}
+
+func (o *CheckType) SetStaticMetadata(metadata resource.StaticMetadata) {
+ o.Name = metadata.Name
+ o.Namespace = metadata.Namespace
+ o.SetGroupVersionKind(schema.GroupVersionKind{
+ Group: metadata.Group,
+ Version: metadata.Version,
+ Kind: metadata.Kind,
+ })
+}
+
+func (o *CheckType) GetCommonMetadata() resource.CommonMetadata {
+ dt := o.DeletionTimestamp
+ var deletionTimestamp *time.Time
+ if dt != nil {
+ deletionTimestamp = &dt.Time
+ }
+ // Legacy ExtraFields support
+ extraFields := make(map[string]any)
+ if o.Annotations != nil {
+ extraFields["annotations"] = o.Annotations
+ }
+ if o.ManagedFields != nil {
+ extraFields["managedFields"] = o.ManagedFields
+ }
+ if o.OwnerReferences != nil {
+ extraFields["ownerReferences"] = o.OwnerReferences
+ }
+ return resource.CommonMetadata{
+ UID: string(o.UID),
+ ResourceVersion: o.ResourceVersion,
+ Generation: o.Generation,
+ Labels: o.Labels,
+ CreationTimestamp: o.CreationTimestamp.Time,
+ DeletionTimestamp: deletionTimestamp,
+ Finalizers: o.Finalizers,
+ UpdateTimestamp: o.GetUpdateTimestamp(),
+ CreatedBy: o.GetCreatedBy(),
+ UpdatedBy: o.GetUpdatedBy(),
+ ExtraFields: extraFields,
+ }
+}
+
+func (o *CheckType) SetCommonMetadata(metadata resource.CommonMetadata) {
+ o.UID = types.UID(metadata.UID)
+ o.ResourceVersion = metadata.ResourceVersion
+ o.Generation = metadata.Generation
+ o.Labels = metadata.Labels
+ o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
+ if metadata.DeletionTimestamp != nil {
+ dt := metav1.NewTime(*metadata.DeletionTimestamp)
+ o.DeletionTimestamp = &dt
+ } else {
+ o.DeletionTimestamp = nil
+ }
+ o.Finalizers = metadata.Finalizers
+ if o.Annotations == nil {
+ o.Annotations = make(map[string]string)
+ }
+ if !metadata.UpdateTimestamp.IsZero() {
+ o.SetUpdateTimestamp(metadata.UpdateTimestamp)
+ }
+ if metadata.CreatedBy != "" {
+ o.SetCreatedBy(metadata.CreatedBy)
+ }
+ if metadata.UpdatedBy != "" {
+ o.SetUpdatedBy(metadata.UpdatedBy)
+ }
+ // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
+ if metadata.ExtraFields != nil {
+ if annotations, ok := metadata.ExtraFields["annotations"]; ok {
+ if cast, ok := annotations.(map[string]string); ok {
+ o.Annotations = cast
+ }
+ }
+ if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
+ if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
+ o.ManagedFields = cast
+ }
+ }
+ if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
+ if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
+ o.OwnerReferences = cast
+ }
+ }
+ }
+}
+
+func (o *CheckType) GetCreatedBy() string {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ return o.ObjectMeta.Annotations["grafana.com/createdBy"]
+}
+
+func (o *CheckType) SetCreatedBy(createdBy string) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
+}
+
+func (o *CheckType) GetUpdateTimestamp() time.Time {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
+ return parsed
+}
+
+func (o *CheckType) SetUpdateTimestamp(updateTimestamp time.Time) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
+}
+
+func (o *CheckType) GetUpdatedBy() string {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
+}
+
+func (o *CheckType) SetUpdatedBy(updatedBy string) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
+}
+
+func (o *CheckType) Copy() resource.Object {
+ return resource.CopyObject(o)
+}
+
+func (o *CheckType) DeepCopyObject() runtime.Object {
+ return o.Copy()
+}
+
+// Interface compliance compile-time check
+var _ resource.Object = &CheckType{}
+
+// +k8s:openapi-gen=true
+type CheckTypeList struct {
+ metav1.TypeMeta `json:",inline" yaml:",inline"`
+ metav1.ListMeta `json:"metadata" yaml:"metadata"`
+ Items []CheckType `json:"items" yaml:"items"`
+}
+
+func (o *CheckTypeList) DeepCopyObject() runtime.Object {
+ return o.Copy()
+}
+
+func (o *CheckTypeList) Copy() resource.ListObject {
+ cpy := &CheckTypeList{
+ TypeMeta: o.TypeMeta,
+ Items: make([]CheckType, len(o.Items)),
+ }
+ o.ListMeta.DeepCopyInto(&cpy.ListMeta)
+ for i := 0; i < len(o.Items); i++ {
+ if item, ok := o.Items[i].Copy().(*CheckType); ok {
+ cpy.Items[i] = *item
+ }
+ }
+ return cpy
+}
+
+func (o *CheckTypeList) GetItems() []resource.Object {
+ items := make([]resource.Object, len(o.Items))
+ for i := 0; i < len(o.Items); i++ {
+ items[i] = &o.Items[i]
+ }
+ return items
+}
+
+func (o *CheckTypeList) SetItems(items []resource.Object) {
+ o.Items = make([]CheckType, len(items))
+ for i := 0; i < len(items); i++ {
+ o.Items[i] = *items[i].(*CheckType)
+ }
+}
+
+// Interface compliance compile-time check
+var _ resource.ListObject = &CheckTypeList{}
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go
new file mode 100644
index 00000000000..15b4a6fbb11
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go
@@ -0,0 +1,34 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+// schema is unexported to prevent accidental overwrites
+var (
+ schemaCheckType = resource.NewSimpleSchema("advisor.grafana.app", "v0alpha1", &CheckType{}, &CheckTypeList{}, resource.WithKind("CheckType"),
+ resource.WithPlural("checktypes"), resource.WithScope(resource.NamespacedScope))
+ kindCheckType = resource.Kind{
+ Schema: schemaCheckType,
+ Codecs: map[resource.KindEncoding]resource.Codec{
+ resource.KindEncodingJSON: &CheckTypeJSONCodec{},
+ },
+ }
+)
+
+// Kind returns a resource.Kind for this Schema with a JSON codec
+func CheckTypeKind() resource.Kind {
+ return kindCheckType
+}
+
+// Schema returns a resource.SimpleSchema representation of CheckType
+func CheckTypeSchema() *resource.SimpleSchema {
+ return schemaCheckType
+}
+
+// Interface compliance checks
+var _ resource.Schema = kindCheckType
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go
new file mode 100644
index 00000000000..a0392a49c18
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go
@@ -0,0 +1,26 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+// +k8s:openapi-gen=true
+type CheckTypeStep struct {
+ Title string `json:"title"`
+ Description string `json:"description"`
+ StepID string `json:"stepID"`
+}
+
+// NewCheckTypeStep creates a new CheckTypeStep object.
+func NewCheckTypeStep() *CheckTypeStep {
+ return &CheckTypeStep{}
+}
+
+// +k8s:openapi-gen=true
+type CheckTypeSpec struct {
+ Name string `json:"name"`
+ Steps []CheckTypeStep `json:"steps"`
+}
+
+// NewCheckTypeSpec creates a new CheckTypeSpec object.
+func NewCheckTypeSpec() *CheckTypeSpec {
+ return &CheckTypeSpec{}
+}
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go
new file mode 100644
index 00000000000..87db65316d0
--- /dev/null
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go
@@ -0,0 +1,44 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+// +k8s:openapi-gen=true
+type CheckTypestatusOperatorState struct {
+ // 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 CheckTypeStatusOperatorStateState `json:"state"`
+ // 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"`
+}
+
+// NewCheckTypestatusOperatorState creates a new CheckTypestatusOperatorState object.
+func NewCheckTypestatusOperatorState() *CheckTypestatusOperatorState {
+ return &CheckTypestatusOperatorState{}
+}
+
+// +k8s:openapi-gen=true
+type CheckTypeStatus struct {
+ // 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]CheckTypestatusOperatorState `json:"operatorStates,omitempty"`
+ // additionalFields is reserved for future use
+ AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
+}
+
+// NewCheckTypeStatus creates a new CheckTypeStatus object.
+func NewCheckTypeStatus() *CheckTypeStatus {
+ return &CheckTypeStatus{}
+}
+
+// +k8s:openapi-gen=true
+type CheckTypeStatusOperatorStateState string
+
+const (
+ CheckTypeStatusOperatorStateStateSuccess CheckTypeStatusOperatorStateState = "success"
+ CheckTypeStatusOperatorStateStateInProgress CheckTypeStatusOperatorStateState = "in_progress"
+ CheckTypeStatusOperatorStateStateFailed CheckTypeStatusOperatorStateState = "failed"
+)
diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go
index 74ccb4861e6..10a2de58e84 100644
--- a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go
+++ b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go
@@ -12,13 +12,19 @@ import (
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportError": schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport": schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref),
- "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportError": schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType": schema_pkg_apis_advisor_v0alpha1_CheckType(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeList": schema_pkg_apis_advisor_v0alpha1_CheckTypeList(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec": schema_pkg_apis_advisor_v0alpha1_CheckTypeSpec(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus": schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep": schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport": schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref),
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref),
}
}
@@ -147,8 +153,24 @@ func schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref common.ReferenceCallb
Format: "",
},
},
+ "stepID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Step ID that the error is associated with",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "itemID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Item ID that the error is associated with",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
- Required: []string{"severity", "reason", "action"},
+ Required: []string{"severity", "reason", "action", "stepID", "itemID"},
},
},
}
@@ -233,6 +255,265 @@ func schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref common.ReferenceCallback)
}
}
+func schema_pkg_apis_advisor_v0alpha1_CheckType(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/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec"),
+ },
+ },
+ "status": {
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus"),
+ },
+ },
+ },
+ Required: []string{"metadata", "spec", "status"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec", "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ }
+}
+
+func schema_pkg_apis_advisor_v0alpha1_CheckTypeList(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/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"metadata", "items"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ }
+}
+
+func schema_pkg_apis_advisor_v0alpha1_CheckTypeSpec(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{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "steps": {
+ 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/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"name", "steps"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep"},
+ }
+}
+
+func schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "operatorStates": {
+ SchemaProps: spec.SchemaProps{
+ Description: "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.",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState"),
+ },
+ },
+ },
+ },
+ },
+ "additionalFields": {
+ SchemaProps: spec.SchemaProps{
+ Description: "additionalFields is reserved for future use",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState"},
+ }
+}
+
+func schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "title": {
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "stepID": {
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"title", "description", "stepID"},
+ },
+ },
+ }
+}
+
+func schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "lastEvaluation": {
+ SchemaProps: spec.SchemaProps{
+ Description: "lastEvaluation is the ResourceVersion last evaluated",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "state": {
+ SchemaProps: spec.SchemaProps{
+ Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "descriptiveState": {
+ SchemaProps: spec.SchemaProps{
+ Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "details": {
+ SchemaProps: spec.SchemaProps{
+ Description: "details contains any extra information that is operator-specific",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"lastEvaluation", "state"},
+ },
+ },
+ }
+}
+
func schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
diff --git a/apps/advisor/pkg/apis/advisor_manifest.go b/apps/advisor/pkg/apis/advisor_manifest.go
index f62ea3addb8..8b39d626436 100644
--- a/apps/advisor/pkg/apis/advisor_manifest.go
+++ b/apps/advisor/pkg/apis/advisor_manifest.go
@@ -12,9 +12,12 @@ import (
)
var (
- rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"errors":{"description":"List of errors","items":{"properties":{"action":{"description":"Action to take to resolve the error","type":"string"},"reason":{"description":"Human readable reason for the error","type":"string"},"severity":{"description":"Severity of the error","enum":["high","low"],"type":"string"}},"required":["severity","reason","action"],"type":"object"},"type":"array"}},"required":["count","errors"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
- versionSchemaCheckv0alpha1 app.VersionSchema
- _ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1)
+ rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"errors":{"description":"List of errors","items":{"properties":{"action":{"description":"Action to take to resolve the error","type":"string"},"itemID":{"description":"Item ID that the error is associated with","type":"string"},"reason":{"description":"Human readable reason for the error","type":"string"},"severity":{"description":"Severity of the error","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the error is associated with","type":"string"}},"required":["severity","reason","action","stepID","itemID"],"type":"object"},"type":"array"}},"required":["count","errors"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
+ versionSchemaCheckv0alpha1 app.VersionSchema
+ _ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1)
+ rawSchemaCheckTypev0alpha1 = []byte(`{"spec":{"properties":{"name":{"type":"string"},"steps":{"items":{"properties":{"description":{"type":"string"},"stepID":{"type":"string"},"title":{"type":"string"}},"required":["title","description","stepID"],"type":"object"},"type":"array"}},"required":["name","steps"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
+ versionSchemaCheckTypev0alpha1 app.VersionSchema
+ _ = json.Unmarshal(rawSchemaCheckTypev0alpha1, &versionSchemaCheckTypev0alpha1)
)
var appManifestData = app.ManifestData{
@@ -40,6 +43,18 @@ var appManifestData = app.ManifestData{
},
},
},
+
+ {
+ Kind: "CheckType",
+ Scope: "Namespaced",
+ Conversion: false,
+ Versions: []app.ManifestKindVersion{
+ {
+ Name: "v0alpha1",
+ Schema: &versionSchemaCheckTypev0alpha1,
+ },
+ },
+ },
},
}
diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go
index 554357caca2..7982661c3b9 100644
--- a/apps/advisor/pkg/app/app.go
+++ b/apps/advisor/pkg/app/app.go
@@ -11,6 +11,7 @@ import (
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
+ "github.com/grafana/grafana/apps/advisor/pkg/app/checktyperegisterer"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
)
@@ -70,6 +71,9 @@ func New(cfg app.Config) (app.App, error) {
},
},
},
+ {
+ Kind: advisorv0alpha1.CheckTypeKind(),
+ },
},
}
@@ -83,6 +87,13 @@ func New(cfg app.Config) (app.App, error) {
return nil, err
}
+ // Save check types as resources
+ ctr, err := checktyperegisterer.New(cfg)
+ if err != nil {
+ return nil, err
+ }
+ a.AddRunnable(ctr)
+
return a, nil
}
@@ -95,6 +106,7 @@ func GetKinds() map[schema.GroupVersion][]resource.Kind {
return map[schema.GroupVersion][]resource.Kind{
gv: {
advisorv0alpha1.CheckKind(),
+ advisorv0alpha1.CheckTypeKind(),
},
}
}
diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go
index f7dbcc53024..88e10a72719 100644
--- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go
+++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go
@@ -86,11 +86,13 @@ func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, ite
// Data source UID validation
err := util.ValidateUID(ds.UID)
if err != nil {
- dsErrs = append(dsErrs, advisor.CheckReportError{
- Severity: advisor.CheckReportErrorSeverityLow,
- Reason: fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name),
- Action: "Check the documentation for more information.",
- })
+ dsErrs = append(dsErrs, checks.NewCheckReportError(
+ advisor.CheckReportErrorSeverityLow,
+ fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name),
+ "Check the documentation for more information.",
+ s.ID(),
+ ds.UID,
+ ))
}
}
return dsErrs, nil
@@ -141,13 +143,15 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, items
continue
}
if resp.Status != backend.HealthStatusOk {
- dsErrs = append(dsErrs, advisor.CheckReportError{
- Severity: advisor.CheckReportErrorSeverityHigh,
- Reason: fmt.Sprintf("Health check failed for %s", ds.Name),
- Action: fmt.Sprintf(
+ dsErrs = append(dsErrs, checks.NewCheckReportError(
+ advisor.CheckReportErrorSeverityHigh,
+ fmt.Sprintf("Health check failed for %s", ds.Name),
+ fmt.Sprintf(
"Go to the data source configuration"+
" and address the issues reported.", ds.UID),
- })
+ s.ID(),
+ ds.UID,
+ ))
}
}
return dsErrs, nil
diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go
index c64bc1e7579..c024b2608b0 100644
--- a/apps/advisor/pkg/app/checks/plugincheck/check.go
+++ b/apps/advisor/pkg/app/checks/plugincheck/check.go
@@ -97,11 +97,13 @@ func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, items [
continue
}
if i.Status == "deprecated" {
- errs = append(errs, advisor.CheckReportError{
- Severity: advisor.CheckReportErrorSeverityHigh,
- Reason: fmt.Sprintf("Plugin deprecated: %s", p.ID),
- Action: "Check the documentation for recommended steps.",
- })
+ errs = append(errs, checks.NewCheckReportError(
+ advisor.CheckReportErrorSeverityHigh,
+ fmt.Sprintf("Plugin deprecated: %s", p.ID),
+ "Check the documentation for recommended steps.",
+ s.ID(),
+ p.ID,
+ ))
}
}
return errs, nil
@@ -150,13 +152,15 @@ func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any)
continue
}
if hasUpdate(p, info) {
- errs = append(errs, advisor.CheckReportError{
- Severity: advisor.CheckReportErrorSeverityLow,
- Reason: fmt.Sprintf("New version available for %s", p.ID),
- Action: fmt.Sprintf(
+ errs = append(errs, checks.NewCheckReportError(
+ advisor.CheckReportErrorSeverityLow,
+ fmt.Sprintf("New version available for %s", p.ID),
+ fmt.Sprintf(
"Go to the plugin admin page"+
" and upgrade to the latest version.", p.ID),
- })
+ s.ID(),
+ p.ID,
+ ))
}
}
diff --git a/apps/advisor/pkg/app/checks/utils.go b/apps/advisor/pkg/app/checks/utils.go
new file mode 100644
index 00000000000..a6c4089da44
--- /dev/null
+++ b/apps/advisor/pkg/app/checks/utils.go
@@ -0,0 +1,21 @@
+package checks
+
+import (
+ advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
+)
+
+func NewCheckReportError(
+ severity advisor.CheckReportErrorSeverity,
+ reason string,
+ action string,
+ stepID string,
+ itemID string,
+) advisor.CheckReportError {
+ return advisor.CheckReportError{
+ Severity: severity,
+ Reason: reason,
+ Action: action,
+ StepID: stepID,
+ ItemID: itemID,
+ }
+}
diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go
new file mode 100644
index 00000000000..fc5ff150a18
--- /dev/null
+++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go
@@ -0,0 +1,82 @@
+package checktyperegisterer
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/grafana/grafana-app-sdk/app"
+ "github.com/grafana/grafana-app-sdk/k8s"
+ "github.com/grafana/grafana-app-sdk/resource"
+ advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
+ "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// Runner is a "runnable" app used to be able to expose and API endpoint
+// with the existing checks types. This does not need to be a CRUD resource, but it is
+// the only way existing at the moment to expose the check types.
+type Runner struct {
+ checkRegistry checkregistry.CheckService
+ client resource.Client
+}
+
+// NewRunner creates a new Runner.
+func New(cfg app.Config) (app.Runnable, error) {
+ // Read config
+ checkRegistry, ok := cfg.SpecificConfig.(checkregistry.CheckService)
+ if !ok {
+ return nil, fmt.Errorf("invalid config type")
+ }
+
+ // Prepare storage client
+ clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{})
+ client, err := clientGenerator.ClientFor(advisorv0alpha1.CheckTypeKind())
+ if err != nil {
+ return nil, err
+ }
+
+ return &Runner{
+ checkRegistry: checkRegistry,
+ client: client,
+ }, nil
+}
+
+func (r *Runner) Run(ctx context.Context) error {
+ for _, t := range r.checkRegistry.Checks() {
+ steps := t.Steps()
+ stepTypes := make([]advisorv0alpha1.CheckTypeStep, len(steps))
+ for i, s := range steps {
+ stepTypes[i] = advisorv0alpha1.CheckTypeStep{
+ Title: s.Title(),
+ Description: s.Description(),
+ StepID: s.ID(),
+ }
+ }
+ obj := &advisorv0alpha1.CheckType{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: t.ID(),
+ Namespace: metav1.NamespaceDefault,
+ },
+ Spec: advisorv0alpha1.CheckTypeSpec{
+ Name: t.ID(),
+ Steps: stepTypes,
+ },
+ }
+ id := obj.GetStaticMetadata().Identifier()
+ _, err := r.client.Create(ctx, id, obj, resource.CreateOptions{})
+ if err != nil {
+ if errors.IsAlreadyExists(err) {
+ // Already exists, update
+ _, err = r.client.Update(ctx, id, obj, resource.UpdateOptions{})
+ if err != nil {
+ return err
+ } else {
+ continue
+ }
+ }
+ return err
+ }
+ }
+ return nil
+}
diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go
new file mode 100644
index 00000000000..e10ecab9876
--- /dev/null
+++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go
@@ -0,0 +1,169 @@
+package checktyperegisterer
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/grafana/grafana-app-sdk/resource"
+ "github.com/grafana/grafana/apps/advisor/pkg/app/checks"
+ k8sErrs "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+func TestCheckTypesRegisterer_Run(t *testing.T) {
+ tests := []struct {
+ name string
+ checks []checks.Check
+ createFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error)
+ updateFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error)
+ expectedErr error
+ }{
+ {
+ name: "successful create",
+ checks: []checks.Check{
+ &mockCheck{
+ id: "check1",
+ steps: []checks.Step{
+ &mockStep{id: "step1", title: "Step 1", description: "Description 1"},
+ },
+ },
+ },
+ createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
+ return obj, nil
+ },
+ updateFunc: nil,
+ expectedErr: nil,
+ },
+ {
+ name: "create already exists, successful update",
+ checks: []checks.Check{
+ &mockCheck{
+ id: "check1",
+ steps: []checks.Step{
+ &mockStep{id: "step1", title: "Step 1", description: "Description 1"},
+ },
+ },
+ },
+ createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
+ return nil, k8sErrs.NewAlreadyExists(schema.GroupResource{}, obj.GetName())
+ },
+ updateFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) {
+ return obj, nil
+ },
+ expectedErr: nil,
+ },
+ {
+ name: "create error",
+ checks: []checks.Check{
+ &mockCheck{
+ id: "check1",
+ steps: []checks.Step{
+ &mockStep{id: "step1", title: "Step 1", description: "Description 1"},
+ },
+ },
+ },
+ createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
+ return nil, errors.New("create error")
+ },
+ updateFunc: nil,
+ expectedErr: errors.New("create error"),
+ },
+ {
+ name: "update error",
+ checks: []checks.Check{
+ &mockCheck{
+ id: "check1",
+ steps: []checks.Step{
+ &mockStep{id: "step1", title: "Step 1", description: "Description 1"},
+ },
+ },
+ },
+ createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
+ return nil, k8sErrs.NewAlreadyExists(schema.GroupResource{}, obj.GetName())
+ },
+ updateFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) {
+ return nil, errors.New("update error")
+ },
+ expectedErr: errors.New("update error"),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Runner{
+ checkRegistry: &mockCheckRegistry{checks: tt.checks},
+ client: &mockClient{
+ createFunc: tt.createFunc,
+ updateFunc: tt.updateFunc,
+ },
+ }
+ err := r.Run(context.Background())
+ if err != nil {
+ if tt.expectedErr == nil {
+ t.Errorf("unexpected error: %v", err)
+ } else if err.Error() != tt.expectedErr.Error() {
+ t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
+ }
+ }
+ })
+ }
+}
+
+type mockCheckRegistry struct {
+ checks []checks.Check
+}
+
+func (m *mockCheckRegistry) Checks() []checks.Check {
+ return m.checks
+}
+
+type mockCheck struct {
+ checks.Check
+
+ id string
+ steps []checks.Step
+}
+
+func (m *mockCheck) ID() string {
+ return m.id
+}
+
+func (m *mockCheck) Steps() []checks.Step {
+ return m.steps
+}
+
+type mockStep struct {
+ checks.Step
+
+ id string
+ title string
+ description string
+}
+
+func (m *mockStep) ID() string {
+ return m.id
+}
+
+func (m *mockStep) Title() string {
+ return m.title
+}
+
+func (m *mockStep) Description() string {
+ return m.description
+}
+
+type mockClient struct {
+ resource.Client
+
+ createFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error)
+ updateFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error)
+}
+
+func (m *mockClient) Create(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
+ return m.createFunc(ctx, id, obj, opts)
+}
+
+func (m *mockClient) Update(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) {
+ return m.updateFunc(ctx, id, obj, opts)
+}