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) +}