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