Defining a custom resource definition as the source

We'll use Kubebuilder to generate a new Kubernetes Custom Resource Definition for our source. Check out the Kubebuilder book to learn more about Kubebuilder.

Create the Source CRD Skeleton

When in doubt, the Kubebuilder Quick Start docs are likely more current than these instructions.

Use the kubebuilder create api command to create the API definition for a new CRD and a controller to reconcile it.

You'll need to choose the following:

  • A group name. This is the resource group that will contain the resource. It's prepended to the domain name chosen earlier to produce the fully-qualified resource name. The reference project uses sources.

  • A version name. This is the initial version string for the CRD. It's usually v1alpha1 for new resources. The reference project uses v1alpha1.

  • A kind name. This is the unqualified type name of the resource. The reference project uses SampleSource.

    The fully-qualified name of the reference resource is samplesources.sources.knative.dev, and its apiVersion is sources.knative.dev/v1alpha1.

kubebuilder create api --group sources --version v1alpha1 --kind SampleSource

This command will ask if you want to create the resource and controller. Answer yes to both to generate scaffolding to edit in future steps.

The result of this command in the reference project can be viewed at https://github.com/grantr/sample-source/pull/2.

Add Fields to Source Type Definition

Locate the Spec and Status types generated by Kubebuilder. In the reference project, these types are defined in pkg/apis/sources/v1alpha1/samplesource_types.go.

Add a Sink field to the Spec type. Its type should be *corev1.ObjectReference. You'll also need to import the Go package k8s.io/api/core/v1 as corev1.

type SampleSourceSpec struct {
    // Sink is a reference to an object that will resolve to a domain name to use
    // as the sink.
    // +optional
    Sink *corev1.ObjectReference `json:"sink,omitempty"`
}

Add a SinkURI string field to the Status type.

type SampleSourceStatus struct {
    // SinkURI is the current active sink URI that has been configured for the SampleSource.
    // +optional
    SinkURI string `json:"sinkURI,omitempty"`
}

Run dep ensure to add the new library to vendor if necessary.

dep ensure

Run make to regenerate the deepcopy code in zz_generated.deepcopy.go and the CRD's OpenAPI schema.

make

Update type tests

Locate the type tests file. In the reference project, this is samplesource_types_test.go in the same directory as samplesource_types.go.

Edit the created object to include a Spec declaring a Sink.

created := &SampleSource{
  ObjectMeta: metav1.ObjectMeta{
    Name:      "foo",
    Namespace: "default",
  },
  Spec: SampleSourceSpec{
    Sink: &corev1.ObjectReference{
      Name:       "fooservice",
      APIVersion: "v1",
      Kind:       "Service",
    },
  },
}

Edit the updated object, setting SinkURI in its Status.

// Test Updating the Labels and Status
updated := fetched.DeepCopy()
updated.Labels = map[string]string{"hello": "world"}
updated.Status = SampleSourceStatus{
  SinkURI: "http://example.com",
}

Run make to run tests and calculate the coverage percentage for all packages in pkg and cmd.

make
go test ./pkg/... ./cmd/... -coverprofile cover.out
?       github.com/knative/sample-source/pkg/apis   [no test files]
?       github.com/knative/sample-source/pkg/apis/sources   [no test files]
ok      github.com/knative/sample-source/pkg/apis/sources/v1alpha1  8.528s  coverage: 33.3% of statements
?       github.com/knative/sample-source/pkg/controller [no test files]
ok      github.com/knative/sample-source/pkg/controller/samplesource    9.151s  coverage: 67.6% of statements
?       github.com/knative/sample-source/pkg/webhook    [no test files]
?       github.com/knative/sample-source/cmd/manager    [no test files]

These edits in the reference project can be viewed at https://github.com/grantr/sample-source/pull/3.

Enable the Status subresource

The Status subresource allows controllers to update only the Status field without updating the Spec field. Since controllers should not update the Spec of their watched objects, this makes controllers safer and easier to write by ensuring they never update Spec accidentally.

This feature became beta in Kubernetes 1.11, so it might not work properly with older versions.

Add the Kubebuilder status subresource annotation to the struct defining the object. In the reference project, this is in pkg/apis/sources/v1alpha1/samplesource_types.go.

// SampleSource is the Schema for the samplesources API
// +k8s:openapi-gen=true
// +kubebuilder:subresource:status
type SampleSource struct {
  // ...
}

Run make to regenerate the CRD yaml.

make

Update tests to use the Status subresource

Now update the tests to use the Status subresource to set SinkURI. This now needs to be separate from the labels update.

// Test Updating the Labels
updated := fetched.DeepCopy()
updated.Labels = map[string]string{"hello": "world"}
g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred())
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
g.Expect(fetched).To(gomega.Equal(updated))

// Test Updating the Status via subresource
updated.Status = SampleSourceStatus{
    SinkURI: "http://example.com",
}
// DeepCopy is required here because the Status update will set the SelfLink
// to reference to /status subresource instead of the original resource.
statusupdated := updated.DeepCopy()
g.Expect(c.Status().Update(context.TODO(), statusupdated)).NotTo(gomega.HaveOccurred())
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
g.Expect(fetched.Status).To(gomega.Equal(updated.Status))

Run make to verify tests pass.

make

These edits in the reference project can be viewed at https://github.com/grantr/sample-source/pull/4.

Next: Reconcile Sources