View Source CRD Versions
Mix.install([:bonny])
Application.put_env(:bonny, :operator_name, "livebook-operator")
CRD Definition and Versions
When defining your operator (use Bonny.Operator
), you have to implement the callback crds/0
where you define your custom resources. A custom resource definition (CRD) is represented by a %Bonny.API.CRD{}
struct which defines 4 fields:
:scope
- either:Namespaced
or:Cluster
:group
- The API group of your controller / this resource:names
- A map with 3-4 keys defining the names of this resource.plural
: name to be used in the URL:/apis/<group>/<version>/<plural>
- e.g. crontabssingular
: singular name to be used as an alias on the CLI and for display - e.g. crontabkind
: is normally the CamelCased singular type. Your resource manifests use this. - e.g. CronTabshortnames
: allow shorter string to match your resource on the CLI - e.g. [ct]
versions
: list of API Version modules for this Resource, defaults to the versions in config.exs
defmodule MyOperator.Operator do
use Bonny.Operator, default_watch_namespace: "default"
step(:delegate_to_controller)
def controllers(_watching_namespace, _opts), do: []
def crds() do
[
%Bonny.API.CRD{
names: %{kind: "CronTab", plural: "crontabs", shortNames: ["ct"], singular: "crontab"},
group: "example.com",
versions: [MyOperator.API.V1.CronTab],
scope: :Namespaces
}
]
end
end
We're going to look at version manifest declaration in more detail in just a moment. For now, let's just define a simple API version v1
for the CronTab
custom resource with just defaults for all the fields. You do this by defining a module that starts with the API declared in the application configuration (YourOperator.API.V1
), followed by the CRD name (CronTab
). The module must use Bonny.API.Version
which expects you to implement manifest/0
.
defmodule MyOperator.API.V1.CronTab do
use Bonny.API.Version
@impl Bonny.API.Version
def manifest() do
defaults()
|> struct!(name: "v1", storage: true)
end
end
YourOperator.API.V1.CronTab.manifest()
Now, if we define a CronTabController
, Bonny finds this version and add it to the CRD manifest.
crds =
[MyOperator.Operator]
|> Bonny.Mix.Operator.crds()
|> Ymlr.documents!()
IO.puts(crds)
Version Manifest Declaration
Our V1.CronTab
module called the defaults/0
macro from manifest/0
. This macro helps initializing a generic version with no schema, subresources or additional printer columns. The storage
flag is set to false (see Multi-Version APIs further down). For the other flags :served
and :deprecated
such as the field :deprecatedWarning
, assumptions are made.
An operator run in production might want to define at least a :schema
, probably :additionalPrinterColumns
and maybe :subresources
. All these fields such as flags can be overriden in manifest/0
.
defmodule YourOperator.API.V1Alpha1.Widget do
use Bonny.API.Version
@impl true
def manifest() do
struct!(
defaults(),
storage: true,
schema: %{
openAPIV3Schema: %{
type: :object,
properties: %{
spec: %{
type: :object,
properties: %{
foos_requested: %{type: :integer}
}
},
status: %{
type: :object,
properties: %{
foos_implemented: %{type: :integer}
}
}
}
}
},
additionalPrinterColumns: [
%{
name: "requested_foos",
type: :integer,
description: "Number of foos requested",
jsonPath: ".spec.foos_requested"
},
%{
name: "implemented_foos",
type: :integer,
description: "Number of foos implemented",
jsonPath: ".status.foos_implemented"
}
],
subresources: %{
status: %{}
}
)
end
end
YourOperator.API.V1Alpha1.Widget.manifest()
The Status Subresource
In the controllers guide we explain the importance of using of the status subresource. In order to use the status subresource, it has to be enabled and its schema has to be defined in the CRD. You can see an example in the code snipped above in the YourOperator.API.V1Alpha1.Widget
.
Two special use cases of the status subresource are skipping obeserved generations and conditions. In order to use these concepts with custom resources, they have to be configured on the CRD. When use
ing Bonny.API.Version
, helper functions add_observed_generation_status/1
and add_conditions/1
are imported to your module. Use them to add the required fields to your manifest:
defmodule YourOperator.API.V1Alpha1.Feature do
use Bonny.API.Version
@impl true
def manifest() do
defaults()
|> struct!(
schema:
%{
# your openAPIV3schema
}
)
# initialize the usage of the observed generations pluggable step
|> add_observed_generation_status()
# initialize the usage of conditions.
|> add_conditions()
end
end
YourOperator.API.V1Alpha1.Widget.manifest()
Multi-Version APIs
There is some documentation about multi-version apis for the kubebuilder. Obviousely, that one is for creating a kubernetes controller in Go, but it's a good read nontheless. This is how it begins:
Most projects start out with an alpha API that changes release to release. However, eventually, most projects will need to move to a more stable API. Once your API is stable though, you can't make breaking changes to it. That's where API versions come into play.
Conversion
Webhooks are currently not implemented in Bonny. There is the module bonny_plug
that can be used to implement them. There might be a neater integration of the two in the future, though.
Bonny already lets you define a version as the hub
. The only thing this does right now is it sets the storage
flag to true
in the generated manifest.
defmodule YourOperator.API.V1.CronTab do
use Bonny.API.Version,
hub: true
@impl Bonny.API.Version
# storage: true not needed here.
def manifest(), do: defaults(name: "v1")
end
YourOperator.API.V1.CronTab.manifest()
Storage Versions
Even if you define multiple versions for the same resource, Kubernetes is only going to store the data in one version - the storage version.
Note that multiple versions may exist in storage if they were written before the storage version changes -- changing the storage version only affects how objects are created/updated after the change.
As mentioned above, there are to ways to define the storage version, by passing hub: true
as an option to use Bonny.API.Version
or by setting storage: true
in manifest/0
.