Apps: Add Example App to ./apps (#112069)
* [API Server] Add Example App for reference use. * Remove Printlns. * Upgrade app-sdk to v0.46.0, update apps to handle breaking changes. * Only start the reconciler for the example app if the v1alpha1 API version is enabled. * Some comment doc updates. * Run make update-workspace * Set codeowner for /apps/example * Run make gofmt and make update-workspace * Run prettier on apps/example/README.md * Add COPY apps/example to Dockerfile * Add an authorizer to the example app. * Fix import ordering. * Update apps/example/kinds/manifest.cue Co-authored-by: Owen Diehl <ow.diehl@gmail.com> * Run make update-workspace * Re-run make gen-go for enterprise import updates * Run make update-workspace --------- Co-authored-by: Owen Diehl <ow.diehl@gmail.com>
This commit is contained in:
co-authored by
Owen Diehl
parent
d25f5199c7
commit
bf65c43783
@@ -0,0 +1,18 @@
|
||||
package v0alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
const (
|
||||
// APIGroup is the API group used by all kinds in this package
|
||||
APIGroup = "example.grafana.app"
|
||||
// APIVersion is the API version used by all kinds in this package
|
||||
APIVersion = "v0alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
||||
GroupVersion = schema.GroupVersion{
|
||||
Group: APIGroup,
|
||||
Version: APIVersion,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ExampleClient struct {
|
||||
client *resource.TypedClient[*Example, *ExampleList]
|
||||
}
|
||||
|
||||
func NewExampleClient(client resource.Client) *ExampleClient {
|
||||
return &ExampleClient{
|
||||
client: resource.NewTypedClient[*Example, *ExampleList](client, ExampleKind()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewExampleClientFromGenerator(generator resource.ClientGenerator) (*ExampleClient, error) {
|
||||
c, err := generator.ClientFor(ExampleKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewExampleClient(c), nil
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Get(ctx context.Context, identifier resource.Identifier) (*Example, error) {
|
||||
return c.client.Get(ctx, identifier)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*ExampleList, error) {
|
||||
return c.client.List(ctx, namespace, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*ExampleList, error) {
|
||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for resp.GetContinue() != "" {
|
||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
Continue: resp.GetContinue(),
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SetContinue(page.GetContinue())
|
||||
resp.SetResourceVersion(page.GetResourceVersion())
|
||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Create(ctx context.Context, obj *Example, opts resource.CreateOptions) (*Example, error) {
|
||||
// Make sure apiVersion and kind are set
|
||||
obj.APIVersion = GroupVersion.Identifier()
|
||||
obj.Kind = ExampleKind().Kind()
|
||||
return c.client.Create(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Update(ctx context.Context, obj *Example, opts resource.UpdateOptions) (*Example, error) {
|
||||
return c.client.Update(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Example, error) {
|
||||
return c.client.Patch(ctx, identifier, req, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus ExampleStatus, opts resource.UpdateOptions) (*Example, error) {
|
||||
return c.client.Update(ctx, &Example{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: ExampleKind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Status: newStatus,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||
return c.client.Delete(ctx, identifier, opts)
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
// ExampleJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type ExampleJSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*ExampleJSONCodec) 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 (*ExampleJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &ExampleJSONCodec{}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 ExampleMetadata 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"`
|
||||
}
|
||||
|
||||
// NewExampleMetadata creates a new ExampleMetadata object.
|
||||
func NewExampleMetadata() *ExampleMetadata {
|
||||
return &ExampleMetadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// 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 Example struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the Example
|
||||
Spec ExampleSpec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status ExampleStatus `json:"status" yaml:"status"`
|
||||
}
|
||||
|
||||
func (o *Example) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *Example) SetSpec(spec any) error {
|
||||
cast, ok := spec.(ExampleSpec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Example) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(ExampleStatus)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type ExampleStatus", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) 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 *Example) 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 *Example) 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 *Example) 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 *Example) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *Example) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *Example) 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 *Example) 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 *Example) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *Example) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *Example) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopy() *Example {
|
||||
cpy := &Example{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopyInto(dst *Example) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &Example{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []Example `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *ExampleList) Copy() resource.ListObject {
|
||||
cpy := &ExampleList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]Example, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*Example); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *ExampleList) 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 *ExampleList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]Example, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*Example)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopy() *ExampleList {
|
||||
cpy := &ExampleList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopyInto(dst *ExampleList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &ExampleList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *ExampleSpec) DeepCopy() *ExampleSpec {
|
||||
cpy := &ExampleSpec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *ExampleSpec) DeepCopyInto(dst *ExampleSpec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of ExampleStatus
|
||||
func (s *ExampleStatus) DeepCopy() *ExampleStatus {
|
||||
cpy := &ExampleStatus{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies ExampleStatus into another ExampleStatus object
|
||||
func (s *ExampleStatus) DeepCopyInto(dst *ExampleStatus) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
@@ -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 (
|
||||
schemaExample = resource.NewSimpleSchema("example.grafana.app", "v0alpha1", &Example{}, &ExampleList{}, resource.WithKind("Example"),
|
||||
resource.WithPlural("examples"), resource.WithScope(resource.NamespacedScope))
|
||||
kindExample = resource.Kind{
|
||||
Schema: schemaExample,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &ExampleJSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func ExampleKind() resource.Kind {
|
||||
return kindExample
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of Example
|
||||
func ExampleSchema() *resource.SimpleSchema {
|
||||
return schemaExample
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindExample
|
||||
@@ -0,0 +1,14 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// Spec is the schema of our resource. The spec should include all the user-editable information for the kind.
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleSpec struct {
|
||||
FirstField int64 `json:"firstField"`
|
||||
}
|
||||
|
||||
// NewExampleSpec creates a new ExampleSpec object.
|
||||
func NewExampleSpec() *ExampleSpec {
|
||||
return &ExampleSpec{}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExamplestatusOperatorState 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 ExampleStatusOperatorStateState `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"`
|
||||
}
|
||||
|
||||
// NewExamplestatusOperatorState creates a new ExamplestatusOperatorState object.
|
||||
func NewExamplestatusOperatorState() *ExamplestatusOperatorState {
|
||||
return &ExamplestatusOperatorState{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleStatus struct {
|
||||
LastObservedGeneration int64 `json:"lastObservedGeneration"`
|
||||
// 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]ExamplestatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewExampleStatus creates a new ExampleStatus object.
|
||||
func NewExampleStatus() *ExampleStatus {
|
||||
return &ExampleStatus{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleStatusOperatorStateState string
|
||||
|
||||
const (
|
||||
ExampleStatusOperatorStateStateSuccess ExampleStatusOperatorStateState = "success"
|
||||
ExampleStatusOperatorStateStateInProgress ExampleStatusOperatorStateState = "in_progress"
|
||||
ExampleStatusOperatorStateStateFailed ExampleStatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package v1alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
const (
|
||||
// APIGroup is the API group used by all kinds in this package
|
||||
APIGroup = "example.grafana.app"
|
||||
// APIVersion is the API version used by all kinds in this package
|
||||
APIVersion = "v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
||||
GroupVersion = schema.GroupVersion{
|
||||
Group: APIGroup,
|
||||
Version: APIVersion,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ExampleClient struct {
|
||||
client *resource.TypedClient[*Example, *ExampleList]
|
||||
}
|
||||
|
||||
func NewExampleClient(client resource.Client) *ExampleClient {
|
||||
return &ExampleClient{
|
||||
client: resource.NewTypedClient[*Example, *ExampleList](client, ExampleKind()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewExampleClientFromGenerator(generator resource.ClientGenerator) (*ExampleClient, error) {
|
||||
c, err := generator.ClientFor(ExampleKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewExampleClient(c), nil
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Get(ctx context.Context, identifier resource.Identifier) (*Example, error) {
|
||||
return c.client.Get(ctx, identifier)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*ExampleList, error) {
|
||||
return c.client.List(ctx, namespace, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*ExampleList, error) {
|
||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for resp.GetContinue() != "" {
|
||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
Continue: resp.GetContinue(),
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SetContinue(page.GetContinue())
|
||||
resp.SetResourceVersion(page.GetResourceVersion())
|
||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Create(ctx context.Context, obj *Example, opts resource.CreateOptions) (*Example, error) {
|
||||
// Make sure apiVersion and kind are set
|
||||
obj.APIVersion = GroupVersion.Identifier()
|
||||
obj.Kind = ExampleKind().Kind()
|
||||
return c.client.Create(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Update(ctx context.Context, obj *Example, opts resource.UpdateOptions) (*Example, error) {
|
||||
return c.client.Update(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Example, error) {
|
||||
return c.client.Patch(ctx, identifier, req, opts)
|
||||
}
|
||||
|
||||
func (c *ExampleClient) UpdateCustom(ctx context.Context, identifier resource.Identifier, newCustom ExampleCustom, opts resource.UpdateOptions) (*Example, error) {
|
||||
return c.client.Update(ctx, &Example{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: ExampleKind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Custom: newCustom,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "custom",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
func (c *ExampleClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus ExampleStatus, opts resource.UpdateOptions) (*Example, error) {
|
||||
return c.client.Update(ctx, &Example{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: ExampleKind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Status: newStatus,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ExampleClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||
return c.client.Delete(ctx, identifier, opts)
|
||||
}
|
||||
|
||||
type GetFooRequest struct {
|
||||
Params GetFooRequestParams
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
func (c *ExampleClient) GetFoo(ctx context.Context, identifier resource.Identifier, request GetFooRequest) (*GetFoo, error) {
|
||||
params := url.Values{}
|
||||
resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{
|
||||
Path: "foo",
|
||||
Verb: "GET",
|
||||
Query: params,
|
||||
Headers: request.Headers,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cast := GetFoo{}
|
||||
err = json.Unmarshal(resp, &cast)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal response bytes into GetFoo: %w", err)
|
||||
}
|
||||
return &cast, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// ExampleJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type ExampleJSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*ExampleJSONCodec) 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 (*ExampleJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &ExampleJSONCodec{}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Custom is a subresource that will be stored the same way status is stored,
|
||||
// and requires using the /custom route to update.
|
||||
// Its content is returned as part of a GET to the resource itself, just like with status.
|
||||
// To route a subresource to an arbitrary handler, use the 'routes' field instead (see below).
|
||||
// metadata if where kind- and schema-specific metadata goes. This is converted into typed annotations
|
||||
// with getters and setters by the code generation.
|
||||
//
|
||||
// metadata: {
|
||||
// kindSpecificField: string
|
||||
// }
|
||||
//
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleCustom struct {
|
||||
MyField string `json:"myField"`
|
||||
OtherField string `json:"otherField"`
|
||||
}
|
||||
|
||||
// NewExampleCustom creates a new ExampleCustom object.
|
||||
func NewExampleCustom() *ExampleCustom {
|
||||
return &ExampleCustom{}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type GetFooRequestBody struct {
|
||||
Bar string `json:"bar"`
|
||||
}
|
||||
|
||||
// NewGetFooRequestBody creates a new GetFooRequestBody object.
|
||||
func NewGetFooRequestBody() *GetFooRequestBody {
|
||||
return &GetFooRequestBody{}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type GetFooRequestParamsObject struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
GetFooRequestParams `json:",inline"`
|
||||
}
|
||||
|
||||
func NewGetFooRequestParamsObject() *GetFooRequestParamsObject {
|
||||
return &GetFooRequestParamsObject{}
|
||||
}
|
||||
|
||||
func (o *GetFooRequestParamsObject) DeepCopyObject() runtime.Object {
|
||||
dst := NewGetFooRequestParamsObject()
|
||||
o.DeepCopyInto(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (o *GetFooRequestParamsObject) DeepCopyInto(dst *GetFooRequestParamsObject) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
dstGetFooRequestParams := GetFooRequestParams{}
|
||||
_ = resource.CopyObjectInto(&dstGetFooRequestParams, &o.GetFooRequestParams)
|
||||
}
|
||||
|
||||
var _ runtime.Object = NewGetFooRequestParamsObject()
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type GetFooRequestParams struct {
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetFooRequestParams creates a new GetFooRequestParams object.
|
||||
func NewGetFooRequestParams() *GetFooRequestParams {
|
||||
return &GetFooRequestParams{}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// The response type for the GET /foo method. This will generate a go type, and will also be used for the OpenAPI definition for the route.
|
||||
// +k8s:openapi-gen=true
|
||||
type GetFooBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// NewGetFooBody creates a new GetFooBody object.
|
||||
func NewGetFooBody() *GetFooBody {
|
||||
return &GetFooBody{}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type GetFoo struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
GetFooBody `json:",inline"`
|
||||
}
|
||||
|
||||
func NewGetFoo() *GetFoo {
|
||||
return &GetFoo{}
|
||||
}
|
||||
|
||||
func (t *GetFooBody) DeepCopyInto(dst *GetFooBody) {
|
||||
_ = resource.CopyObjectInto(dst, t)
|
||||
}
|
||||
|
||||
func (o *GetFoo) DeepCopyObject() runtime.Object {
|
||||
dst := NewGetFoo()
|
||||
o.DeepCopyInto(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (o *GetFoo) DeepCopyInto(dst *GetFoo) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.GetFooBody.DeepCopyInto(&dst.GetFooBody)
|
||||
}
|
||||
|
||||
var _ runtime.Object = NewGetFoo()
|
||||
@@ -0,0 +1,31 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
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 ExampleMetadata 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"`
|
||||
}
|
||||
|
||||
// NewExampleMetadata creates a new ExampleMetadata object.
|
||||
func NewExampleMetadata() *ExampleMetadata {
|
||||
return &ExampleMetadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
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 Example struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the Example
|
||||
Spec ExampleSpec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status ExampleStatus `json:"status" yaml:"status"`
|
||||
|
||||
Custom ExampleCustom `json:"custom" yaml:"custom"`
|
||||
}
|
||||
|
||||
func (o *Example) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *Example) SetSpec(spec any) error {
|
||||
cast, ok := spec.(ExampleSpec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Example) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
|
||||
"custom": o.Custom,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
|
||||
case "custom":
|
||||
return o.Custom, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(ExampleStatus)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type ExampleStatus", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
|
||||
case "custom":
|
||||
cast, ok := value.(ExampleCustom)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set custom type %#v, not of type ExampleCustom", value)
|
||||
}
|
||||
o.Custom = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Example) 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 *Example) 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 *Example) 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 *Example) 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 *Example) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *Example) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *Example) 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 *Example) 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 *Example) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *Example) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *Example) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopy() *Example {
|
||||
cpy := &Example{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *Example) DeepCopyInto(dst *Example) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
o.Custom.DeepCopyInto(&dst.Custom)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &Example{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []Example `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *ExampleList) Copy() resource.ListObject {
|
||||
cpy := &ExampleList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]Example, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*Example); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *ExampleList) 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 *ExampleList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]Example, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*Example)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopy() *ExampleList {
|
||||
cpy := &ExampleList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *ExampleList) DeepCopyInto(dst *ExampleList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &ExampleList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *ExampleSpec) DeepCopy() *ExampleSpec {
|
||||
cpy := &ExampleSpec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *ExampleSpec) DeepCopyInto(dst *ExampleSpec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of ExampleStatus
|
||||
func (s *ExampleStatus) DeepCopy() *ExampleStatus {
|
||||
cpy := &ExampleStatus{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies ExampleStatus into another ExampleStatus object
|
||||
func (s *ExampleStatus) DeepCopyInto(dst *ExampleStatus) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of ExampleCustom
|
||||
func (s *ExampleCustom) DeepCopy() *ExampleCustom {
|
||||
cpy := &ExampleCustom{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies ExampleCustom into another ExampleCustom object
|
||||
func (s *ExampleCustom) DeepCopyInto(dst *ExampleCustom) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaExample = resource.NewSimpleSchema("example.grafana.app", "v1alpha1", &Example{}, &ExampleList{}, resource.WithKind("Example"),
|
||||
resource.WithPlural("examples"), resource.WithScope(resource.NamespacedScope))
|
||||
kindExample = resource.Kind{
|
||||
Schema: schemaExample,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &ExampleJSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func ExampleKind() resource.Kind {
|
||||
return kindExample
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of Example
|
||||
func ExampleSchema() *resource.SimpleSchema {
|
||||
return schemaExample
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindExample
|
||||
@@ -0,0 +1,34 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// #DefinedType is a re-usable definition for us to use in our schema.
|
||||
// Fields leading with # are definitions in CUE and won't be included in the generated types.
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleDefinedType struct {
|
||||
// Info is information about this entry. This comment, like all comments
|
||||
// on fields or definitions, will be copied into the generated types as well.
|
||||
Info string `json:"info"`
|
||||
// Next is an optional next element in the DefinedType, allowing for a self-referential
|
||||
// linked-list like structure. The ? in the field makes this optional.
|
||||
Next *ExampleDefinedType `json:"next,omitempty"`
|
||||
}
|
||||
|
||||
// NewExampleDefinedType creates a new ExampleDefinedType object.
|
||||
func NewExampleDefinedType() *ExampleDefinedType {
|
||||
return &ExampleDefinedType{}
|
||||
}
|
||||
|
||||
// Spec is the schema of our resource. The spec should include all the user-editable information for the kind.
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleSpec struct {
|
||||
// Example fields
|
||||
FirstField string `json:"firstField"`
|
||||
SecondField int64 `json:"secondField"`
|
||||
List *ExampleDefinedType `json:"list,omitempty"`
|
||||
}
|
||||
|
||||
// NewExampleSpec creates a new ExampleSpec object.
|
||||
func NewExampleSpec() *ExampleSpec {
|
||||
return &ExampleSpec{}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExamplestatusOperatorState 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 ExampleStatusOperatorStateState `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"`
|
||||
}
|
||||
|
||||
// NewExamplestatusOperatorState creates a new ExamplestatusOperatorState object.
|
||||
func NewExamplestatusOperatorState() *ExamplestatusOperatorState {
|
||||
return &ExamplestatusOperatorState{}
|
||||
}
|
||||
|
||||
// status is where state and status information which may be used or updated by the operator or back-end should be placed
|
||||
// If you do not have any such information, you do not need to include this field,
|
||||
// however, as mentioned above, certain fields will be added by the kind system regardless.
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleStatus struct {
|
||||
LastObservedGeneration int64 `json:"lastObservedGeneration"`
|
||||
// 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]ExamplestatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewExampleStatus creates a new ExampleStatus object.
|
||||
func NewExampleStatus() *ExampleStatus {
|
||||
return &ExampleStatus{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ExampleStatusOperatorStateState string
|
||||
|
||||
const (
|
||||
ExampleStatusOperatorStateStateSuccess ExampleStatusOperatorStateState = "success"
|
||||
ExampleStatusOperatorStateStateInProgress ExampleStatusOperatorStateState = "in_progress"
|
||||
ExampleStatusOperatorStateStateFailed ExampleStatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type GetOtherRequestParamsObject struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
GetOtherRequestParams `json:",inline"`
|
||||
}
|
||||
|
||||
func NewGetOtherRequestParamsObject() *GetOtherRequestParamsObject {
|
||||
return &GetOtherRequestParamsObject{}
|
||||
}
|
||||
|
||||
func (o *GetOtherRequestParamsObject) DeepCopyObject() runtime.Object {
|
||||
dst := NewGetOtherRequestParamsObject()
|
||||
o.DeepCopyInto(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (o *GetOtherRequestParamsObject) DeepCopyInto(dst *GetOtherRequestParamsObject) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
dstGetOtherRequestParams := GetOtherRequestParams{}
|
||||
_ = resource.CopyObjectInto(&dstGetOtherRequestParams, &o.GetOtherRequestParams)
|
||||
}
|
||||
|
||||
var _ runtime.Object = NewGetOtherRequestParamsObject()
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type GetOtherRequestParams struct {
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetOtherRequestParams creates a new GetOtherRequestParams object.
|
||||
func NewGetOtherRequestParams() *GetOtherRequestParams {
|
||||
return &GetOtherRequestParams{}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type GetOther struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// NewGetOther creates a new GetOther object.
|
||||
func NewGetOther() *GetOther {
|
||||
return &GetOther{}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type GetSomethingRequestParamsObject struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
GetSomethingRequestParams `json:",inline"`
|
||||
}
|
||||
|
||||
func NewGetSomethingRequestParamsObject() *GetSomethingRequestParamsObject {
|
||||
return &GetSomethingRequestParamsObject{}
|
||||
}
|
||||
|
||||
func (o *GetSomethingRequestParamsObject) DeepCopyObject() runtime.Object {
|
||||
dst := NewGetSomethingRequestParamsObject()
|
||||
o.DeepCopyInto(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (o *GetSomethingRequestParamsObject) DeepCopyInto(dst *GetSomethingRequestParamsObject) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
dstGetSomethingRequestParams := GetSomethingRequestParams{}
|
||||
_ = resource.CopyObjectInto(&dstGetSomethingRequestParams, &o.GetSomethingRequestParams)
|
||||
}
|
||||
|
||||
var _ runtime.Object = NewGetSomethingRequestParamsObject()
|
||||
@@ -0,0 +1,12 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type GetSomethingRequestParams struct {
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetSomethingRequestParams creates a new GetSomethingRequestParams object.
|
||||
func NewGetSomethingRequestParams() *GetSomethingRequestParams {
|
||||
return &GetSomethingRequestParams{}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type GetSomethingBody struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// NewGetSomethingBody creates a new GetSomethingBody object.
|
||||
func NewGetSomethingBody() *GetSomethingBody {
|
||||
return &GetSomethingBody{}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type GetSomething struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
GetSomethingBody `json:",inline"`
|
||||
}
|
||||
|
||||
func NewGetSomething() *GetSomething {
|
||||
return &GetSomething{}
|
||||
}
|
||||
|
||||
func (t *GetSomethingBody) DeepCopyInto(dst *GetSomethingBody) {
|
||||
_ = resource.CopyObjectInto(dst, t)
|
||||
}
|
||||
|
||||
func (o *GetSomething) DeepCopyObject() runtime.Object {
|
||||
dst := NewGetSomething()
|
||||
o.DeepCopyInto(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (o *GetSomething) DeepCopyInto(dst *GetSomething) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.GetSomethingBody.DeepCopyInto(&dst.GetSomethingBody)
|
||||
}
|
||||
|
||||
var _ runtime.Object = NewGetSomething()
|
||||
@@ -0,0 +1,374 @@
|
||||
//
|
||||
// This file is generated by grafana-app-sdk
|
||||
// DO NOT EDIT
|
||||
//
|
||||
|
||||
package apis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v0alpha1"
|
||||
v1alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
rawSchemaExamplev0alpha1 = []byte(`{"Example":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"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"},"spec":{"additionalProperties":false,"description":"Spec is the schema of our resource. The spec should include all the user-editable information for the kind.","properties":{"firstField":{"type":"integer"}},"required":["firstField"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"lastObservedGeneration":{"type":"integer"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"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"}},"required":["lastObservedGeneration"],"type":"object"}}`)
|
||||
versionSchemaExamplev0alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaExamplev0alpha1, &versionSchemaExamplev0alpha1)
|
||||
rawSchemaExamplev1alpha1 = []byte(`{"DefinedType":{"additionalProperties":false,"description":"#DefinedType is a re-usable definition for us to use in our schema.\nFields leading with # are definitions in CUE and won't be included in the generated types.","properties":{"info":{"description":"Info is information about this entry. This comment, like all comments\non fields or definitions, will be copied into the generated types as well.","type":"string"},"next":{"$ref":"#/components/schemas/DefinedType","description":"Next is an optional next element in the DefinedType, allowing for a self-referential\nlinked-list like structure. The ? in the field makes this optional."}},"required":["info"],"type":"object"},"Example":{"properties":{"custom":{"$ref":"#/components/schemas/custom"},"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"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"},"custom":{"additionalProperties":false,"description":"Custom is a subresource that will be stored the same way status is stored,\nand requires using the /custom route to update.\nIts content is returned as part of a GET to the resource itself, just like with status.\nTo route a subresource to an arbitrary handler, use the 'routes' field instead (see below).\nmetadata if where kind- and schema-specific metadata goes. This is converted into typed annotations\nwith getters and setters by the code generation.\nmetadata: {\n\tkindSpecificField: string\n}","properties":{"myField":{"type":"string"},"otherField":{"type":"string"}},"required":["myField","otherField"],"type":"object"},"spec":{"additionalProperties":false,"description":"Spec is the schema of our resource. The spec should include all the user-editable information for the kind.","properties":{"firstField":{"description":"Example fields","type":"string"},"list":{"$ref":"#/components/schemas/DefinedType"},"secondField":{"type":"integer"}},"required":["firstField","secondField"],"type":"object"},"status":{"additionalProperties":false,"description":"status is where state and status information which may be used or updated by the operator or back-end should be placed\nIf you do not have any such information, you do not need to include this field,\nhowever, as mentioned above, certain fields will be added by the kind system regardless.","properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"lastObservedGeneration":{"type":"integer"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"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"}},"required":["lastObservedGeneration"],"type":"object"}}`)
|
||||
versionSchemaExamplev1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaExamplev1alpha1, &versionSchemaExamplev1alpha1)
|
||||
)
|
||||
|
||||
var appManifestData = app.ManifestData{
|
||||
AppName: "example",
|
||||
Group: "example.grafana.app",
|
||||
PreferredVersion: "v1alpha1",
|
||||
Versions: []app.ManifestVersion{
|
||||
{
|
||||
Name: "v0alpha1",
|
||||
Served: false,
|
||||
Kinds: []app.ManifestVersionKind{
|
||||
{
|
||||
Kind: "Example",
|
||||
Plural: "Examples",
|
||||
Scope: "Namespaced",
|
||||
Conversion: true,
|
||||
Admission: &app.AdmissionCapabilities{
|
||||
Validation: &app.ValidationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
Mutation: &app.MutationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
Schema: &versionSchemaExamplev0alpha1,
|
||||
},
|
||||
},
|
||||
Routes: app.ManifestVersionRoutes{
|
||||
Namespaced: map[string]spec3.PathProps{},
|
||||
Cluster: map[string]spec3.PathProps{},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "v1alpha1",
|
||||
Served: false,
|
||||
Kinds: []app.ManifestVersionKind{
|
||||
{
|
||||
Kind: "Example",
|
||||
Plural: "Examples",
|
||||
Scope: "Namespaced",
|
||||
Conversion: true,
|
||||
Admission: &app.AdmissionCapabilities{
|
||||
Validation: &app.ValidationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
Mutation: &app.MutationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
Schema: &versionSchemaExamplev1alpha1,
|
||||
Routes: map[string]spec3.PathProps{
|
||||
"foo": {
|
||||
Get: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
|
||||
OperationId: "getFoo",
|
||||
|
||||
Parameters: []*spec3.Parameter{
|
||||
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "message",
|
||||
In: "query",
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
Default: &spec3.Response{
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Description: "Default OK response",
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Description: "The response type for the GET /foo method. This will generate a go type, and will also be used for the OpenAPI definition for the route.",
|
||||
Properties: map[string]spec.Schema{
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
"message": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"message",
|
||||
"apiVersion",
|
||||
"kind",
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Routes: app.ManifestVersionRoutes{
|
||||
Namespaced: map[string]spec3.PathProps{
|
||||
"/something": {
|
||||
Get: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
|
||||
OperationId: "getSomething",
|
||||
|
||||
Parameters: []*spec3.Parameter{
|
||||
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "message",
|
||||
In: "query",
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
Default: &spec3.Response{
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Description: "Default OK response",
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
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",
|
||||
},
|
||||
},
|
||||
"message": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
"namespace": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"namespace",
|
||||
"message",
|
||||
"apiVersion",
|
||||
"kind",
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Cluster: map[string]spec3.PathProps{
|
||||
"/other": {
|
||||
Get: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
|
||||
OperationId: "getOther",
|
||||
|
||||
Parameters: []*spec3.Parameter{
|
||||
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "message",
|
||||
In: "query",
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
Default: &spec3.Response{
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Description: "Default OK response",
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"message": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{
|
||||
"message",
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func LocalManifest() app.Manifest {
|
||||
return app.NewEmbeddedManifest(appManifestData)
|
||||
}
|
||||
|
||||
func RemoteManifest() app.Manifest {
|
||||
return app.NewAPIServerManifest("example")
|
||||
}
|
||||
|
||||
var kindVersionToGoType = map[string]resource.Kind{
|
||||
"Example/v0alpha1": v0alpha1.ExampleKind(),
|
||||
"Example/v1alpha1": v1alpha1.ExampleKind(),
|
||||
}
|
||||
|
||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
||||
// If there is no association for the provided Kind and Version, exists will return false.
|
||||
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
|
||||
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoResponseType = map[string]any{
|
||||
|
||||
"v1alpha1|Example|foo|GET": v1alpha1.GetFoo{},
|
||||
|
||||
"v1alpha1||<namespace>/something|GET": v1alpha1.GetSomething{},
|
||||
"v1alpha1||other|GET": v1alpha1.GetOther{},
|
||||
}
|
||||
|
||||
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
|
||||
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
|
||||
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
|
||||
// Resource routes (those without a kind) should prefix their route with "<namespace>/" if the route is namespaced (otherwise the route is assumed to be cluster-scope)
|
||||
func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoParamsType = map[string]runtime.Object{
|
||||
"v1alpha1|Example|foo|GET": &v1alpha1.GetFooRequestParamsObject{},
|
||||
}
|
||||
|
||||
func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoRequestBodyType = map[string]any{}
|
||||
|
||||
func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
type GoTypeAssociator struct{}
|
||||
|
||||
func NewGoTypeAssociator() *GoTypeAssociator {
|
||||
return &GoTypeAssociator{}
|
||||
}
|
||||
|
||||
func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) {
|
||||
return ManifestGoTypeAssociator(kind, version)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) {
|
||||
return ManifestCustomRouteResponsesAssociator(kind, version, path, verb)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
||||
return ManifestCustomRouteQueryAssociator(kind, version, path, verb)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) {
|
||||
return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/k8s"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana-app-sdk/operator"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"github.com/grafana/grafana-app-sdk/simple"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
examplev0alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v0alpha1"
|
||||
examplev1alpha1 "github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
)
|
||||
|
||||
// New creates a new instance of the Example App. It gets called after the app's APIs have been registered,
|
||||
// and is used for routing non-storage API requests, admission control, conversion, and can run
|
||||
// reconcilers on kinds.
|
||||
func New(cfg app.Config) (app.App, error) {
|
||||
// APIPath needs to be set to `/apis`, as it defaults to empty
|
||||
cfg.KubeConfig.APIPath = "/apis"
|
||||
// We create a client to work with our Example kind in our reconciler
|
||||
client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()).ClientFor(examplev1alpha1.ExampleKind())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create example client: %w", err)
|
||||
}
|
||||
var reconciler operator.Reconciler
|
||||
exampleConfig, ok := cfg.SpecificConfig.(*ExampleConfig)
|
||||
if ok && exampleConfig.EnableReconciler {
|
||||
reconciler = NewExampleReconciler(client)
|
||||
// Set the default logger if the reconciler is enabled--this should be done in grafana's API server handling instead,
|
||||
// and will be corrected in a future PR
|
||||
logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug, // Temporarily hardcoded to debug for the example
|
||||
}))
|
||||
}
|
||||
|
||||
// This is the configuration for our App.
|
||||
simpleConfig := simple.AppConfig{
|
||||
Name: "example",
|
||||
KubeConfig: cfg.KubeConfig,
|
||||
// ManagedKinds is the list of all kinds our app manages (the kinds owned by our app).
|
||||
// Here, a Kind is defined as a distinct Group, Version, and Kind combination,
|
||||
// so for each version of our Example kind, we need to add it to this list.
|
||||
// Each kind can also have admission control attached to it--different versions can have different admission control attached.
|
||||
// Handlers for custom routes defined in the manifest for the kind go here--this is where they actuall get routed,
|
||||
// they are only defined in the manifest.
|
||||
// Reconcilers and/or Watchers are also attached here, though they should only be attached to a single version per kind.
|
||||
ManagedKinds: []simple.AppManagedKind{
|
||||
{
|
||||
Kind: examplev0alpha1.ExampleKind(),
|
||||
// Validator is run on ingress and is it returns an error the request is rejected
|
||||
Validator: NewValidator(),
|
||||
// Mutator is run on ingress and makes changes to the input object
|
||||
Mutator: NewMutator(),
|
||||
},
|
||||
{
|
||||
Kind: examplev1alpha1.ExampleKind(),
|
||||
// We only want the reconciler on one version of our kind, and it's usually best to use the latest
|
||||
// We'll receive events for every example object, regardless of version used in the API,
|
||||
// it will convert them to the version used for the reconciler.
|
||||
Reconciler: reconciler,
|
||||
// By default, reconcilers for ManagedKinds are wrapped in
|
||||
ReconcileOptions: simple.BasicReconcileOptions{
|
||||
// Namespace is the namespace your reconciler will watch.
|
||||
// It defaults to all, so this isn't necessary to specify the way we do here.
|
||||
Namespace: resource.NamespaceAll,
|
||||
// We can optionally filter our reconciler to only get events for Example resources which
|
||||
// satisfy the following label filters
|
||||
// LabelFilters: []string{"foo=bar"},
|
||||
// By default, reconcilers for ManagedKinds are wrapped in the app-sdk's OpinionatedReconciler.
|
||||
// To turn this functionality off, you can set UsePlain to false
|
||||
// UsePlain: true,
|
||||
},
|
||||
// Validator is run on ingress and is it returns an error the request is rejected
|
||||
Validator: NewValidator(),
|
||||
// Mutator is run on ingress and makes changes to the input object
|
||||
Mutator: NewMutator(),
|
||||
// We defined this route in our CUE, but we need to actually define the HTTP handler for it.
|
||||
CustomRoutes: simple.AppCustomRouteHandlers{
|
||||
{
|
||||
Path: "foo",
|
||||
Method: "GET",
|
||||
}: ExampleGetFooHandler,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Conversion for kinds is defined for all versions of a kind at once.
|
||||
// This interface may change in the future, see https://github.com/grafana/grafana-app-sdk/issues/617
|
||||
Converters: map[schema.GroupKind]simple.Converter{
|
||||
{
|
||||
Group: cfg.ManifestData.Group,
|
||||
Kind: examplev0alpha1.ExampleKind().Kind(),
|
||||
}: NewExampleConverter(),
|
||||
},
|
||||
// VersionedCustomRoutes are the custom route handlers for routes defined at the version level of the manifest
|
||||
// instead of for a specific kind. This are sometimes referred to as "resource routes"
|
||||
// (as opposed to "subresource routes" which are attached to kinds).
|
||||
VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{
|
||||
"v1alpha1": {
|
||||
{
|
||||
Namespaced: true,
|
||||
Path: "something",
|
||||
Method: "GET",
|
||||
}: GetSomethingHandler,
|
||||
{
|
||||
Namespaced: false,
|
||||
Path: "other",
|
||||
Method: "GET",
|
||||
}: GetOtherHandler,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
a, err := simple.NewApp(simpleConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This makes it easier to catch problems at startup, rather than when something doesn't behave as expected.
|
||||
// ValidateManifest will ensure that the capabilities you define in your simple.AppConfig
|
||||
// match the capabilities described in the AppManifest.
|
||||
err = a.ValidateManifest(cfg.ManifestData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func GetKinds() map[schema.GroupVersion][]resource.Kind {
|
||||
gv := schema.GroupVersion{
|
||||
Group: examplev1alpha1.ExampleKind().Group(),
|
||||
Version: examplev1alpha1.ExampleKind().Version(),
|
||||
}
|
||||
return map[schema.GroupVersion][]resource.Kind{
|
||||
gv: {examplev1alpha1.ExampleKind()},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
var namespacedSomethingRouteMatcher = regexp.MustCompile(fmt.Sprintf(`^/apis/%s/%s/namespaces/([^\/]+)/something$`, v1alpha1.APIGroup, v1alpha1.APIVersion))
|
||||
|
||||
// GetAuthorizer returns an authorizer for all kinds managed by the example app.
|
||||
// It must be added to the installer in pkg/registry/apps/example/register.go to be used
|
||||
func GetAuthorizer() authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(
|
||||
func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
if !attr.IsResourceRequest() {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
// require a user
|
||||
u, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "valid user is required", err
|
||||
}
|
||||
|
||||
// check if is admin
|
||||
if u.GetIsGrafanaAdmin() {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
// Only allow admins to call the custom subresource
|
||||
if attr.GetSubresource() == "custom" {
|
||||
return authorizer.DecisionDeny, "forbidden", nil
|
||||
}
|
||||
|
||||
// Only allow admins to call the namespaced and cluster routes
|
||||
// There's no easy way to check that from attrs like with GetSubresource(),
|
||||
// so we look at the full path and check
|
||||
if namespacedSomethingRouteMatcher.MatchString(attr.GetPath()) {
|
||||
return authorizer.DecisionDeny, "forbidden", nil
|
||||
}
|
||||
if attr.GetPath() == fmt.Sprintf("/apis/%s/%s/other", v1alpha1.APIGroup, v1alpha1.APIVersion) {
|
||||
return authorizer.DecisionDeny, "forbidden", nil
|
||||
}
|
||||
|
||||
// Otherwise, allow
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
// ExampleConfig is an example app-specific config type
|
||||
type ExampleConfig struct {
|
||||
EnableReconciler bool
|
||||
EnableSomeFeature bool
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/k8s"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"github.com/grafana/grafana-app-sdk/simple"
|
||||
"github.com/grafana/grafana/apps/example/pkg/apis/example/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var _ simple.Converter = NewExampleConverter()
|
||||
|
||||
type ExampleConverter struct{}
|
||||
|
||||
func NewExampleConverter() *ExampleConverter {
|
||||
return &ExampleConverter{}
|
||||
}
|
||||
|
||||
// Convert converts an object from an arbitrary input version slice of bytes
|
||||
// to a target version, and returns the JSON bytes of that version.
|
||||
func (e *ExampleConverter) Convert(obj k8s.RawKind, targetAPIVersion string) ([]byte, error) {
|
||||
srcGVK := schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
dstGVK := schema.FromAPIVersionAndKind(targetAPIVersion, v1alpha1.ExampleKind().Kind())
|
||||
if srcGVK.Group != v1alpha1.APIGroup {
|
||||
// This should never happen, but check just in case
|
||||
return nil, fmt.Errorf("wrong group to convert example.grafana.app, got %s", srcGVK.Group)
|
||||
}
|
||||
if srcGVK.Kind != v1alpha1.ExampleKind().Kind() {
|
||||
// This should also never happen, but check just in case
|
||||
return nil, fmt.Errorf("wrong kind to convert Example, got %s", srcGVK.Kind)
|
||||
}
|
||||
if srcGVK == dstGVK {
|
||||
// This should never happen, but if it does no conversion is necessary, we can return the input
|
||||
return obj.Raw, nil
|
||||
}
|
||||
|
||||
// Check source version
|
||||
switch srcGVK.Version {
|
||||
case v0alpha1.APIVersion:
|
||||
srcKind := v0alpha1.ExampleKind()
|
||||
uncastSrcObj, err := srcKind.Read(bytes.NewReader(obj.Raw), resource.KindEncodingJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse JSON bytes into %s: %w", srcGVK.String(), err)
|
||||
}
|
||||
srcObj, ok := uncastSrcObj.(*v0alpha1.Example)
|
||||
if !ok {
|
||||
return nil, errors.New("read object was not of type *v0alpha1.Example")
|
||||
}
|
||||
switch dstGVK.Version {
|
||||
case v1alpha1.APIVersion:
|
||||
dstObj := &v1alpha1.Example{}
|
||||
// Set Type metadata
|
||||
dstObj.SetGroupVersionKind(dstGVK)
|
||||
// Copy Object metadata
|
||||
srcObj.ObjectMeta.DeepCopyInto(&dstObj.ObjectMeta)
|
||||
// Copy spec and status
|
||||
dstObj.Spec.FirstField = strconv.Itoa(int(srcObj.Spec.FirstField))
|
||||
dstObj.Status.LastObservedGeneration = srcObj.Status.LastObservedGeneration
|
||||
dstObj.Status.AdditionalFields = srcObj.Status.AdditionalFields
|
||||
if srcObj.Status.OperatorStates != nil {
|
||||
dstObj.Status.OperatorStates = make(map[string]v1alpha1.ExamplestatusOperatorState)
|
||||
for k, v := range srcObj.Status.OperatorStates {
|
||||
dstObj.Status.OperatorStates[k] = v1alpha1.ExamplestatusOperatorState{
|
||||
LastEvaluation: v.LastEvaluation,
|
||||
State: v1alpha1.ExampleStatusOperatorStateState(v.State),
|
||||
DescriptiveState: v.DescriptiveState,
|
||||
Details: v.Details,
|
||||
}
|
||||
}
|
||||
}
|
||||
dstKind := v1alpha1.ExampleKind()
|
||||
buf := &bytes.Buffer{}
|
||||
err := dstKind.Write(dstObj, buf, resource.KindEncodingJSON)
|
||||
return buf.Bytes(), err
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown target version %s", dstGVK.Version)
|
||||
}
|
||||
case v1alpha1.APIVersion:
|
||||
srcKind := v1alpha1.ExampleKind()
|
||||
uncastSrcObj, err := srcKind.Read(bytes.NewReader(obj.Raw), resource.KindEncodingJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse JSON bytes into %s: %w", srcGVK.String(), err)
|
||||
}
|
||||
srcObj, ok := uncastSrcObj.(*v1alpha1.Example)
|
||||
if !ok {
|
||||
return nil, errors.New("read object was not of type *v1alpha1.Example")
|
||||
}
|
||||
switch dstGVK.Version {
|
||||
case v0alpha1.APIVersion:
|
||||
dstObj := &v0alpha1.Example{}
|
||||
// Set Type metadata
|
||||
dstObj.SetGroupVersionKind(dstGVK)
|
||||
// Copy Object metadata
|
||||
srcObj.ObjectMeta.DeepCopyInto(&dstObj.ObjectMeta)
|
||||
// Copy spec and status
|
||||
castInt, _ := strconv.Atoi(srcObj.Spec.FirstField) // Lossy backwards conversion
|
||||
dstObj.Spec.FirstField = int64(castInt)
|
||||
dstObj.Status.LastObservedGeneration = srcObj.Status.LastObservedGeneration
|
||||
dstObj.Status.AdditionalFields = srcObj.Status.AdditionalFields
|
||||
if srcObj.Status.OperatorStates != nil {
|
||||
dstObj.Status.OperatorStates = make(map[string]v0alpha1.ExamplestatusOperatorState)
|
||||
for k, v := range srcObj.Status.OperatorStates {
|
||||
dstObj.Status.OperatorStates[k] = v0alpha1.ExamplestatusOperatorState{
|
||||
LastEvaluation: v.LastEvaluation,
|
||||
State: v0alpha1.ExampleStatusOperatorStateState(v.State),
|
||||
DescriptiveState: v.DescriptiveState,
|
||||
Details: v.Details,
|
||||
}
|
||||
}
|
||||
}
|
||||
dstKind := v0alpha1.ExampleKind()
|
||||
buf := &bytes.Buffer{}
|
||||
err := dstKind.Write(dstObj, buf, resource.KindEncodingJSON)
|
||||
return buf.Bytes(), err
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown target version %s", dstGVK.Version)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unknown source version %s", srcGVK.Version)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/simple"
|
||||
)
|
||||
|
||||
var _ simple.KindMutator = NewMutator()
|
||||
|
||||
type Mutator struct{}
|
||||
|
||||
func NewMutator() *Mutator {
|
||||
return &Mutator{}
|
||||
}
|
||||
|
||||
// Mutate makes modifications to an input object from the API, and returns the changed object.
|
||||
// This mutation will be done on every request, so it can be used to add or update things like labels
|
||||
// or annotations. Here, we add an annotation noting the last resourceVersion this was called for.
|
||||
func (m *Mutator) Mutate(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
|
||||
annotations := req.Object.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations["example.grafana.app/mutated"] = req.Object.GetResourceVersion()
|
||||
req.Object.SetAnnotations(annotations)
|
||||
return &app.MutatingResponse{
|
||||
UpdatedObject: req.Object,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana-app-sdk/operator"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
)
|
||||
|
||||
// ExampleReconciler wraps TypedReconciler to simplify some of our reconciliation logic,
|
||||
// as TypedReconciler will handle type checking of the input object for us.
|
||||
type ExampleReconciler struct {
|
||||
operator.TypedReconciler[*v1alpha1.Example]
|
||||
client resource.Client
|
||||
}
|
||||
|
||||
func NewExampleReconciler(client resource.Client) *ExampleReconciler {
|
||||
reconciler := ExampleReconciler{
|
||||
TypedReconciler: operator.TypedReconciler[*v1alpha1.Example]{},
|
||||
client: client,
|
||||
}
|
||||
reconciler.ReconcileFunc = reconciler.doReconcile
|
||||
return &reconciler
|
||||
}
|
||||
|
||||
// doReconcile is the main reconciliation loop for our app's Example reconciler.
|
||||
// All it does is print a log message and then update the last observed generation in the status
|
||||
// (if the request is a DELETE, it doesn't try to update the status, as the update would fail).
|
||||
func (e *ExampleReconciler) doReconcile(ctx context.Context, req operator.TypedReconcileRequest[*v1alpha1.Example]) (operator.ReconcileResult, error) {
|
||||
if req.Object.GetGeneration() == req.Object.Status.LastObservedGeneration {
|
||||
// Skip if we've already processed this spec
|
||||
return operator.ReconcileResult{}, nil
|
||||
}
|
||||
|
||||
logging.FromContext(ctx).Info("reconciling example", "name", req.Object.GetName(), "namespace", req.Object.GetNamespace(), "action", operator.ResourceActionFromReconcileAction(req.Action))
|
||||
|
||||
// If this is a delete, we don't need to do anything
|
||||
if req.Action == operator.ReconcileActionDeleted {
|
||||
return operator.ReconcileResult{}, nil
|
||||
}
|
||||
|
||||
// Update the status.
|
||||
// We use resource.UpdateObject here to handle conflicts when doing the update,
|
||||
// as it gets the current state, performs our update function, then pushes to the remote
|
||||
_, err := resource.UpdateObject(ctx, e.client, req.Object.GetStaticMetadata().Identifier(), func(obj *v1alpha1.Example, _ bool) (*v1alpha1.Example, error) {
|
||||
obj.Status.LastObservedGeneration = req.Object.GetGeneration()
|
||||
return obj, nil
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
})
|
||||
if err != nil {
|
||||
return operator.ReconcileResult{}, err
|
||||
}
|
||||
|
||||
return operator.ReconcileResult{}, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana/apps/example/pkg/apis/example/v1alpha1"
|
||||
)
|
||||
|
||||
// ExampleGetFooHandler handles requests for the GET /foo subresource route
|
||||
func ExampleGetFooHandler(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
||||
message := "Hello, world!"
|
||||
return json.NewEncoder(writer).Encode(v1alpha1.GetFoo{
|
||||
GetFooBody: v1alpha1.GetFooBody{
|
||||
Message: message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetSomethingHandler handles requests for the GET /something resource route
|
||||
func GetSomethingHandler(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
||||
message := "This is a namespaced route"
|
||||
if request.URL.Query().Has("message") {
|
||||
message = request.URL.Query().Get("message")
|
||||
}
|
||||
return json.NewEncoder(writer).Encode(v1alpha1.GetSomething{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: fmt.Sprintf("%s/%s", v1alpha1.APIGroup, v1alpha1.APIVersion),
|
||||
},
|
||||
GetSomethingBody: v1alpha1.GetSomethingBody{
|
||||
Namespace: request.ResourceIdentifier.Namespace,
|
||||
Message: message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetOtherHandler handles requests for the GET /other cluster-scoped resource route
|
||||
func GetOtherHandler(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error {
|
||||
message := "This is a cluster route"
|
||||
if request.URL.Query().Has("message") {
|
||||
message = request.URL.Query().Get("message")
|
||||
}
|
||||
return json.NewEncoder(writer).Encode(v1alpha1.GetOther{
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/simple"
|
||||
)
|
||||
|
||||
var _ simple.KindValidator = NewValidator()
|
||||
|
||||
// Validator implements simple.KindValidator
|
||||
type Validator struct{}
|
||||
|
||||
func NewValidator() *Validator {
|
||||
return &Validator{}
|
||||
}
|
||||
|
||||
// Validate runs any kind of validation on incoming objects,
|
||||
// and returns an error to reject the request.
|
||||
// Here, we just reject any Example resource which is named "invalid"
|
||||
func (v *Validator) Validate(ctx context.Context, req *app.AdmissionRequest) error {
|
||||
if req.Object.GetName() == "invalid" {
|
||||
return errors.New("example cannot be named 'invalid'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user