Skip to content

Commit f208114

Browse files
jakobmoellerdevsehcamilamacedo86everettraven
committed
📖 refactor external type docs to be up to date
Co-authored-by: Steven E. Harris <seh@panix.com> Co-authored-by: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Co-authored-by: Bryce Palmer <bpalmer@redhat.com>
1 parent 57cfaf8 commit f208114

File tree

2 files changed

+190
-72
lines changed

2 files changed

+190
-72
lines changed

docs/book/src/reference/submodule-layouts.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ YOUR_GO_PATH/test-operator imports
110110
```
111111

112112
The reason for this is that you may have not pushed your modules into the VCS yet and resolving the main module will fail as it can no longer
113-
directly access the API types as package but only as module.
113+
directly access the API types as a package but only as a module.
114114

115115
To solve this issue, we will have to tell the go tooling to properly `replace` the API module with a local reference to your path.
116116

@@ -120,8 +120,8 @@ You can do this with 2 different approaches: go modules and go workspaces.
120120

121121
For go modules, you will edit the main `go.mod` file of your project and issue a replace directive.
122122

123-
You can do this by editing the go.mod with
124-
123+
You can do this by editing the `go.mod` with
124+
``
125125
```shell
126126
go mod edit -require YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0 # Only if you didn't already resolve the module
127127
go mod edit -replace YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0=./api/v1alpha1
@@ -148,7 +148,7 @@ go mod tidy
148148

149149
For go workspaces, you will not edit the `go.mod` files yourself, but rely on the workspace support in go.
150150

151-
To initialize a workspace for your project, run ´go work init` in the project root.
151+
To initialize a workspace for your project, run `go work init` in the project root.
152152

153153
Now let us include both modules in our workspace:
154154
```shell

docs/book/src/reference/using_an_external_type.md

Lines changed: 186 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,95 +6,157 @@ There are several different external types that may be referenced when writing a
66
* CRDs that are created and installed in another project.
77
* A custom API defined via the aggregation layer, served by an extension API server for which the primary API server acts as a proxy.
88

9-
Currently Kubebuilder handles the first twoCRDs and Core Resourcesseamlessly. You must scaffold the latter twoExternal CRDs and APIs created via aggregationmanually.
9+
Currently, kubebuilder handles the first two, CRDs and Core Resources, seamlessly. You must scaffold the latter two, External CRDs and APIs created via aggregation, manually.
1010

1111
In order to use a Kubernetes Custom Resource that has been defined in another project
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
23-
```
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
37-
```
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
3835

39-
In the case above the import path would be `github.com/theiruser/theirproject/apis/theirgroup/v1alpha1`
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.
4037

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.
4140

42-
### Add a controller
41+
### Add a controller for the external Type
42+
43+
Run the command `create api` to scaffold only the controller to manage the external type:
4344

44-
be sure to answer no when it asks if you would like to create an api? [Y/n]
4545
```shell
46-
kubebuilder create api --group mygroup --version $APIVERSION --kind MyKind
46+
kubebuilder create api --group <theirgroup> --version v1alpha1 --kind <ExternalTypeKind> --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+
This will result in a `PROJECT` entry with the default domain of the `PROJECT` (`my.domain` if not specified in `kubebuilder init`).
53+
For use of other domains, such as `theirs.com`, one will have to manually adjust the `PROJECT` file with the correct domain for the entry:
54+
55+
<aside class="note">
56+
If you are looking to create Controllers to manage Kubernetes Core types (i.e. Deployments/Pods)y
57+
you do not need to update the PROJECT file or register the Schema in the manager. All Core Types are registered by default. The Kubebuilder CLI will add the required values to the PROJECT file, but you still need to perform changes to the RBAC markers manually to ensure that the Rules will be generated accordingly.
58+
</aside>
59+
60+
file: PROJECT
61+
```
62+
domain: my.domain
63+
layout:
64+
- go.kubebuilder.io/v4
65+
projectName: testkube
66+
repo: example.com
67+
resources:
68+
- controller: true
69+
domain: my.domain ## <- Replace the domain with theirs.com domain
70+
group: mygroup
71+
kind: ExternalType
72+
version: v1alpha1
73+
version: "3"
4774
```
4875

49-
## Edit the API files.
76+
At the same time, the generated RBAC manifests need to be adjusted:
77+
78+
file: internal/controller/externaltype_controller.go
79+
```go
80+
// ExternalTypeReconciler reconciles a ExternalType object
81+
type ExternalTypeReconciler struct {
82+
client.Client
83+
Scheme *runtime.Scheme
84+
}
85+
86+
// external types can be added like this
87+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes,verbs=get;list;watch;create;update;patch;delete
88+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/status,verbs=get;update;patch
89+
//+kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/finalizers,verbs=update
90+
// core types can be added like this
91+
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
92+
//+kubebuilder:rbac:groups=core,resources=pods/status,verbs=get;list;watch;create;update;patch;delete
93+
//+kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update
94+
```
5095

5196
### Register your Types
5297

53-
Edit the following file to the pkg/apis directory to append their `AddToScheme` to your `AddToSchemes`:
98+
<aside class="note">
99+
Note that this is only valid for external types and not the kubernetes core types.
100+
Core types such as pods or nodes are registered by default in the scheme.
101+
</aside>
102+
103+
Edit the following lines to the main.go file to register the external types:
54104

55-
file: pkg/apis/mytype_addtoscheme.go
105+
file: cmd/main.go
56106
```go
57107
package apis
58108

59109
import (
60-
mygroupv1alpha1 "github.com/myuser/myrepo/apis/mygroup/v1alpha1"
61110
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
62111
)
63112

