Compare commits

...

16 Commits

Author SHA1 Message Date
Dario Tranchitella
9ca69e91f9 fix(kubeadm): ensure admin clusterrolebinding with in-cluster client (#608)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-10-21 21:21:53 +02:00
Dario Tranchitella
e4939f6dcb docs: kamaji-ingress-addon guide (#605)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-10-21 07:30:20 +02:00
Adriano Pezzuto
fcad29ddba feat(doc): document release support in Edge Releases (#604) 2024-10-18 19:59:34 +02:00
Jan Schoone
cae3c6041f chore(adopters): Sovereign Cloud Stack is a kamaji adopter
* fix(adopters): order alphabetically according to the header

Signed-off-by: Jan Schoone <jan@jass.es>

* feat(adopters): add Sovereign Cloud Stack

Signed-off-by: Jan Schoone <jan@jass.es>

---------

Signed-off-by: Jan Schoone <jan@jass.es>
2024-10-16 17:14:42 +02:00
Dario Tranchitella
7e08b9a7ce feat: cluster domain customisation (#594)
* feat(api): customising cluster domain option

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(helm): customising cluster domain option

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: customising cluster domain option

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-10-06 09:40:20 +02:00
dependabot[bot]
a21f199847 chore(ci): bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 (#592)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.1.0...v6.1.1)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-05 15:37:53 +02:00
Dario Tranchitella
7b89d69a1c feat: kine bind on uds (#593)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-10-04 15:36:58 +02:00
Simon Kienzler
8b71843325 feat: make datastore schema (prefix) configurable (#554)
* feat: Add DataStoreSchema field to TCP spec

* feat: Read DB_SCHEMA from TCP spec field

* feat: Default DataStoreSchema in webhook

* fix: Catch unsetting the dataStore via CEL

* fix: Apply all patches, not only the first

This also includes converting OnUpdate() to a no-op, as the
existence and immutability of the fields are already checked
by the API server, thanks to kubebuilder markers.

The webhook ensures that fields like dataStore, dataStoreSchema
are defaulted during creation (if unset), and the CEL expressions
prohibit unsetting them during update.

* test: Add tests for defaulting webhook

* fix: typo

* fix: Linter issues

* fix: make apidoc

* Update TCP CRD in charts folder

* fix: Don't run E2E tests during `make test`

* fix: Use proper `metav1` import name

* feat: Handle updates of TCPs without dataStoreSchema (+ tests)

* fix: Prioritize Status over Spec

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>

* Update goDoc on DataStore field

* make apidoc

---------

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2024-10-02 17:33:28 +02:00
dependabot[bot]
489e0e1653 feat(deps): bump go.uber.org/automaxprocs from 1.5.3 to 1.6.0 (#589)
Bumps [go.uber.org/automaxprocs](https://github.com/uber-go/automaxprocs) from 1.5.3 to 1.6.0.
- [Release notes](https://github.com/uber-go/automaxprocs/releases)
- [Changelog](https://github.com/uber-go/automaxprocs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/automaxprocs/compare/v1.5.3...v1.6.0)

---
updated-dependencies:
- dependency-name: go.uber.org/automaxprocs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 15:20:14 +02:00
dependabot[bot]
71b653eee9 feat(deps): bump github.com/docker/docker (#588)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 27.3.0+incompatible to 27.3.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.3.0...v27.3.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 15:27:48 +02:00
Dario Tranchitella
96fc9149a0 fix(ci): ref_name for edge tags (#587) 2024-09-20 11:47:55 +02:00
Dario Tranchitella
3e4e45cd6e fix(ci): triggering action upon tags (#586)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-09-20 11:44:29 +02:00
Dario Tranchitella
11bda430c6 fix(ci): building in case of edge or stable tag (#585)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2024-09-20 11:10:18 +02:00
Dario Tranchitella
293387e0d1 chore(ci): building and pushing edge images (#584) 2024-09-20 10:53:49 +02:00
dependabot[bot]
092eeb0274 feat(deps): bump github.com/docker/docker (#583)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 27.2.1+incompatible to 27.3.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.2.1...v27.3.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 10:49:54 +02:00
Wouter van Os
f483e812a5 fix(kubeadm): let Kubelet automatically determine resolvConf (#582) 2024-09-19 19:15:30 +02:00
30 changed files with 653 additions and 98 deletions

View File

@@ -16,7 +16,7 @@ jobs:
with:
go-version-file: go.mod
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6.1.0
uses: golangci/golangci-lint-action@v6.1.1
with:
version: v1.54.2
only-new-issues: false

View File

@@ -2,6 +2,9 @@ name: Container image build
on:
push:
tags:
- edge-*
- v*
branches:
- master
@@ -21,8 +24,8 @@ jobs:
run: ./bin/ko login quay.io -u ${{ secrets.QUAY_IO_USERNAME }} -p ${{ secrets.QUAY_IO_TOKEN }}
- name: "ko: login to docker.io container registry"
run: ./bin/ko login docker.io -u ${{ secrets.DOCKER_IO_USERNAME }} -p ${{ secrets.DOCKER_IO_TOKEN }}
- name: "ko: build and push latest"
run: make VERSION=latest KO_LOCAL=false KO_PUSH=true build
- name: "ko: build and push tag"
run: make VERSION=${{ github.ref_name }} KO_LOCAL=false KO_PUSH=true build
if: startsWith(github.event.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/edge-')
- name: "ko: build and push latest"
run: make VERSION=latest KO_LOCAL=false KO_PUSH=true build

View File

@@ -7,16 +7,17 @@ Feel free to open a Pull-Request to get yours listed.
| Type | Name | Since | Website | Use-Case |
|:-|:-|:-|:-|:-|
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
| End-user | Sicuro Tech Lab | 2024 | [link](https://sicurotechlab.it/) | Sicuro Tech Lab offers cloud infrastructure for Web Agencies and uses kamaji to provide managed k8s services. |
| R&D | TIM | 2024 | [link](https://www.gruppotim.it) | TIM is an Italian telecommunications company using Kamaji for experimental research and development purposes. |
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
| End-user | sevensphere | 2023 | [link](https://www.sevensphere.io) | Sevensphere provides consulting services for end-user companies / cloud providers and uses Kamaji for designing cloud/on-premises Kubernetes-as-a-Service platform. |
| Vendor | Ænix | 2023 | [link](https://aenix.io/) | Ænix provides consulting services for cloud providers and uses Kamaji for running Kubernetes-as-a-Service in free PaaS platform [Cozystack](https://cozystack.io). |
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
| Vendor | Aknostic | 2023 | [link](https://aknostic.com) | Aknostic is a cloud-native consultancy company using Kamaji to build a Kubernetes based PaaS. |
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
| R&D | Orange | 2024 | [link](https://gitlab.com/Orange-OpenSource/kanod) | Orange is a French telecommunications company using Kamaji for experimental research purpose, with Kanod research solution. |
| Vendor | Qumulus | 2024 | [link](https://www.qumulus.io) | Qumulus is a cloud provider and plans to use Kamaji for it's hosted Kubernetes service |
| End-user | sevensphere | 2023 | [link](https://www.sevensphere.io) | Sevensphere provides consulting services for end-user companies / cloud providers and uses Kamaji for designing cloud/on-premises Kubernetes-as-a-Service platform. |
| End-user | Sicuro Tech Lab | 2024 | [link](https://sicurotechlab.it/) | Sicuro Tech Lab offers cloud infrastructure for Web Agencies and uses kamaji to provide managed k8s services. |
| Vendor | Sovereign Cloud Stack | 2024 | [link](https://sovereigncloudstack.org) | Sovereign Cloud Stack develops a standardized cloud platform and uses Kamaji in there Kubernetes-as-a-Service reference implementation |
| R&D | TIM | 2024 | [link](https://www.gruppotim.it) | TIM is an Italian telecommunications company using Kamaji for experimental research and development purposes. |
| Vendor | Ænix | 2023 | [link](https://aenix.io/) | Ænix provides consulting services for cloud providers and uses Kamaji for running Kubernetes-as-a-Service in free PaaS platform [Cozystack](https://cozystack.io). |
### Adopter Types

View File

@@ -121,8 +121,13 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and
golint: golangci-lint ## Linting the code according to the styling guide.
$(GOLANGCI_LINT) run -c .golangci.yml
test:
go test ./... -coverprofile cover.out
.PHONY: test
test: ## Run unit tests (all tests except E2E).
@go test \
./api/... \
./cmd/... \
./internal/... \
-coverprofile cover.out
_datastore-mysql:
$(MAKE) NAME=$(NAME) -C deploy/kine/mysql mariadb

View File

@@ -14,6 +14,11 @@ type NetworkProfileSpec struct {
// Address where API server of will be exposed.
// In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
Address string `json:"address,omitempty"`
// The default domain name used for DNS resolution within the cluster.
//+kubebuilder:default="cluster.local"
//+kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the cluster domain is not supported"
//+kubebuilder:validation:Pattern=.*\..*
ClusterDomain string `json:"clusterDomain,omitempty"`
// AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address in the section of
// ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)
AllowAddressAsExternalIP bool `json:"allowAddressAsExternalIP,omitempty"`
@@ -249,12 +254,21 @@ type AddonsSpec struct {
}
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStore) || has(self.dataStore)", message="unsetting the dataStore is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)", message="unsetting the dataStoreSchema is not supported"
type TenantControlPlaneSpec struct {
// DataStore allows to specify a DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
// This parameter is optional and acts as an override over the default one which is used by the Kamaji Operator.
// Migration from a different DataStore to another one is not yet supported and the reconciliation will be blocked.
DataStore string `json:"dataStore,omitempty"`
ControlPlane ControlPlane `json:"controlPlane"`
// Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
// Migration from one DataStore to another backed by a different Driver is not supported.
DataStore string `json:"dataStore,omitempty"`
// DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
// value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreSchema values are unique. It's up
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
// DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreSchema is not supported"
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
ControlPlane ControlPlane `json:"controlPlane"`
// Kubernetes specification for tenant control plane
Kubernetes KubernetesSpec `json:"kubernetes"`
// NetworkProfile specifies how the network is

View File

@@ -6415,8 +6415,19 @@ spec:
description: |-
DataStore allows to specify a DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
This parameter is optional and acts as an override over the default one which is used by the Kamaji Operator.
Migration from a different DataStore to another one is not yet supported and the reconciliation will be blocked.
Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
Migration from one DataStore to another backed by a different Driver is not supported.
type: string
dataStoreSchema:
description: |-
DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreSchema values are unique. It's up
to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.
type: string
x-kubernetes-validations:
- message: changing the dataStoreSchema is not supported
rule: self == oldSelf
kubernetes:
description: Kubernetes specification for tenant control plane
properties:
@@ -6539,6 +6550,14 @@ spec:
items:
type: string
type: array
clusterDomain:
default: cluster.local
description: The default domain name used for DNS resolution within the cluster.
pattern: .*\..*
type: string
x-kubernetes-validations:
- message: changing the cluster domain is not supported
rule: self == oldSelf
dnsServiceIPs:
default:
- 10.96.0.10
@@ -6563,6 +6582,11 @@ spec:
- controlPlane
- kubernetes
type: object
x-kubernetes-validations:
- message: unsetting the dataStore is not supported
rule: '!has(oldSelf.dataStore) || has(self.dataStore)'
- message: unsetting the dataStoreSchema is not supported
rule: '!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)'
status:
description: TenantControlPlaneStatus defines the observed state of TenantControlPlane.
properties:

View File

@@ -0,0 +1,20 @@
# Enterprise Addons
This document contains the documentation of the available Kamaji addons.
## What is a Kamaji Addon
A Kamaji Addon is a separate component installed in the same Kamaji cluster.
It offers an additional set of features required for enterprise-grade usage.
The developed Kamaji addons are closed-source available and work with the upstream Kamaji edition.
## Distribution of Addons
The Kamaji Addons are available behind an active [subscription license](https://clastix.io/support/).
Once a subscription is activated, the [CLASTIX](https://clastix.io) team will automate the push of required OCI (container images) and Helm Chart artefacts to the customer's OCI-compatible repository.
## Available addons
- [Ingress Addon](/enterprise-addons/ingress): expose Tenant Control Planes behind an Ingress Controller to reduce the amount of required LoadBalancer services

View File

@@ -0,0 +1,182 @@
# Ingress Addon
A Kubernetes API Server could be announced to users in several ways.
The most preferred way is leveraging on Load Balancers with their dedicated IP.
![Load Balancer setup](../images/kamaji-addon-ingress-lb.png#only-light)
![Load Balancer setup](../images/kamaji-addon-ingress-lb-dark.png#only-dark)
However, IPv4 addresses could be limited and scarce in availability, as well as expensive for public ones when running in the Cloud.
A possible optimisation could be implementing an Ingress Controller which routes traffic to Kubernetes API Servers on a host-routing basis.
Despite this solution sounding optimal for end users, it brings some challenges from the worker nodes' standpoint.
## Challenges
Internally deployed applications that need to interact with the Kubernetes API Server will leverage on the `kubernetes` endpoint in the `default` namespace:
every request sent to the `https://kubernetes.default.svc` endpoint will be forwarded to the Kubernetes API Server.
The routing put in place by the Kubernetes CNI is based on the L4, meaning that all the requests will be forwarded to the Ingress Controller with no `Host` header,
making impossible a routing based on the FQDN.
## Solution
The `kamaji-addon-ingress` is an addon that will expose the Tenant Control Plane behind an Ingress Controller.
It's responsible for creating an `Ingress` object with the required HTTP rules, as well as the annotations needed for the TLS/SSL passthrough.
![Ingress Controller setup](../images/kamaji-addon-ingress-ic.png#only-light)
![Ingress Controller setup](../images/kamaji-addon-ingress-ic-dark.png#only-dark)
Following is the list of supported Ingress Controllers:
- [HAProxy Technologies Kubernetes Ingress](https://github.com/haproxytech/kubernetes-ingress)
> Active subscribers can request additional Ingress Controller flavours
## How to enable the Addon
Annotate the Tenant Control Plane instances with the key `kamaji.clastix.io/ingress.domain` and the domain suffix domain value:
```shell
kubectl annotate tenantcontrolplane $NAME kamaji.clastix.io/ingress.domain=$SUFFIX_DOMAIN
```
The value must be the expected suffix domain of generated resources.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
annotations:
kamaji.clastix.io/ingress.domain: clastix.cloud # the expected kamaji-addon-ingress label
name: tenant-00
namespace: apezzuto
```
Once a Tenant Control Plane has been annotated with this key, the addon will generate the following `Ingress` object.
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
haproxy.org/ssl-passthrough: "true"
name: 592ee7b8-cd07-48cf-b754-76f370c3f87c
namespace: apezzuto
spec:
ingressClassName: haproxy
rules:
- host: apezzuto-tenant-00.k8s.clastix.cloud
http:
paths:
- backend:
service:
name: tenant-00
port:
number: 6443
path: /
pathType: Prefix
- host: apezzuto-tenant-00.konnectivity.clastix.cloud
http:
paths:
- backend:
service:
name: tenant-00
port:
number: 8132
path: /
pathType: Prefix
```
The pattern for the generated hosts is the following:
`${tcp.namespace}-${tcp.name}.{k8s|konnectivity}.${ADDON_ANNOTATION_VALUE}`
> Please, notice the `konnectivity` rule will be created only if the `konnectivity` addon has been enabled.
## Infrastructure requirements
For Tenant Control Plane objects leveraging on this addon, the following changes must be implemented.
### Ingress Controller
The Ingress Controller must be deployed to listen for `https` connection on the default port `443`:
if you have different requirements, please, engage with the CLASTIX team.
### DNS resolution
The following zones must be configured properly according to your DNS provider:
```
*.konnectivity.clastix.cloud A <YOUR_INGRESS_CONTROLLER_IP>
*.k8s.clastix.cloud A <YOUR_INGRESS_CONTROLLER_IP>
```
### Certificate SANs
```yaml
networkProfile:
certSANs:
- apezzuto-tenant-00.k8s.clastix.cloud
- apezzuto-tenant-00.konnectivity.clastix.cloud
dnsServiceIPs:
- 10.96.0.10
podCidr: 10.244.0.0/16
port: 6443
serviceCidr: 10.96.0.0/16
```
### Service type and Ingress
The Kubernetes API Server can be exposed using a `ClusterIP`, rather than a Load Balancer.
```yaml
spec:
controlPlane:
service:
serviceType: ClusterIP
ingress:
hostname: apezzuto-tenant-00.k8s.clastix.cloud:443
ingressClassName: unhandled
```
The `ingressClassName` value must match a non-handled `IngressClass` object,
the addon will take care of generating the correct object.
> Nota Bene: the `hostname` must absolutely point to the 443 port
### Kubernetes components extra Arguments
The Kubernetes API Server must start with the following flag:
```yaml
spec:
controlPlane:
deployment:
extraArgs:
apiServer:
- --endpoint-reconciler-type=none
```
The `kamaji-addon-ingress` will be responsible for populating the `kubernetes` EndpointSlice object in the Tenant cluster.
If you're running with `konnectivity`, also this extra argument must be enforced:
```yaml
spec:
addons:
konnectivity:
agent:
extraArgs:
- --proxy-server-host=apezzuto-tenant-00.konnectivity.clastix.cloud
- --proxy-server-port=443
```
## Air-gapped environments
The `kamaji-addon-ingress` works with a deployed component in the Tenant Cluster based on the container image `docker.io/clastix/tcp-proxy:latest`.
The same image can be replaced by customising the Addon Helm value upon installation:
```
--set options.tcpProxyImage=private.repository.tld/tcp-proxy:latest
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -839,7 +839,18 @@ such as the number of Pod replicas, the Service resource, or the Ingress.<br/>
<td>
DataStore allows to specify a DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
This parameter is optional and acts as an override over the default one which is used by the Kamaji Operator.
Migration from a different DataStore to another one is not yet supported and the reconciliation will be blocked.<br/>
Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
Migration from one DataStore to another backed by a different Driver is not supported.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>dataStoreSchema</b></td>
<td>string</td>
<td>
DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreSchema values are unique. It's up
to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.<br/>
</td>
<td>false</td>
</tr><tr>
@@ -13913,6 +13924,15 @@ ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)<br/>
Use this field to add additional hostnames when exposing the Tenant Control Plane with third solutions.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>clusterDomain</b></td>
<td>string</td>
<td>
The default domain name used for DNS resolution within the cluster.<br/>
<br/>
<i>Default</i>: cluster.local<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>dnsServiceIPs</b></td>
<td>[]string</td>

View File

@@ -10,16 +10,19 @@ Edge Release artifacts are published on a monthly basis as part of the open sour
Edge Release artifacts contain the code in from the main branch at the point in time when they were cut. This means they always have the latest features and fixes, and have undergone automated testing as well as maintainer code review. Edge Releases may involve partial features that are later modified or backed out. They may also involve breaking changes, of course, we do our best to avoid this. Edge Releases are generally considered production ready, and the project will mark specific releases as “_not recommended_” if bugs are discovered after release.
| Kamaji | Management Cluster | Tenant Cluster |
|-------------|--------------------|----------------------|
| edge-24.9.2 | v1.22+ | [v1.28.0 .. v1.31.1] |
Using Edge Release artifacts and reporting bugs helps us ensure a rapid pace of development and is a great way to help maintainers. We publish edge release guidance as part of the release notes and strive to always provide production-ready artifacts.
### Stable Releases
Stable Release artifacts of Kamaji follow semantic versioning, whereby changes in major version denote large feature additions and possible breaking changes and changes in minor versions denote safe upgrades without breaking changes.
Stable Release artifacts of Kamaji follow semantic versioning, whereby changes in major version denote large feature additions and possible breaking changes and changes in minor versions denote safe upgrades without breaking changes. As of July 2024 [Clastix Labs](https://github.com/clastix) organization does no longer provide stable release artifacts. Latest stable release available is:
| Kamaji | Management Cluster | Tenant Cluster |
|--------|--------------------|----------------------|
| v1.0.0 | v1.22+ | [v1.21.0 .. v1.30.2] |
As of July 2024 [Clastix Labs](https://github.com/clastix) organization does no longer provide stable release artifacts. Stable Release artifacts are offered on a subscription basis by [CLASTIX](https://clastix.io), the main Kamaji project contributor.
> Learn more about [available subscription plans](https://clastix.io/support/) from CLASTIX.
Stable Release artifacts are offered now on a subscription basis by [CLASTIX](https://clastix.io), the main Kamaji project contributor. Learn more about [available subscription plans](https://clastix.io/support/) provided by CLASTIX.

View File

@@ -77,4 +77,7 @@ nav:
- reference/versioning.md
- reference/api.md
- 'Telemetry': telemetry.md
- 'Enterprise Addons':
- enterprise-addons/index.md
- enterprise-addons/ingress.md
- 'Contribute': contribute.md

4
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/JamesStewy/go-mysqldump v0.2.2
github.com/blang/semver v3.5.1+incompatible
github.com/clastix/kamaji-telemetry v1.0.0
github.com/docker/docker v27.2.1+incompatible
github.com/docker/docker v27.3.1+incompatible
github.com/go-logr/logr v1.4.2
github.com/go-pg/pg/v10 v10.13.0
github.com/go-sql-driver/mysql v1.8.1
@@ -26,7 +26,7 @@ require (
github.com/testcontainers/testcontainers-go v0.33.0
go.etcd.io/etcd/api/v3 v3.5.16
go.etcd.io/etcd/client/v3 v3.5.16
go.uber.org/automaxprocs v1.5.3
go.uber.org/automaxprocs v1.6.0
gomodules.xyz/jsonpatch/v2 v2.4.0
k8s.io/api v0.31.1
k8s.io/apimachinery v0.31.1

8
go.sum
View File

@@ -60,8 +60,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -413,8 +413,8 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=

View File

@@ -36,6 +36,9 @@ const (
usrLocalShareCaCertificateVolumeName = "usr-local-share-ca-certificates"
schedulerKubeconfigVolumeName = "scheduler-kubeconfig"
controllerManagerKubeconfigVolumeName = "controller-manager-kubeconfig"
kineUDSVolume = "kine-uds"
kineUDSFolder = "/uds"
kineUDSPath = kineUDSFolder + "/kine"
dataStoreCertsVolumeName = "kine-config"
kineVolumeCertName = "kine-certs"
)
@@ -632,6 +635,16 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
volumeMounts := d.initVolumeMounts(kubernetesPKIVolumeName, podSpec.Containers[index].VolumeMounts, extraVolumeMounts...)
if d.DataStore.Spec.Driver == kamajiv1alpha1.KineMySQLDriver ||
d.DataStore.Spec.Driver == kamajiv1alpha1.KinePostgreSQLDriver ||
d.DataStore.Spec.Driver == kamajiv1alpha1.KineNatsDriver {
d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
Name: kineUDSVolume,
ReadOnly: false,
MountPath: kineUDSFolder,
})
}
d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
Name: kubernetesPKIVolumeName,
ReadOnly: true,
@@ -711,7 +724,7 @@ func (d Deployment) buildKubeAPIServerCommand(tenantControlPlane kamajiv1alpha1.
switch d.DataStore.Spec.Driver {
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver, kamajiv1alpha1.KineNatsDriver:
desiredArgs["--etcd-servers"] = "http://127.0.0.1:2379"
desiredArgs["--etcd-servers"] = "unix://" + kineUDSPath
case kamajiv1alpha1.EtcdDriver:
httpsEndpoints := make([]string, 0, len(d.DataStore.Spec.Endpoints))
@@ -751,7 +764,7 @@ func (d Deployment) secretProjection(secretName, certKeyName, keyName string) *c
}
func (d Deployment) removeKineVolumes(podSpec *corev1.PodSpec) {
for _, volumeName := range []string{kineVolumeCertName, dataStoreCertsVolumeName} {
for _, volumeName := range []string{kineVolumeCertName, dataStoreCertsVolumeName, kineUDSVolume} {
if found, index := utilities.HasNamedVolume(podSpec.Volumes, volumeName); found {
var volumes []corev1.Volume
@@ -768,7 +781,20 @@ func (d Deployment) buildKineVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.
return
}
found, index := utilities.HasNamedVolume(podSpec.Volumes, dataStoreCertsVolumeName)
found, index := utilities.HasNamedVolume(podSpec.Volumes, kineUDSVolume)
if !found {
index = len(podSpec.Volumes)
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
}
podSpec.Volumes[index].Name = kineUDSVolume
podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: "Memory",
},
}
found, index = utilities.HasNamedVolume(podSpec.Volumes, dataStoreCertsVolumeName)
if !found {
index = len(podSpec.Volumes)
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
@@ -830,6 +856,8 @@ func (d Deployment) buildKine(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.Tenant
// Building kine arguments, taking in consideration the user-space ones if provided.
args := map[string]string{}
args["--listen-address"] = "unix://" + kineUDSPath
if d.DataStore.Spec.TLSConfig != nil {
// Ensuring the init container required for kine is present:
// a chmod is required for kine in order to read the certificates to connect to the secured datastore.
@@ -908,6 +936,11 @@ func (d Deployment) buildKine(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.Tenant
MountPath: "/certs",
ReadOnly: false,
},
{
Name: kineUDSVolume,
MountPath: kineUDSFolder,
ReadOnly: false,
},
}
podSpec.Containers[index].Env = []corev1.EnvVar{
{

View File

@@ -50,7 +50,7 @@ func CreateKubeadmInitConfiguration(params Parameters) (*Configuration, error) {
},
}
conf.Networking = kubeadmapi.Networking{
DNSDomain: "cluster.local",
DNSDomain: params.TenantControlPlaneClusterDomain,
PodSubnet: params.TenantControlPlanePodCIDR,
ServiceSubnet: params.TenantControlPlaneServiceCIDR,
}

View File

@@ -32,22 +32,23 @@ func (c *Configuration) Checksum() string {
}
type Parameters struct {
TenantControlPlaneName string
TenantControlPlaneNamespace string
TenantControlPlaneEndpoint string
TenantControlPlaneAddress string
TenantControlPlaneCertSANs []string
TenantControlPlanePort int32
TenantControlPlanePodCIDR string
TenantControlPlaneServiceCIDR string
TenantDNSServiceIPs []string
TenantControlPlaneVersion string
TenantControlPlaneCGroupDriver string
ETCDs []string
CertificatesDir string
KubeconfigDir string
KubeProxyOptions *AddonOptions
CoreDNSOptions *AddonOptions
TenantControlPlaneName string
TenantControlPlaneNamespace string
TenantControlPlaneEndpoint string
TenantControlPlaneAddress string
TenantControlPlaneCertSANs []string
TenantControlPlanePort int32
TenantControlPlaneClusterDomain string
TenantControlPlanePodCIDR string
TenantControlPlaneServiceCIDR string
TenantDNSServiceIPs []string
TenantControlPlaneVersion string
TenantControlPlaneCGroupDriver string
ETCDs []string
CertificatesDir string
KubeconfigDir string
KubeProxyOptions *AddonOptions
CoreDNSOptions *AddonOptions
}
type AddonOptions struct {

View File

@@ -89,6 +89,9 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]by
// kubeadm <= v1.27 has a different type for FlushFrequency
// https://github.com/kubernetes/component-base/blob/55b3ab0db0081303695d641b9b43d560bf3f7a65/logs/api/v1/types.go#L42-L45
kc.Logging.FlushFrequency.SerializeAsString = false
// Restore default behaviour so Kubelet will automatically
// determine the resolvConf location, as reported in clastix/kamaji#581.
kc.ResolverConfig = nil
return utilities.EncodeToYaml(&kc)
}

View File

@@ -155,9 +155,25 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
username = coalesceFn(tenantControlPlane.Status.Storage.Setup.User)
}
var dataStoreSchema string
switch {
case len(tenantControlPlane.Status.Storage.Setup.Schema) > 0:
// for existing TCPs, the dataStoreSchema will be adopted from the status,
// as the mutating webhook only takes care of TCP creations, not updates
dataStoreSchema = tenantControlPlane.Status.Storage.Setup.Schema
tenantControlPlane.Spec.DataStoreSchema = dataStoreSchema
case len(tenantControlPlane.Spec.DataStoreSchema) > 0:
// for new TCPs, the spec field will have been provided by the user
// or defaulted by the defaulting webhook
dataStoreSchema = tenantControlPlane.Spec.DataStoreSchema
default:
// this can only happen on TCP creations when the webhook is not installed
return fmt.Errorf("cannot build datastore storage config, schema name must either exist in Spec or Status")
}
r.resource.Data = map[string][]byte{
"DB_CONNECTION_STRING": []byte(r.ConnString),
"DB_SCHEMA": coalesceFn(tenantControlPlane.Status.Storage.Setup.Schema),
"DB_SCHEMA": []byte(dataStoreSchema),
"DB_USER": username,
"DB_PASSWORD": password,
}

View File

@@ -0,0 +1,115 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore_test
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/resources"
"github.com/clastix/kamaji/internal/resources/datastore"
)
var _ = Describe("DatastoreStorageConfig", func() {
var (
ctx context.Context
dsc *datastore.Config
tcp *kamajiv1alpha1.TenantControlPlane
ds *kamajiv1alpha1.DataStore
)
BeforeEach(func() {
ctx = context.Background()
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{},
}
ds = &kamajiv1alpha1.DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "datastore",
Namespace: "default",
},
}
Expect(kamajiv1alpha1.AddToScheme(scheme)).To(Succeed())
Expect(corev1.AddToScheme(scheme)).To(Succeed())
})
JustBeforeEach(func() {
fakeClient = fake.NewClientBuilder().
WithScheme(scheme).WithObjects(tcp).WithStatusSubresource(tcp).Build()
dsc = &datastore.Config{
Client: fakeClient,
ConnString: "",
DataStore: *ds,
}
})
When("TCP has no dataStoreSchema defined", func() {
It("should return an error", func() {
_, err := resources.Handle(ctx, dsc, tcp)
Expect(err).To(HaveOccurred())
})
})
When("TCP has dataStoreSchema set in spec", func() {
BeforeEach(func() {
tcp.Spec.DataStoreSchema = "custom-prefix"
})
It("should create the datastore secret with the schema name from the spec", func() {
op, err := resources.Handle(ctx, dsc, tcp)
Expect(err).ToNot(HaveOccurred())
Expect(op).To(Equal(controllerutil.OperationResultCreated))
secrets := &corev1.SecretList{}
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
Expect(secrets.Items).To(HaveLen(1))
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("custom-prefix")))
})
})
When("TCP has dataStoreSchema set in status, but not in spec", func() {
// this test case ensures that existing TCPs (created in a CRD version without
// the dataStoreSchema field) correctly adopt the spec field from the status.
It("should create the datastore secret with the correct schema name and update the TCP spec", func() {
By("updating the TCP status")
Expect(fakeClient.Get(ctx, client.ObjectKeyFromObject(tcp), tcp)).To(Succeed())
tcp.Status.Storage.Setup.Schema = "existing-schema-name"
Expect(fakeClient.Status().Update(ctx, tcp)).To(Succeed())
By("handling the resource")
op, err := resources.Handle(ctx, dsc, tcp)
Expect(err).ToNot(HaveOccurred())
Expect(op).To(Equal(controllerutil.OperationResultCreated))
By("checking the secret")
secrets := &corev1.SecretList{}
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
Expect(secrets.Items).To(HaveLen(1))
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("existing-schema-name")))
By("checking the TCP spec")
// we have to check the modified struct here (instead of retrieving the object
// via the fakeClient), as the TCP resource update is not done by the resources.
// Instead, the TCP controller will handle TCP updates after handling all resources
tcp.Spec.DataStoreSchema = "existing-schema-name"
})
})
})

View File

@@ -0,0 +1,23 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var (
fakeClient client.Client
scheme *runtime.Scheme = runtime.NewScheme()
)
func TestDatastore(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Datastore Suite")
}

View File

@@ -92,17 +92,18 @@ func (r *KubeadmConfigResource) mutate(ctx context.Context, tenantControlPlane *
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
params := kubeadm.Parameters{
TenantControlPlaneAddress: address,
TenantControlPlanePort: port,
TenantControlPlaneName: tenantControlPlane.GetName(),
TenantControlPlaneNamespace: tenantControlPlane.GetNamespace(),
TenantControlPlaneEndpoint: r.getControlPlaneEndpoint(tenantControlPlane.Spec.ControlPlane.Ingress, address, port),
TenantControlPlaneCertSANs: tenantControlPlane.Spec.NetworkProfile.CertSANs,
TenantControlPlanePodCIDR: tenantControlPlane.Spec.NetworkProfile.PodCIDR,
TenantControlPlaneServiceCIDR: tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
TenantControlPlaneVersion: tenantControlPlane.Spec.Kubernetes.Version,
ETCDs: r.ETCDs,
CertificatesDir: r.TmpDirectory,
TenantControlPlaneAddress: address,
TenantControlPlanePort: port,
TenantControlPlaneName: tenantControlPlane.GetName(),
TenantControlPlaneNamespace: tenantControlPlane.GetNamespace(),
TenantControlPlaneEndpoint: r.getControlPlaneEndpoint(tenantControlPlane.Spec.ControlPlane.Ingress, address, port),
TenantControlPlaneCertSANs: tenantControlPlane.Spec.NetworkProfile.CertSANs,
TenantControlPlaneClusterDomain: tenantControlPlane.Spec.NetworkProfile.ClusterDomain,
TenantControlPlanePodCIDR: tenantControlPlane.Spec.NetworkProfile.PodCIDR,
TenantControlPlaneServiceCIDR: tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
TenantControlPlaneVersion: tenantControlPlane.Spec.Kubernetes.Version,
ETCDs: r.ETCDs,
CertificatesDir: r.TmpDirectory,
}
config, err := kubeadm.CreateKubeadmInitConfiguration(params)

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -143,7 +144,6 @@ func (r *KubeadmPhase) GetKubeadmFunction(ctx context.Context, tcp *kamajiv1alph
defer func() { _ = os.Remove(tmp) }()
var caSecret corev1.Secret
if err = r.Client.Get(ctx, types.NamespacedName{Name: tcp.Status.Certificates.CA.SecretName, Namespace: tcp.Namespace}, &caSecret); err != nil {
return nil, err
}
@@ -164,7 +164,9 @@ func (r *KubeadmPhase) GetKubeadmFunction(ctx context.Context, tcp *kamajiv1alph
_ = os.WriteFile(fmt.Sprintf("%s/%s", tmp, i), kubeconfigValue, os.ModePerm)
}
if _, err = kubeconfig.EnsureAdminClusterRoleBinding(tmp, nil); err != nil {
if _, err = kubeconfig.EnsureAdminClusterRoleBinding(tmp, func(_ context.Context, _ clientset.Interface, _ clientset.Interface, duration time.Duration, duration2 time.Duration) (clientset.Interface, error) {
return kubeconfig.EnsureAdminClusterRoleBindingImpl(ctx, c, c, duration, duration2)
}); err != nil {
return nil, err
}

View File

@@ -0,0 +1,16 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestHandlers(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Handlers Suite")
}

View File

@@ -6,6 +6,7 @@ package handlers
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
"gomodules.xyz/jsonpatch/v2"
@@ -23,24 +24,17 @@ type TenantControlPlaneDefaults struct {
func (t TenantControlPlaneDefaults) OnCreate(object runtime.Object) AdmissionResponse {
return func(ctx context.Context, req admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
original := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
if len(tcp.Spec.DataStore) == 0 {
operations, err := utils.JSONPatch(tcp, func() {
tcp.Spec.DataStore = t.DefaultDatastore
})
if err != nil {
return nil, errors.Wrap(err, "cannot create patch responses upon Tenant Control Plane creation")
}
defaulted := original.DeepCopy()
t.defaultUnsetFields(defaulted)
return operations, nil
operations, err := utils.JSONPatch(original, defaulted)
if err != nil {
return nil, errors.Wrap(err, "cannot create patch responses upon Tenant Control Plane creation")
}
if tcp.Spec.ControlPlane.Deployment.Replicas == nil {
tcp.Spec.ControlPlane.Deployment.Replicas = pointer.To(int32(2))
}
return nil, nil
return operations, nil
}
}
@@ -48,22 +42,22 @@ func (t TenantControlPlaneDefaults) OnDelete(runtime.Object) AdmissionResponse {
return utils.NilOp()
}
func (t TenantControlPlaneDefaults) OnUpdate(object runtime.Object, oldObject runtime.Object) AdmissionResponse {
return func(ctx context.Context, req admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
newTCP, oldTCP := object.(*kamajiv1alpha1.TenantControlPlane), oldObject.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
func (t TenantControlPlaneDefaults) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse {
// all immutability requirements are handled trough CEL annotations on the TenantControlPlaneSpec type
return utils.NilOp()
}
if oldTCP.Spec.DataStore == newTCP.Spec.DataStore {
return nil, nil
}
func (t TenantControlPlaneDefaults) defaultUnsetFields(tcp *kamajiv1alpha1.TenantControlPlane) {
if len(tcp.Spec.DataStore) == 0 {
tcp.Spec.DataStore = t.DefaultDatastore
}
if len(newTCP.Spec.DataStore) == 0 {
return nil, fmt.Errorf("DataStore is a required field")
}
if tcp.Spec.ControlPlane.Deployment.Replicas == nil {
tcp.Spec.ControlPlane.Deployment.Replicas = pointer.To(int32(2))
}
if newTCP.Spec.ControlPlane.Deployment.Replicas == nil {
newTCP.Spec.ControlPlane.Deployment.Replicas = pointer.To(int32(2))
}
return nil, nil
if len(tcp.Spec.DataStoreSchema) == 0 {
dss := strings.ReplaceAll(fmt.Sprintf("%s_%s", tcp.GetNamespace(), tcp.GetName()), "-", "_")
tcp.Spec.DataStoreSchema = dss
}
}

View File

@@ -0,0 +1,78 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers_test
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gomodules.xyz/jsonpatch/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/webhook/handlers"
)
var _ = Describe("TCP Defaulting Webhook", func() {
var (
ctx context.Context
t handlers.TenantControlPlaneDefaults
tcp *kamajiv1alpha1.TenantControlPlane
)
BeforeEach(func() {
t = handlers.TenantControlPlaneDefaults{
DefaultDatastore: "etcd",
}
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{},
}
ctx = context.Background()
})
Describe("fields missing", func() {
It("should issue all required patches", func() {
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
Expect(ops).To(HaveLen(3))
})
It("should default the dataStore", func() {
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
Expect(ops).To(ContainElement(
jsonpatch.Operation{Operation: "add", Path: "/spec/dataStore", Value: "etcd"},
))
})
It("should default the dataStoreSchema to the expected value", func() {
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
Expect(ops).To(ContainElement(
jsonpatch.Operation{Operation: "add", Path: "/spec/dataStoreSchema", Value: "default_tcp"},
))
})
})
Describe("fields are already set", func() {
BeforeEach(func() {
tcp.Spec.DataStore = "etcd"
tcp.Spec.DataStoreSchema = "my_tcp"
tcp.Spec.ControlPlane.Deployment.Replicas = ptr.To(int32(2))
})
It("should not issue any patches", func() {
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
Expect(ops).To(BeEmpty())
})
})
})

View File

@@ -10,18 +10,16 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
func JSONPatch(obj client.Object, modifierFunc func()) ([]jsonpatch.Operation, error) {
original, err := json.Marshal(obj)
func JSONPatch(original, modified client.Object) ([]jsonpatch.Operation, error) {
originalJSON, err := json.Marshal(original)
if err != nil {
return nil, errors.Wrap(err, "cannot marshal input object")
return nil, errors.Wrap(err, "cannot marshal original object")
}
modifierFunc()
patched, err := json.Marshal(obj)
modifiedJSON, err := json.Marshal(modified)
if err != nil {
return nil, errors.Wrap(err, "cannot marshal patched object")
return nil, errors.Wrap(err, "cannot marshal modified object")
}
return jsonpatch.CreatePatch(original, patched)
return jsonpatch.CreatePatch(originalJSON, modifiedJSON)
}