Skip to content

Commit b16cc63

Browse files
jakobmoellerdevsehcamilamacedo86
committed
📖 refactor external type docs to be up to date
Co-authored-by: Steven E. Harris <[email protected]> Co-authored-by: Camila Macedo <[email protected]>
1 parent 57cfaf8 commit b16cc63

File tree

1 file changed

+206
-66
lines changed

1 file changed

+206
-66
lines changed

docs/book/src/reference/using_an_external_type.md

Lines changed: 206 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,89 +12,173 @@ In order to use a Kubernetes Custom Resource that has been defined in another pr
1212
you will need to have several items of information.
1313
* The Domain of the CR
1414
* The Group under the Domain
15-
* The Go import path of the CR Type definition.
15+
* The Go import path of the CR Type definition
16+
* The Custom Resource Type you want to depend on.
1617

1718
The Domain and Group variables have been discussed in other parts of the documentation. The import path would be located in the project that installs the CR.
18-
19+
The Custom Resource Type is usually a Go Type of the same name as the CustomResourceDefinition in kubernetes, e.g. for a `Pod` there will be a type `Pod` in the `v1` group.
20+
For Kubernetes Core Types, the domain can be omitted.
21+
``
1922
This document uses `my` and `their` prefixes as a naming convention for repos, groups, and types to clearly distinguish between your own project and the external one you are referencing.
20-
Note that by default, multigroup APIs are no longer included. To enable them again, see [the guide on multigroup API migration](https://book.kubebuilder.io/migration/multi-group.html).
2123

22-
Example external API Aggregation directory structure
24+
In our example we will assume the following external API Type:
25+
26+
`github.com/theiruser/theirproject` is another kubebuilder project on whose CRD we want to depend and extend on.
27+
Thus, it contains a `go.mod` in its repository root. The import path for the go types would be `github.com/theiruser/theirproject/api/theirgroup/v1alpha1`.
28+
29+
The Domain of the CR is `theirs.com`, the Group is `theirgroup` and the kind and go type would be `ExternalType`.
30+
31+
If there is an interest to have multiple Controllers running in different Groups (e.g. because one is an owned CRD and one is an external Type), please first
32+
reconfigure the Project to use a multi-group layout as described in the [Multi-Group documentation](../migration/multi-group.md).
33+
34+
### Prerequisites
35+
36+
The following guide assumes that you have already created a project using `kubebuilder init` in a directory in the GOPATH. Please reference the [Getting Started Guide](../getting-started.md) for more information.
37+
38+
Note that if you did not pass `--domain` to `kubebuilder init` you will need to modify it for the individual api types as the default is `my.domain`, not `theirs.com`.
39+
Similarly, if you intend to use your own domain, please configure your own domain with `kubebuilder init` and do not use `theirs.com for the domain.
40+
41+
### Add a controller for the external Type
42+
43+
We will now add a controller for the given type
44+
45+
```shell
46+
kubebuilder create api --group theirgroup --version v1alpha1 --kind ExternalType --controller --resource=false
47+
```
48+
49+
Note that the `resource` argument is set to false, as we are not attempting to create our own CustomResourceDefinition,
50+
but instead rely on an external one.
51+
52+
### Adjusting the domain for an external type
53+
54+
In case of a domain mismatch of your project domain with the domain of the external type, you will need to adjust the domain for the external type:
55+
56+
#### Using a type not included in the Kubernetes Core Resources
57+
58+
file: PROJECT
2359
```
24-
github.com
25-
├── theiruser
26-
├── theirproject
27-
├── apis
28-
├── theirgroup
29-
├── doc.go`
30-
├── install
31-
│   ├── install.go
32-
├── v1alpha1
33-
│   ├── doc.go
34-
│   ├── register.go
35-
│   ├── types.go
36-
│   ├── zz_generated.deepcopy.go
60+
domain: my.domain
61+
layout:
62+
- go.kubebuilder.io/v4
63+
projectName: testkube
64+
repo: github.com/jakobmoellerdev/testkube
65+
resources:
66+
- controller: true
67+
domain: theirs.com
68+
group: mygroup
69+
kind: ExternalType
70+
version: v1alpha1
71+
version: "3"
3772
```
3873

39-
In the case above the import path would be `github.com/theiruser/theirproject/apis/theirgroup/v1alpha1`
74+
Adjusting the generated RBAC manifests:
4075

76+
file: internal/controller/externaltype_controller.go
77+
```go
78+
// ExternalTypeReconciler reconciles a ExternalType object
79+
type ExternalTypeReconciler struct {
80+
client.Client
81+
Scheme *runtime.Scheme
82+
}
4183

42-
### Add a controller
84+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes,verbs=get;list;watch;create;update;patch;delete
85+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/status,verbs=get;update;patch
86+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/finalizers,verbs=update
4387

44-
be sure to answer no when it asks if you would like to create an api? [Y/n]
45-
```shell
46-
kubebuilder create api --group mygroup --version $APIVERSION --kind MyKind
4788
```
4889

49-
## Edit the API files.
90+
#### Using a type included in the Kubernetes Core Resources
91+
92+
file: PROJECT
93+
```
94+
domain: my.domain
95+
layout:
96+
- go.kubebuilder.io/v4
97+
projectName: testkube
98+
repo: github.com/jakobmoellerdev/testkube
99+
resources:
100+
- controller: true
101+
domain: ""
102+
kind: Pod
103+
version: v1alpha1
104+
version: "3"
105+
```
106+
107+
Adjusting the generated RBAC manifests:
108+
109+
file: internal/controller/externaltype_controller.go
110+
```go
111+
// PodReconciler reconciles a Pod object
112+
type ExternalTypeReconciler struct {
113+
client.Client
114+
Scheme *runtime.Scheme
115+
}
116+
117+
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
118+
//+kubebuilder:rbac:groups=core,resources=pods/status,verbs=get;update;patch
119+
//+kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update
120+
121+
```
50122

51123
### Register your Types
52124

53-
Edit the following file to the pkg/apis directory to append their `AddToScheme` to your `AddToSchemes`:
125+
Edit the following lines to the main.go file to register the external types:
54126

55-
file: pkg/apis/mytype_addtoscheme.go
127+
file: cmd/main.go
56128
```go
57129
package apis
58130

59131
import (
60-
mygroupv1alpha1 "github.com/myuser/myrepo/apis/mygroup/v1alpha1"
61132
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
62133
)
63134

64135
func init() {
65-
// Register the types with the Scheme so the components can map objects
66-
// to GroupVersionKinds and back
67-
AddToSchemes = append(
68-
AddToSchemes,
69-
mygroupv1alpha1.SchemeBuilder.AddToScheme,
70-
theirgroupv1alpha1.SchemeBuilder.AddToScheme,
71-
)
72-
}
73-
136+
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
137+
utilruntime.Must(theirgroupv1alpha1.AddToScheme(scheme)) // this contains the external API types
138+
//+kubebuilder:scaffold:scheme
139+
}
74140
```
75141

76-
## Edit the Controller files
142+
## Edit the Controller `SetupWithManager function
77143

