-
Notifications
You must be signed in to change notification settings - Fork 63
Adding a New Resource
For the below steps, rbac.authorization.k8s.io/roles
will be used as the example.
Before any other step, you should add the following to generate helpful functions for use in the webhook. In pkg/codegen/main.go we need to add our new resource to generateObjectsFromRequest
. This function takes a map[string]args.Group
. The key is the resource group, and the values are slices of the objects to generate functions for.
...
"rbac.authorization.k8s.io": {
Types: []interface{}{
&rbacv1.Role{},
&rbacv1.RoleBinding{},
},
...
This will generate two useful functions <Resource>OldAndNewFromRequest
and <Resource>FromRequest
which are helper functions that return <Resource>
objects from the raw JSON found in the Request. (Role example)
Note: If we will need a controller for the resource, we also need to run the controller generation. Add the resource to controllergen.Run
under Groups
in the same way.
controllergen.Run(args.Options{
...
Groups: map[string]args.Group{
"management.cattle.io": {
Types: []interface{}{
v3.Cluster{},
...
This generates the controller that we can use to perform actions on those resources. (GlobalRole example)
To generate files, simply run go generate ./...
at the root of the repo.
The next step is to create a webhook that performs the validation or mutation we want. These are added in a new directory under pkg/resources/<resource group>/<API version>/<resource>
. So for Roles, it can be found in pkg/resources/rbac.authorization.k8s.io/v1/role
. Validators go in validator.go
and Mutators go in mutator.go
.
To add a new Webhook that we need to implement a WebhookHandler
, more specifically, either a ValidatingAdmissionHandler
or a MutatingAdmissionHandler
.
While the function NewValidator
and NewMutator
are not necessary for the implementation of either ValidatingAdmissionHandler
or MutatingAdmissionHandler
, it is convention to add it to be used in handlers.go
as discussed below in "Handler". For Roles, the NewValidator()
has no inputs. It would be just as valid to create a Validator directly &role.Validator{}
. However, when the validator needs access to controllers, resolvers, etc. passing it via the NewValidator
function is used. For example, GlobalRoles has the following NewValidator
:
// NewValidator returns a new validator used for validation globalRoles.
func NewValidator(ruleResolver validation.AuthorizationRuleResolver, grbResolver *resolvers.GRBClusterRuleResolver,
sar authorizationv1.SubjectAccessReviewInterface) *Validator {
return &Validator{
admitter: admitter{
resolver: ruleResolver,
grbResolver: grbResolver,
sar: sar,
},
}
}
When implementing the function ValidatingWebhook
, it is possible to add LabelSelector
s to the returned webhook.ObjectSelector
. This informs the webhook that when validating an object, only validate objects that have the given label. This is extremely valuable when adding validation to a frequently modified object (ie core types) to reduce the performance impact. Every additional webhook introduces a potential bottleneck, so strive to limit the objects validated.
For Roles, we select only Roles that have the label authz.management.cattle.io/gr-owner
(link):
webhook.ObjectSelector = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: grOwnerLabel,
Operator: metav1.LabelSelectorOpExists,
},
},
}
More information on Label Selectors
This function is where the bulk of the work happens. This passes in a Request
and returns a Response
and an error
. Any validating logic falls into this function and returns return admission.ResponseAllowed(), nil
if the requested change is allowed. Any other return, such as return nil, err
or return admission.ResponseBadRequest(), nil
denies the request and prevents the caller from modifying the resource.
Each resource's webhook should have a reasonable, preferably total, unit test coverage.
For each resource's webhook there should be at least a minimal integration test residing in tests/integration
.
To run integration tests
export KUBECONFIG=<cluster to test on>
go test ./tests/integration -run IntegrationTest
For every resource, documentation must be added describing what validation and mutation occurs for the resource. This is done in a markdown file (eg Role.md
). Please make sure to detail every check being made.
Once you've added all the information related to validation and mutation, run go generate ./...
at the root of the repo. This will add your newly created docs to docs.md
, which is the complete collection of all validation and mutation that occurs in rancher/webhook
.
The final step of adding a new resource webhook is to add the Validator to the list of handlers. In pkg/server/handlers.go
, create the validator roles := role.NewValidator()
and append it to the list of handlers.
In pkg/server/handlers.go
, you'll notice that many of the handlers are only added if clients.MultiClusterManagement
is true
. These are webhooks that only get added to the local cluster, and not any downstream clusters. This is almost always where new webhooks get added. Unless there is a very urgent use case, NEVER make webhooks on downstream clusters. This could have potentially enormous performance ramifications. Only do so when absolutely necessary (ie security reasons). If you're unsure, either default to adding it to only local (inside if clients.MultiClusterManagement
) or ask someone more familiar with the webhook.