Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add main docs #53

Merged
merged 9 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ implementing [admission controllers](https://kubernetes.io/docs/reference/access
and [conversion hooks](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion)
for Kubernetes in Java. Supports both **quarkus** and **spring boot**. Both **sync** and **async** programing model.

## Documentation

**For a more detailed documentation check the [docs](docs).**

## Sample Usage

### Admission Controllers
Expand All @@ -15,7 +19,7 @@ Defining a mutation or validation controller is simple as:

@Singleton
@Named(MUTATING_CONTROLLER)
public AdmissionController<Pod> mutatingController() {
public AdmissionController<Ingress> mutatingController() {
return new AdmissionController<>((resource, operation) -> {
if (resource.getMetadata().getLabels() == null) {
resource.getMetadata().setLabels(new HashMap<>());
Expand All @@ -27,7 +31,7 @@ Defining a mutation or validation controller is simple as:

@Singleton
@Named(VALIDATING_CONTROLLER)
public AdmissionController<Pod> validatingController() {
public AdmissionController<Ingress> validatingController() {
return new AdmissionController<>((resource, operation) -> {
if (resource.getMetadata().getLabels() == null
|| resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
import io.javaoperatorsdk.webhook.admission.Operation;

/**
* Any change made on the resource will be reflected in the response.
*
* @param <T> type of Kubernetes resources
*/
public interface Mutator<T extends KubernetesResource> {

T mutate(T resource, Operation operation) throws NotAllowedException;
Expand Down
156 changes: 156 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Kubernetes Webhooks Framework Documentation

## Intro

Kubernetes Webhooks Framework makes it simple to
implementing [admission controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)
and [conversion hooks](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion)
for Kubernetes in Java.

## Getting Started

Before you start make sure you understand these concepts in Kubernetes, reading the docs mentioned above.

### Samples

There are samples both
for [Spring Boot](https://github.com/java-operator-sdk/admission-controller-framework/tree/main/samples/spring-boot)
and [Quarkus](https://github.com/java-operator-sdk/kubernetes-webooks-framework/tree/main/samples/quarkus), both of them
implements the same logic. Both sync and async APIs
are showcased. This documentation describes the Quarkus version, however Spring Boot version is almost identical.

There are two endpoint, one
for [admission controllers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/admission/AdmissionEndpoint.java)
(a validating and a mutating) and one for the
sample [conversion hook](https://github.com/java-operator-sdk/admission-controller-framework/blob/76fd9c4b9fef6738310a7dd97b159c3668ced9f1/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/conversion/ConversionEndpoint.java)
.

Starting from those endpoints, it should be trivial to understand how the underlying logic works.

### End-To-End Tests

The goal of the end-to-end tests is to test the framework in a production like environment, but also works as an
executable documentation to guide developers how to deploy and configure the target service.

The [end-to-end tests](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java)
are using the [same test cases](https://github.com/java-operator-sdk/admission-controller-framework/blob/de2b0da7f592aa166049ef7ad65bcebf8d45e358/samples/commons/src/test/java/io/javaoperatorsdk/webhook/sample/EndToEndTestBase.java) and are based on the samples (See Spring Boot
version [here](https://github.com/java-operator-sdk/admission-controller-framework/blob/e2637a90152bebfca2983ba17268c1f7ec7e9602/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/SpringBootWebhooksE2E.java)).
To see how those tests are executed during a pull request check
the [related GitHub Action](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/.github/workflows/pr.yml#L66-L66)

The samples are first built, then [deployed](https://github.com/java-operator-sdk/admission-controller-framework/blob/6959de06c0de1c8e04fc241ea5f4196219002e53/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java#L23-L30) to a local Kubernetes cluster (in our case minikube is used).
For Quarkus most of the deployment artifacts is generated using extensions (works similarly for Spring Boot,
using [dekorate](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/spring-boot/pom.xml#L52-L63)):

```xml

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.certmanager</groupId>
<artifactId>quarkus-certmanager</artifactId>
</dependency>
```

Only additional resources used for admission hooks, are present in
the [k8s](https://github.com/java-operator-sdk/admission-controller-framework/tree/main/samples/quarkus/k8s)
directory. These are the configuration files to configure the admission hooks. For example the configuration for
validation look like:

```yaml

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "validating.quarkus.example.com"
annotations:
# Cert Manager annotation to inject CA
cert-manager.io/inject-ca-from: default/quarkus-sample
webhooks:
- name: "validating.quarkus.example.com"
rules:
- apiGroups: [ "networking.k8s.io" ]
apiVersions: [ "v1" ]
operations: [ "*" ]
resources: [ "ingresses" ]
scope: "Namespaced"
clientConfig:
service:
namespace: "default"
name: "quarkus-sample"
path: "/validate"
port: 443
admissionReviewVersions: [ "v1" ]
sideEffects: None
timeoutSeconds: 5
```

The conversion hook is configured within the `CustomResourceDefinition`, see
related [Kubernetes docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#configure-customresourcedefinition-to-use-conversion-webhooks).
Since this is [not yet supported](https://github.com/fabric8io/kubernetes-client/issues/4692) by the fabric8 client CRD
generator, the hook definition is
[added before](https://github.com/java-operator-sdk/admission-controller-framework/blob/e2637a90152bebfca2983ba17268c1f7ec7e9602/samples/commons/src/test/java/io/javaoperatorsdk/webhook/sample/EndToEndTestBase.java#L97-L124) CRD is applied.

Note
that [cert manager](https://github.com/java-operator-sdk/admission-controller-framework/blob/e2637a90152bebfca2983ba17268c1f7ec7e9602/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java#L19-L23)
is used to generate certificates for the application and for configurations.

## Admission Controllers API

There are two types of admission controllers: validation and mutation. Both should be extremely simple to use.

To create the related controller simply pass a lambda to the constructor
of [AdmissionController](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/admission/AdmissionController.java#L104-L104)
that validates the resource. (See also
the [async version](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/admission/AsyncAdmissionController.java#L104-L104)
of admission controller implementation.)

```java
new AdmissionController<>((resource,operation) -> {
if(resource.getMetadata().getLabels() == null || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null){
throw new NotAllowedException("Missing label: "+APP_NAME_LABEL_KEY);
}
});
```

respectively mutates it:

```java
new AdmissionController<>((resource,operation) -> {
if(resource.getMetadata().getLabels() == null){
resource.getMetadata().setLabels(new HashMap<>());
}
resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY,"mutation-test");
return resource;
});
```

All changes made to the resource are reflected in the response created by the admission controller.

## Conversion Hooks API

[ConversionController](https://github.com/java-operator-sdk/admission-controller-framework/blob/core/src/main/java/io/javaoperatorsdk/webhook/conversion/ConversionController.java) (
respectively [AsyncConversionController](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java))
handles conversion between different versions of custom resources
using [mappers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Mapper.java)
( respectively [async mappers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncMapper.java)).

The mapper interface is simple:

```java
public interface Mapper<R extends HasMetadata, HUB> {

HUB toHub(R resource);

R fromHub(HUB hub);

}
```

It handles mapping to and from a Hub. Hub is an intermediate representation in a conversion. Thus, the conversion
steps from v1 to v2 happens in the following way: v1 -> HUB -> v2. Using the provided v1 and v2 mappers implementations.
Having this approach is useful mainly in case there are more than two version of resources on the cluster, so there is
no need for a mapper for every combination. See also related docs in
[Kubebuilder](https://book.kubebuilder.io/multiversion-tutorial/conversion-concepts.html).
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class AdmissionControllers {
public static final String VALIDATION_TARGET_LABEL = "app.kubernetes.io/name";
public static final String MUTATION_TARGET_LABEL = "app.kubernetes.io/id";

// adds a label to the target resource
public static AdmissionController<Ingress> mutatingController() {
return new AdmissionController<>((resource, operation) -> {
if (resource.getMetadata().getLabels() == null) {
Expand All @@ -27,6 +28,7 @@ public static AdmissionController<Ingress> mutatingController() {
});
}

// validates if a resource contains the target label
public static AdmissionController<Ingress> validatingController() {
return new AdmissionController<>((resource, operation) -> {
if (resource.getMetadata().getLabels() == null
Expand Down
1 change: 1 addition & 0 deletions samples/quarkus/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</dependency>
<!-- used to generate Kubernetes resources -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
Expand Down