78-
### Use the correct imports for your API
144+
### Use the correct imports for your API and uncomment the controlled resource
79145

80-
file: pkg/controllers/mytype_controller.go
146+
file: internal/controllers/externaltype_controllers.go
81147
```go
82148
package controllers
83149

84150
import (
85-
mygroupv1alpha1 "github.com/myuser/myrepo/apis/mygroup/v1alpha1"
86151
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
87152
)
153+
154+
//...
155+
156+
// SetupWithManager sets up the controller with the Manager.
157+
func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error {
158+
return ctrl.NewControllerManagedBy(mgr).
159+
For(&theirgroupv1alpha1.ExternalType{}).
160+
Complete(r)
161+
}
162+
88163
```
89164

90-
Note that core resources may simply be imported by depending on the API's from upstream Kubernetes:
165+
Note that core resources may simply be imported by depending on the API's from upstream Kubernetes and do not need additional `AddToScheme` registrations:
91166

167+
file: internal/controllers/externaltype_controllers.go
92168
```go
93169
package controllers
94170
// contains core resources like Deployment
95171
import (
96172
v1 "k8s.io/api/apps/v1"
97173
)
174+
175+
176+
// SetupWithManager sets up the controller with the Manager.
177+
func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error {
178+
return ctrl.NewControllerManagedBy(mgr).
179+
For(&v1.Pod{}).
180+
Complete(r)
181+
}
98182
```
99183

