Resources live under pkg/apis/<group>/<version>/<resource>_types.go
.
It is recommended to use apiserver-boot
to create new groups,
versions, and resources.
Provide your domain + the api group and version + the resource Kind. The resource name will be the pluralized lowercased kind.
apiserver-boot create group version resource --group <group> --version <version> --kind <Kind>
A resource has a go struct which defines the Kind schema, and is annotated with comment directives used by the code generator to wire the storage and REST endpoints.
Example:
// +genclient=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +resource:path=foos
// +k8s:openapi-gen=true
// Foo defines some thing
type Foo struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// spec defines the desired state of Foo
Spec FooSpec `json:"spec,omitempty"`
// status records the observed state of Foo
Status FooStatus `json:"status,omitempty"`
}
// FooSpec defines the desired state of Foo
type FooSpec struct {
// some_spec_field defines some desired state about Foo
SomeSpecField int `json:"some_spec_field,omitempty"`
}
// FooStatus records the observed state of Foo
type FooStatus struct {
// some_status_field records some observed state about Foo
SomeStatusField int `json:"some_status_field,omitempty"`
}
// +resource:path=foos
This tells the code generator to generate the REST storage endpoints for this resource.
// +k8s:openapi-gen=true
This tells the code generator to include this resource in the openapi spec published by the apiserver
// Foo defines some thing
This will appear in the openapi spec and the generated reference docs as the description of the resource.
type Foo struct {...}
This block defines the resource schema
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
These define metadata common to most resources - such as the name, group/version/kind, annotations, and labels.
// spec defines the desired state of Foo
Spec FooSpec `json:"spec,omitempty"`
This field defines the desired state of Foo that the controller loops will work towards.
// status records the observed state of Foo
Status FooStatus `json:"status,omitempty"`
This field records the state of Foo observed by the controller loops for clients to read.
// FooSpec defines the desired state of Foo
type FooSpec struct {
// some_spec_field defines some desired state about Foo
SomeSpecField int `json:"some_spec_field,omitempty"`
}
// FooStatus records the observed state of Foo
type FooStatus struct {
// some_status_field records some observed state about Foo
SomeStatusField int `json:"some_status_field,omitempty"`
}
These structures define the schema for the desired and observed state.
By default, a controller for your resource will also be created under
pkg/controller/<kind>/controller.go
. This will listen for creates
or updates to your resource and execute code in response. You can modify
the code to also listen for changes to other resource types that your
kind manages.
// +controller:group=bar,version=v1beta1,kind=Foo,resource=foos
type FooControllerImpl struct {
// informer listens for events about Foos
informer cache.SharedIndexInformer
// lister indexes properties about Foos
ulister listers.FooLister
}
// Init initializes the controller and is called by the generated code
// config - client configuration for talking to the apiserver
// si - informer factory shared across all controllers for listening to events and indexing resource properties
// queue - message queue for handling new events. unique to this controller.
func (c *FooControllerImpl) Init(
config *rest.Config,
si *sharedinformers.SharedInformers,
queue workqueue.RateLimitingInterface) {
// Get the informer and lister for subscribing to foo events and querying foos from
// the lister cache
i := si.Factory.Bar().V1beta1().Foo()
c.informer = i.Informer()
c.lister = i.Lister()
// Add an event handler to enqueue a message for foo adds / updates
c.informer.AddEventHandler(&controller.QueueingEventHandler{queue})
}
// Reconcile handles enqueued messages
func (c *UniversityControllerImpl) Reconcile(u *v1beta1.Foo) error {
// Put your event handling code here
fmt.Printf("Running reconcile Foo for %s\n", u.Name)
return nil
}
func (c *FooControllerImpl) Get(namespace, name string) (*v1beta1.Foo, error) {
return c.lister.Foos(namespace).Get(name)
}
// +controller:group=bar,version=v1beta1,kind=Foo,resource=foos
type FooControllerImpl struct {
// informer listens for events about Foos
informer cache.SharedIndexInformer
// lister indexes properties about Foos
ulister listers.FooLister
}
This declares a new controller that responds to events on Foo resources
// Init initializes the controller and is called by the generated code
// config - client configuration for talking to the apiserver
// si - informer factory shared across all controllers for listening to events and indexing resource properties
// queue - message queue for handling new events. unique to this controller.
func (c *FooControllerImpl) Init(
config *rest.Config,
si *sharedinformers.SharedInformers,
queue workqueue.RateLimitingInterface) {
// Get the informer and lister for subscribing to foo events and querying foos from
// the lister cache
i := si.Factory.Bar().V1beta1().Foo()
c.informer = i.Informer()
c.lister = i.Lister()
// Add an event handler to enqueue a message for foo adds / updates
c.informer.AddEventHandler(&controller.QueueingEventHandler{queue})
}
This registers a new EventHandler for Add and Update events to Foo resources and queues messages in response.
// Reconcile handles enqueued messages
func (c *UniversityControllerImpl) Reconcile(u *v1beta1.Foo) error {
// Put your event handling code here
fmt.Printf("Running reconcile Foo for %s\n", u.Name)
return nil
}
This function is called when messages are dequeued. It should read the actual state and reconcile it with the desired state.
func (c *FooControllerImpl) Get(namespace, name string) (*v1beta1.Foo, error) {
return c.lister.Foos(namespace).Get(name)
}
This function looks up a Foo object for a namespace + name. It is executed just before the Reconcile method to lookup the Foo object.
To generate the REST endpoint and storage wiring for your resource,
run apiserver-boot build generated
from the go package root directory.
This will also generate go client code to read and write your resources under pkg/client
.