64113
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-
114+
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
115+
utilruntime.Must(theirgroupv1alpha1.AddToScheme(scheme)) // this contains the external API types
116+
//+kubebuilder:scaffold:scheme
117+
}
74118
```
75119

76-
## Edit the Controller files
120+
## Edit the Controller `SetupWithManager` function
77121

78-
### Use the correct imports for your API
122+
### Use the correct imports for your API and uncomment the controlled resource
79123

80-
file: pkg/controllers/mytype_controller.go
124+
file: internal/controllers/externaltype_controllers.go
81125
```go
82126
package controllers
83127

84128
import (
85-
mygroupv1alpha1 "github.com/myuser/myrepo/apis/mygroup/v1alpha1"
86129
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
87130
)
131+
132+
//...
133+
134+
// SetupWithManager sets up the controller with the Manager.
135+
func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error {
136+
return ctrl.NewControllerManagedBy(mgr).
137+
For(&theirgroupv1alpha1.ExternalType{}).
138+
Complete(r)
139+
}
140+
88141
```
89142

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

145+
file: internal/controllers/externaltype_controllers.go
92146
```go
93147
package controllers
94148
// contains core resources like Deployment
95149
import (
96150
v1 "k8s.io/api/apps/v1"
97151
)
152+
153+
154+
// SetupWithManager sets up the controller with the Manager.
155+
func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error {
156+
return ctrl.NewControllerManagedBy(mgr).
157+
For(&v1.Pod{}).
158+
Complete(r)
159+
}
98160
```
99161

100162
### Update dependencies
@@ -103,47 +165,103 @@ import (
103165
go mod tidy
104166
```
105167

168+
### Generate RBACs with updated Groups and Resources
106169

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.
170+
```
171+
make manifests
172+
```
111173

112174
## Prepare for testing
113175

114-
#### Register your resource in the Scheme
176+
### Register your resource in the Scheme
115177

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

118-
file pkg/controllers/my_kind_controller_suite_test.go
180+
file: internal/controllers/suite_test.go
119181
```go
182+
package controller
183+
184+
import (
185+
"fmt"
186+
"path/filepath"
187+
"runtime"
188+
"testing"
189+
190+
. "github.com/onsi/ginkgo/v2"
191+
. "github.com/onsi/gomega"
192+
193+
"k8s.io/client-go/kubernetes/scheme"
194+
"k8s.io/client-go/rest"
195+
"sigs.k8s.io/controller-runtime/pkg/client"
196+
"sigs.k8s.io/controller-runtime/pkg/envtest"
197+
logf "sigs.k8s.io/controller-runtime/pkg/log"
198+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
199+
//+kubebuilder:scaffold:imports
200+
theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1"
201+
)
202+
120203
var cfg *rest.Config
204+
var k8sClient client.Client
205+
var testEnv *envtest.Environment
121206

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-
}
207+
func TestControllers(t *testing.T) {
208+
RegisterFailHandler(Fail)
132209

133-
apis.AddToScheme(scheme.Scheme)
210+
RunSpecs(t, "Controller Suite")
211+
}
134212

135-
var err error
136-
if cfg, err = t.Start(); err != nil {
137-
log.Fatal(err)
213+
214+
var _ = BeforeSuite(func() {
215+
//...
216+
By("bootstrapping test environment")
217+
testEnv = &envtest.Environment{
218+
CRDDirectoryPaths: []string{
219+
// if you are using vendoring and rely on a kubebuilder based project, you can simply rely on the vendored config directory
220+
filepath.Join("..", "..", "..", "vendor", "github.com", "theiruser", "theirproject", "config", "crds"),
221+
// otherwise you can simply download the CRD from any source and place it within the config/crd/bases directory,
222+
filepath.Join("..", "..", "config", "crd", "bases"),
223+
},
224+
ErrorIfCRDPathMissing: false,
225+
226+
// The BinaryAssetsDirectory is only required if you want to run the tests directly
227+
// without call the makefile target test. If not informed it will look for the
228+
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
229+
// Note that you must have the required binaries setup under the bin directory to perform
230+
// the tests directly. When we run make test it will be setup and used automatically.
231+
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
232+
fmt.Sprintf("1.28.3-%s-%s", runtime.GOOS, runtime.GOARCH)),
138233
}
234+
235+
var err error
236+
// cfg is defined in this file globally.
237+
cfg, err = testEnv.Start()
238+
Expect(err).NotTo(HaveOccurred())
239+
Expect(cfg).NotTo(BeNil())
139240

140-
code := m.Run()
141-
t.Stop()
142-
os.Exit(code)
143-
}
241+
//+kubebuilder:scaffold:scheme
242+
Expect(theirgroupv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed())
243+
244+
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
245+
Expect(err).NotTo(HaveOccurred())
246+
Expect(k8sClient).NotTo(BeNil())
247+
248+
249+
})
144250

145251
```
146252

253+
### Verifying API Availability in the Cluster
254+
255+
Since we are now using external types, you will now have to rely on them being installed into the cluster.
256+
If the APIs are not available at the time the manager starts, all informers listening to the non-available types
257+
will fail, causing the manager to exit with an error similar to
258+
259+
```
260+
failed to get informer from cache {"error": "Timeout: failed waiting for *v1alpha1.ExternalType Informer to sync"}
261+
```
262+
263+
This will signal that the API Server is not yet ready to serve the external types.
264+
147265
## Helpful Tips
148266

149267
### Locate your domain and group variables
@@ -152,6 +270,6 @@ The following kubectl commands may be useful
152270

153271
```shell
154272
kubectl api-resources --verbs=list -o name
155-
kubectl api-resources --verbs=list -o name | grep mydomain.com
273+
kubectl api-resources --verbs=list -o name | grep my.domain
156274
```
157275

0 commit comments

Comments
 (0)