diff --git a/docs/content/.pages b/docs/content/.pages
index 6d3bb58c2..786980b11 100644
--- a/docs/content/.pages
+++ b/docs/content/.pages
@@ -3,4 +3,5 @@ nav:
- index.md
- Setup: setup
- Contributing: contributing
+ - Developers: developers
- Reference: reference
diff --git a/docs/content/developers/.pages b/docs/content/developers/.pages
new file mode 100644
index 000000000..1d588e8f9
--- /dev/null
+++ b/docs/content/developers/.pages
@@ -0,0 +1,4 @@
+nav:
+ - index.md
+ - Backend: backend
+ - Konnector: konnector
diff --git a/docs/content/developers/backend/.pages b/docs/content/developers/backend/.pages
new file mode 100644
index 000000000..f68314ff3
--- /dev/null
+++ b/docs/content/developers/backend/.pages
@@ -0,0 +1,3 @@
+nav:
+ - index.md
+ - HTTP: http
diff --git a/docs/content/developers/backend/http/.pages b/docs/content/developers/backend/http/.pages
new file mode 100644
index 000000000..35f34ac32
--- /dev/null
+++ b/docs/content/developers/backend/http/.pages
@@ -0,0 +1,3 @@
+nav:
+ - cluster-binding.md
+ - api-binding.md
diff --git a/docs/content/developers/backend/http/api-binding.md b/docs/content/developers/backend/http/api-binding.md
new file mode 100644
index 000000000..9104a2b2a
--- /dev/null
+++ b/docs/content/developers/backend/http/api-binding.md
@@ -0,0 +1,50 @@
+# API Binding
+
+## Overview
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant consumer-cluster as Consumer cluster
+ participant client as Client
+ participant provider-cluster as Provider cluster
+ participant provider-backend as Provider backend
+
+
+ %% Create APIServiceExportRequest
+ client->>provider-cluster: Create "APIServiceExportRequest"
+
+ par Provider
+ provider-backend->>provider-cluster: Get "APIServiceExportRequest"
+ loop For each "APIServiceExportRequest.spec.resources"
+ provider-backend->>provider-cluster: Create "APIServiceExport"
+ end
+ provider-backend->>provider-cluster: Set "APIServiceExportRequest.status.phase"
+ and Client
+ loop Every 1 second for 10 minutes
+ client->>provider-cluster: Get "APIServiceExportRequest"
+ client->>client: Verify "APIServiceExportRequest"
(.status.phase == Succeeded)"
+ end
+ end
+
+ %% Create APIServiceBindings
+ loop For each "APIServiceExportRequest.spec.resources"
+ client->>consumer-cluster: Create "APIServiceBinding"
+ end
+```
+
+## Contracts
+
+**APIServiceExportRequest**
+
+In case the _APIServiceExportRequest_ is accepted, the provider backend **must** ensure that
+
+* for each `APIServiceExportRequest.spec.resources` an `APIServiceExport` is created
+* each `APIServiceExport` is created in the namespace of the `APIServiceExportRequest`
+* each `APIServiceExport` is created with a name following the pattern `resource.resource + "." + resource.group`
+* the `APIServiceExportRequest.status.phase` is set to `Succeeded`
+
+In case the _APIServiceExportRequest_ is declined, the provider backend **must** ensure that
+
+* the `APIServiceExportRequest.status.terminalMessage` is set to a human readable message describing the reason
+* the `APIServiceExportRequest.status.phase` is set to `Failed`
diff --git a/docs/content/developers/backend/http/cluster-binding.md b/docs/content/developers/backend/http/cluster-binding.md
new file mode 100644
index 000000000..02e90cd8d
--- /dev/null
+++ b/docs/content/developers/backend/http/cluster-binding.md
@@ -0,0 +1,70 @@
+# Cluster Binding
+
+## Overview
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant consumer-cluster as Consumer cluster
+ participant client as Client
+ participant provider-backend as Provider backend
+ participant authentication-provider as Authentication Provider
+
+ %% Get provider information
+ client->>+provider-backend: GET "${PROVIDER_BINDING_URL}"
+ provider-backend->>-client: 200 "BindingProvider"
+
+ client->>client: Verify "BindingProvider"
+
+ %% Authenticate to provider
+ client-->>authentication-provider: Authenticate to provider
+
+ %% Bind to API
+ provider-backend->>client: 200 "BindingResponse"
+
+ client->>consumer-cluster: Ensure kubeconfig secret
+
+ loop For each "BindingResponse.requests"
+ client->>consumer-cluster: Bind remote API to consumer cluster
+ end
+```
+
+## Contracts
+
+**BindingResponse**
+
+In case the cluster binding request is accepted, the provider backend **must** ensure that
+
+* the kubeconfig returned in the `BindingResponse` contains a current context
+* the configured current context points to the "cluster namespace"
+* the `ClusterBinding` object named "cluster" exists in the "cluster namespace"
+* the secret referenced by `ClusterBinding.spec.kubeconfigSecretRef.name` exists
+* the key of the secret referenced by `ClusterBinding.spec.kubeconfigSecretRef.key` contains a valid kubeconfig
+* the configured current context points to a user with at least the following permissions in the "cluster namespace" (here expressed as RBAC rules)
+ ```yaml
+ - apiGroups: ["kube-bind.io"]
+ resources: ["apiserviceexportrequests"]
+ verbs: ["create", "delete", "patch", "update", "get", "list", "watch"]
+
+ - apiGroups: ["kube-bind.io"]
+ resources: ["apiserviceexports"]
+ verbs: ["get", "watch", "list"]
+ - apiGroups: ["kube-bind.io"]
+ resources: ["apiserviceexports/status"]
+ verbs: ["get", "patch", "update"]
+
+ - apiGroups: ["kube-bind.io"]
+ resources: ["apiservicenamespaces"]
+ verbs: ["create", "delete", "patch", "update", "get", "list", "watch"]
+
+ - apiGroups: ["kube-bind.io"]
+ resources: ["clusterbindings"]
+ verbs: ["get", "watch", "list"]
+ - apiGroups: ["kube-bind.io"]
+ resources: ["clusterbindings/status"]
+ verbs: ["get", "patch", "update"]
+
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["get", "watch", "list"]
+ ```
diff --git a/docs/content/developers/backend/index.md b/docs/content/developers/backend/index.md
new file mode 100644
index 000000000..781894a4f
--- /dev/null
+++ b/docs/content/developers/backend/index.md
@@ -0,0 +1 @@
+# Backend
diff --git a/docs/content/developers/index.md b/docs/content/developers/index.md
new file mode 100644
index 000000000..73437b84b
--- /dev/null
+++ b/docs/content/developers/index.md
@@ -0,0 +1 @@
+# Developers
diff --git a/docs/content/developers/konnector/.pages b/docs/content/developers/konnector/.pages
new file mode 100644
index 000000000..b6df526d4
--- /dev/null
+++ b/docs/content/developers/konnector/.pages
@@ -0,0 +1,3 @@
+nav:
+ - index.md
+ - Controllers: controllers
diff --git a/docs/content/developers/konnector/controllers/.pages b/docs/content/developers/konnector/controllers/.pages
new file mode 100644
index 000000000..322ecd430
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/.pages
@@ -0,0 +1,4 @@
+nav:
+ - konnector.md
+ - apiservicebinding.md
+ - Cluster: cluster
diff --git a/docs/content/developers/konnector/controllers/apiservicebinding.md b/docs/content/developers/konnector/controllers/apiservicebinding.md
new file mode 100644
index 000000000..7557373d7
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/apiservicebinding.md
@@ -0,0 +1,36 @@
+# APIServiceBindings
+
+The APIServiceBinding controller watches `APIServiceBindings` and the referenced `Secrets` in the **consumer
+cluster**.
+
+It is responsible for:
+
+* validating the kubeconfig stored in the secrets referenced by `APIServiceBindings`
+
+## Overview
+
+```mermaid
+flowchart TD
+ %% Nodes
+ start@{ shape: start }
+ stop@{ shape: stop }
+
+ enqueue_reconcile(["Enqueue reconcile call for APIServiceBinding"])
+
+ get_kubeconfig_secret(["Get referenced kubeconfig secret"])
+ is_kubeconfig_valid{"kubeconfig
valid?"}
+
+ set_condition_secret_valid_to_true(["Set condition 'SecretValid' to true"])
+ set_condition_secret_valid_to_false(["Set condition 'SecretValid' to false"])
+
+ %% Transitions
+ start --> enqueue_reconcile
+ enqueue_reconcile --> get_kubeconfig_secret
+ get_kubeconfig_secret --> is_kubeconfig_valid
+
+ is_kubeconfig_valid --> |yes| set_condition_secret_valid_to_true
+ is_kubeconfig_valid --> |no| set_condition_secret_valid_to_false
+
+ set_condition_secret_valid_to_true --> stop
+ set_condition_secret_valid_to_false --> stop
+```
diff --git a/docs/content/developers/konnector/controllers/cluster/.pages b/docs/content/developers/konnector/controllers/cluster/.pages
new file mode 100644
index 000000000..12e123a3c
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/cluster/.pages
@@ -0,0 +1,4 @@
+nav:
+ - clusterbinding.md
+ - apiservicenamespace.md
+ - apiservicebinding.md
diff --git a/docs/content/developers/konnector/controllers/cluster/apiservicebinding.md b/docs/content/developers/konnector/controllers/cluster/apiservicebinding.md
new file mode 100644
index 000000000..64f76a1c9
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/cluster/apiservicebinding.md
@@ -0,0 +1,70 @@
+# APIServiceBindings
+
+The APIServiceBinding controller watches `APIServiceBindings`, and `CRDs` in the **consumer cluster** and `APIServiceExports` in the **provider cluster**.
+
+It is responsible for:
+
+* synchronizing `APIServiceExports` in the **provider cluster** to `CRDs` in the **consumer cluster**
+
+## Overview
+
+```mermaid
+flowchart TD
+ %% Nodes
+ start@{ shape: start }
+ stop@{ shape: stop }
+
+ enqueue_reconcile(["Enqueue reconcile call for APIServiceBinding"])
+
+ is_apiservicebinding_owned{"binding
owned?"}
+ set_apiservicebinding_condition_connected_to_true(["Set condition 'Connected' to true"])
+ set_apiservicebinding_condition_connected_to_false(["Set condition 'Connected' to false"])
+ set_apiservicebinding_condition_schemainsync_to_true(["Set condition 'SchemaInSync' to true"])
+ set_apiservicebinding_condition_schemainsync_to_false(["Set condition 'SchemaInSync' to false"])
+
+ get_apiserviceexport(["Get APIServiceExport"])
+ convert_apiserviceexport_to_crd(["Convert APIServiceExport to CRD"])
+ is_apiserviceexport_present{"export
exists?"}
+ is_apiserviceexport_valid{"export
valid?"}
+
+ get_crd(["Get CRD"])
+ create_crd(["Create CRD"])
+ update_crd(["Update CRD"])
+ is_crd_present{"CRD
exists?"}
+ is_crd_owned{"CRD
owned?"}
+
+ get_clusterbinding(["Get ClusterBinding"])
+ set_apiservicebinding_provider_name(["Set provider name"])
+
+ %% Transitions
+ start --> enqueue_reconcile
+ enqueue_reconcile --> is_apiservicebinding_owned
+
+ is_apiservicebinding_owned --> |yes| get_apiserviceexport
+ get_apiserviceexport --> is_apiserviceexport_present
+ is_apiserviceexport_present --> |yes| set_apiservicebinding_condition_connected_to_true
+ is_apiserviceexport_present --> |no| set_apiservicebinding_condition_connected_to_false
+ set_apiservicebinding_condition_connected_to_true --> convert_apiserviceexport_to_crd
+ set_apiservicebinding_condition_connected_to_false --> stop
+
+ convert_apiserviceexport_to_crd --> is_apiserviceexport_valid
+ is_apiserviceexport_valid --> |yes| get_crd
+ is_apiserviceexport_valid --> |no| set_apiservicebinding_condition_schemainsync_to_false
+
+ get_crd --> is_crd_present
+ is_crd_present --> |no| create_crd
+ is_crd_present --> |yes| is_crd_owned
+ update_crd --> set_apiservicebinding_condition_schemainsync_to_true
+ create_crd --> set_apiservicebinding_condition_schemainsync_to_true
+
+ is_crd_owned --> |yes| update_crd
+ is_crd_owned --> |no| set_apiservicebinding_condition_schemainsync_to_false
+
+ set_apiservicebinding_condition_schemainsync_to_true --> get_clusterbinding
+ set_apiservicebinding_condition_schemainsync_to_false --> get_clusterbinding
+
+ get_clusterbinding --> set_apiservicebinding_provider_name
+
+ is_apiservicebinding_owned --> |no| stop
+ set_apiservicebinding_provider_name --> stop
+```
diff --git a/docs/content/developers/konnector/controllers/cluster/apiservicenamespace.md b/docs/content/developers/konnector/controllers/cluster/apiservicenamespace.md
new file mode 100644
index 000000000..48f502e44
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/cluster/apiservicenamespace.md
@@ -0,0 +1,32 @@
+# APIServiceNamespaces
+
+The APIServiceNamespace controller watches `Namespaces` in the **consumer cluster** and `APIServiceNamespaces` in the **provider cluster**.
+
+It is responsible for:
+
+* synchronizing `Namespaces` in the **consumer cluster** with `APIServiceNamespaces` in the **provider cluster**
+
+## Overview
+
+```mermaid
+flowchart TD
+ %% Nodes
+ start@{ shape: start }
+ stop@{ shape: stop }
+
+ enqueue_reconcile(["Enqueue reconcile call for APIServiceNamespace"])
+
+ get_namespace(["Get associated namespace"])
+ is_namespace_present(["namespace
exists?"])
+
+ delete_api_service_namespace(["Delete APIServiceNamespace"])
+
+ %% Transitions
+ start --> enqueue_reconcile
+ enqueue_reconcile --> get_namespace
+ get_namespace --> is_namespace_present
+
+ is_namespace_present --> |yes| stop
+ is_namespace_present --> |no| delete_api_service_namespace
+ delete_api_service_namespace --> stop
+```
diff --git a/docs/content/developers/konnector/controllers/cluster/clusterbinding.md b/docs/content/developers/konnector/controllers/cluster/clusterbinding.md
new file mode 100644
index 000000000..a15ae0d83
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/cluster/clusterbinding.md
@@ -0,0 +1,86 @@
+# ClusterBindings
+
+The ClusterBinding controller watches `Secrets` (referenced by `APIServiceBindings`) in the **consumer
+cluster** and `ClusterBindings`, the referenced `Secrets`, and `APIServiceExport` in the **provider
+cluster**.
+
+It is responsible for:
+
+* synchronizing the secret referenced by the `ClusterBinding` in the **provider cluster** to the secret referenced by the `APIServiceBindings` in the **consumer cluster**
+* reporting heartbeat to `ClusterBinding` in the **provider cluster**
+* reporting konnector version `ClusterBinding` in the **provider cluster**
+* reporting heartbeat to all `APIServiceBindings` managed by `ClusterBinding` in the **consumer cluster**
+
+## Overview
+
+```mermaid
+flowchart TD
+ %% Nodes
+ start@{ shape: start }
+ stop@{ shape: stop }
+
+ enqueue_reconcile(["Enqueue reconcile call for ClusterBinding"])
+
+ update_cluster_binding(["Update ClusterBinding"])
+ is_cluster_binding_update_successful{"update
successful?"}
+
+ get_cluster_binding_kubeconfig_secret(["Get referenced provider kubeconfig secret"])
+ is_cluster_binding_kubeconfig_secret_valid{"secret
valid?"}
+
+ set_cluster_binding_condition_secret_valid_to_true(["Set condition 'SecretValid' to true"])
+ set_cluster_binding_condition_secret_valid_to_false(["Set condition 'SecretValid' to false"])
+ set_cluster_binding_condition_valid_version_to_true(["Set condition 'ValidVersion' to true"])
+ set_cluster_binding_condition_valid_version_to_false(["Set condition 'ValidVersion' to false"])
+ set_cluster_binding_condition_ready(["Set condition 'Ready' to summary"])
+
+ set_cluster_binding_status_last_heartbeat(["Set status 'LastHeartbeatTime' to now"])
+ set_cluster_binding_status_konnector_version(["Set status 'KonnectorVersion'"])
+
+ get_api_binding_kubeconfig_secret(["Get consumer kubeconfig secret"])
+ create_api_binding_kubeconfig_secret(["Create consumer kubeconfig secret"])
+ update_api_binding_kubeconfig_secret(["Update consumer kubeconfig secret"])
+ is_api_binding_kubeconfig_secret_present{"secret
exists?"}
+
+ set_api_binding_status_heartbeating_to_true(["Set APIServiceBinding conditions 'Heartbeating' to true"])
+ set_api_binding_status_heartbeating_to_false(["Set APIServiceBinding conditions 'Heartbeating' to false"])
+
+ get_konnector_version(["Get konnector version"])
+ is_konnector_version_valid{"version
valid?"}
+
+ %% Transitions
+ start --> enqueue_reconcile
+ enqueue_reconcile --> get_cluster_binding_kubeconfig_secret
+ get_cluster_binding_kubeconfig_secret --> is_cluster_binding_kubeconfig_secret_valid
+
+ is_cluster_binding_kubeconfig_secret_valid --> |yes| get_api_binding_kubeconfig_secret
+ get_api_binding_kubeconfig_secret --> is_api_binding_kubeconfig_secret_present
+ is_api_binding_kubeconfig_secret_present --> |yes| update_api_binding_kubeconfig_secret
+ is_api_binding_kubeconfig_secret_present --> |no| create_api_binding_kubeconfig_secret
+ update_api_binding_kubeconfig_secret --> set_cluster_binding_condition_secret_valid_to_true
+ create_api_binding_kubeconfig_secret --> set_cluster_binding_condition_secret_valid_to_true
+
+ is_cluster_binding_kubeconfig_secret_valid --> |no| set_cluster_binding_condition_secret_valid_to_false
+
+ set_cluster_binding_condition_secret_valid_to_true --> set_cluster_binding_status_last_heartbeat
+ set_cluster_binding_condition_secret_valid_to_false --> set_cluster_binding_status_last_heartbeat
+
+ set_cluster_binding_status_last_heartbeat --> get_konnector_version
+ get_konnector_version --> is_konnector_version_valid
+
+ is_konnector_version_valid --> |yes| set_cluster_binding_status_konnector_version
+ set_cluster_binding_status_konnector_version --> set_cluster_binding_condition_valid_version_to_true
+
+ is_konnector_version_valid --> |no| set_cluster_binding_condition_valid_version_to_false
+
+ set_cluster_binding_condition_valid_version_to_true --> set_cluster_binding_condition_ready
+ set_cluster_binding_condition_valid_version_to_false --> set_cluster_binding_condition_ready
+
+ set_cluster_binding_condition_ready --> update_cluster_binding
+ update_cluster_binding --> is_cluster_binding_update_successful
+
+ is_cluster_binding_update_successful --> |yes| set_api_binding_status_heartbeating_to_true
+ is_cluster_binding_update_successful --> |no| set_api_binding_status_heartbeating_to_false
+
+ set_api_binding_status_heartbeating_to_true --> stop
+ set_api_binding_status_heartbeating_to_false --> stop
+```
diff --git a/docs/content/developers/konnector/controllers/konnector.md b/docs/content/developers/konnector/controllers/konnector.md
new file mode 100644
index 000000000..a41434f3f
--- /dev/null
+++ b/docs/content/developers/konnector/controllers/konnector.md
@@ -0,0 +1,60 @@
+# konnector
+
+The konnector implements the main reconciliation loop and watches `APIServiceBindings` and the referenced `Secrets` in the **consumer cluster**.
+
+It is responsible for:
+
+* starting / stopping a set of controllers per service provider
+
+## Overview
+
+```mermaid
+flowchart TD
+ %% Nodes
+ start@{ shape: start }
+ stop@{ shape: stop }
+
+ enqueue_reconcile(["Enqueue reconcile call for APIServiceBinding"])
+
+ get_kubeconfig_secret(["Get referenced kubeconfig secret"])
+ is_kubeconfig_empty{"kubeconfig
empty?"}
+
+ get_controller_context_for_binding(["Get controller context for binding"])
+ is_controller_context_for_binding_present{"context
exists?"}
+ is_controller_context_current{"context
up-to-date?"}
+ is_controller_context_used{"context
in use?"}
+
+ get_controller_context_for_kubeconfig(["Get controller context for kubeconfig"])
+ is_controller_context_for_kubeconfig_present{"context
exists?"}
+
+ create_controller_context_for_binding(["Create controller context for binding"])
+ add_binding_to_controller_context(["Add binding to controller context"])
+ remove_binding_from_controller_context(["Remove binding from controller context"])
+
+ start_controllers(["Start controllers"])
+ stop_controllers(["Stop controllers"])
+
+ %% Transitions
+ start --> enqueue_reconcile
+ enqueue_reconcile --> get_kubeconfig_secret
+ get_kubeconfig_secret --> get_controller_context_for_binding
+ get_controller_context_for_binding --> is_controller_context_for_binding_present
+
+ is_controller_context_for_binding_present -->|yes| is_controller_context_current
+ is_controller_context_current -->|yes| is_kubeconfig_empty
+ is_controller_context_current -->|no| remove_binding_from_controller_context
+ remove_binding_from_controller_context --> is_controller_context_used
+ is_controller_context_used -->|yes| stop
+ is_controller_context_used -->|no| stop_controllers
+ stop_controllers --> stop
+
+ is_controller_context_for_binding_present -->|no| is_kubeconfig_empty
+ is_kubeconfig_empty -->|yes| stop
+ is_kubeconfig_empty -->|no| get_controller_context_for_kubeconfig
+ get_controller_context_for_kubeconfig --> is_controller_context_for_kubeconfig_present
+ is_controller_context_for_kubeconfig_present -->|yes| add_binding_to_controller_context
+ add_binding_to_controller_context --> stop
+ is_controller_context_for_kubeconfig_present -->|no| create_controller_context_for_binding
+ create_controller_context_for_binding --> start_controllers
+ start_controllers --> stop
+```
diff --git a/docs/content/developers/konnector/index.md b/docs/content/developers/konnector/index.md
new file mode 100644
index 000000000..e3310cffc
--- /dev/null
+++ b/docs/content/developers/konnector/index.md
@@ -0,0 +1 @@
+# konnector
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index f79b015fb..19b1bdcf4 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -37,7 +37,7 @@ theme:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
- scheme: default
+ scheme: default
primary: white
toggle:
icon: material/brightness-7
@@ -97,7 +97,11 @@ markdown_extensions:
# Lets you embed content from another file
- pymdownx.snippets
# Arbitrary nesting of code/content blocks inside each other
- - pymdownx.superfences
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
# Enable note/warning/etc. callouts
- admonition
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 28d6ef28c..b613cf4d8 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,7 +1,7 @@
mike==2.1.3
-mkdocs==1.5.3
+mkdocs==1.6.1
mkdocs-awesome-pages-plugin==2.9.2
mkdocs-macros-plugin==1.0.5
-mkdocs-material==9.5.17
+mkdocs-material==9.5.49
mkdocs-material-extensions==1.3.1
mkdocs-static-i18n==1.2.2