100184
### Update dependencies
@@ -103,47 +187,103 @@ import (
103187
go mod tidy
104188
```
105189

190+
### Generate RBACs with updated Groups and Resources
106191

107-
### Verifying API Availability in the Cluster
108-
109-
Since we are now using external types, it is best-practice to verify the existance of the API in the cluster.
110-
You can use the manager's client to verify API Existance before starting the controllers through the manager.
192+
```
193+
make manifests
194+
```
111195

112196
## Prepare for testing
113197

114-
#### Register your resource in the Scheme
198+
### Register your resource in the Scheme
115199

116-
Edit the `CRDDirectoryPaths` in your test suite by appending the path to their CRDs:
200+
Edit the `CRDDirectoryPaths` in your test suite and add the correct `AddToScheme` entry during suite initialization:
117201

118-
file pkg/controllers/my_kind_controller_suite_test.go
202+
file: internal/controllers/suite_test.go
119203
```go
204+
package controller
205+
206+
import (
207+
"fmt"
208+
"path/filepath"
209+
"runtime"
210+
"testing"
211+
212+
. "github.com/onsi/ginkgo/v2"
213+
. "github.com/onsi/gomega"
214+
215+
"k8s.io/client-go/kubernetes/scheme"
216+
"k8s.io/client-go/rest"
217+
"sigs.k8s.io/controller-runtime/pkg/client"
218+
"sigs.k8s.io/controller-runtime/pkg/envtest"
219+
logf "sigs.k8s.io/controller-runtime/pkg/log"
220+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
221+
//+kubebuilder:scaffold:imports
222+
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
223+
)
224+
120225
var cfg *rest.Config
226+
var k8sClient client.Client
227+
var testEnv *envtest.Environment
121228

122-
func TestMain(m *testing.M) {
123-
// Get a config to talk to the apiserver
124-
t := &envtest.Environment{
125-
Config: cfg,
126-
CRDDirectoryPaths: []string{
127-
filepath.Join("..", "..", "..", "config", "crds"),
128-
filepath.Join("..", "..", "..", "vendor", "github.com", "theiruser", "theirproject", "config", "crds"),
129-
},
130-
UseExistingCluster: true,
131-
}
229+
func TestControllers(t *testing.T) {
230+
RegisterFailHandler(Fail)
132231

133-
apis.AddToScheme(scheme.Scheme)
232+
RunSpecs(t, "Controller Suite")
233+
}
134234

135-
var err error
136-
if cfg, err = t.Start(); err != nil {
137-
log.Fatal(err)
235+
236+
var _ = BeforeSuite(func() {
237+
//...
238+
By("bootstrapping test environment")
239+
testEnv = &envtest.Environment{
240+
CRDDirectoryPaths: []string{
241+
// if you are using vendoring and rely on a kubebuilder based project, you can simply rely on the vendored config directory
242+
filepath.Join("..", "..", "..", "vendor", "github.com", "theiruser", "theirproject", "config", "crds"),
243+
// otherwise you can simply download the CRD from any source and place it within the config/crd/bases directory,
244+
filepath.Join("..", "..", "config", "crd", "bases"),
245+
},
246+
ErrorIfCRDPathMissing: false,
247+
248+
// The BinaryAssetsDirectory is only required if you want to run the tests directly
249+
// without call the makefile target test. If not informed it will look for the
250+
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
251+
// Note that you must have the required binaries setup under the bin directory to perform
252+
// the tests directly. When we run make test it will be setup and used automatically.
253+
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
254+
fmt.Sprintf("1.28.3-%s-%s", runtime.GOOS, runtime.GOARCH)),
138255
}
256+
257+
var err error
258+
// cfg is defined in this file globally.
259+
cfg, err = testEnv.Start()
260+
Expect(err).NotTo(HaveOccurred())
261+
Expect(cfg).NotTo(BeNil())
139262

140-
code := m.Run()
141-
t.Stop()
142-
os.Exit(code)
143-
}
263+
//+kubebuilder:scaffold:scheme
264+
Expect(theirgroupv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
265+
266+
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
267+
Expect(err).NotTo(HaveOccurred())
268+
Expect(k8sClient).NotTo(BeNil())
269+
270+
271+
})
144272

145273
```
146274

275+
### Verifying API Availability in the Cluster
276+
277+
Since we are now using external types, you will now have to rely on them being installed into the cluster.
278+
If the APIs are not available at the time the manager starts, all informers listening to the non-available types
279+
will fail, causing the manager to exit with an error similar to
280+
281+
```
282+
failed to get informer from cache {"error": "Timeout: failed waiting for *v1alpha1.ExternalType Informer to sync"}
283+
```
284+
285+
This will signal that the API Server is not yet ready to serve the external types.
286+
147287
## Helpful Tips
148288

149289
### Locate your domain and group variables
@@ -152,6 +292,6 @@ The following kubectl commands may be useful
152292

153293
```shell
154294
kubectl api-resources --verbs=list -o name
155-
kubectl api-resources --verbs=list -o name | grep mydomain.com
295+
kubectl api-resources --verbs=list -o name | grep my.domain
156296
```
157297

0 commit comments

Comments
 (0)