mirror of
https://github.com/clastix/kamaji.git
synced 2026-03-01 01:00:39 +00:00
Compare commits
16 Commits
edge-24.9.
...
edge-24.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ca69e91f9 | ||
|
|
e4939f6dcb | ||
|
|
fcad29ddba | ||
|
|
cae3c6041f | ||
|
|
7e08b9a7ce | ||
|
|
a21f199847 | ||
|
|
7b89d69a1c | ||
|
|
8b71843325 | ||
|
|
489e0e1653 | ||
|
|
71b653eee9 | ||
|
|
96fc9149a0 | ||
|
|
3e4e45cd6e | ||
|
|
11bda430c6 | ||
|
|
293387e0d1 | ||
|
|
092eeb0274 | ||
|
|
f483e812a5 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
9
.github/workflows/ko-build.yml
vendored
9
.github/workflows/ko-build.yml
vendored
@@ -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
|
||||
|
||||
15
ADOPTERS.md
15
ADOPTERS.md
@@ -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
|
||||
|
||||
9
Makefile
9
Makefile
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
20
docs/content/enterprise-addons/index.md
Normal file
20
docs/content/enterprise-addons/index.md
Normal 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
|
||||
182
docs/content/enterprise-addons/ingress.md
Normal file
182
docs/content/enterprise-addons/ingress.md
Normal 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.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
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
|
||||
```
|
||||
BIN
docs/content/images/kamaji-addon-ingress-ic-dark.png
Normal file
BIN
docs/content/images/kamaji-addon-ingress-ic-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
BIN
docs/content/images/kamaji-addon-ingress-ic.png
Normal file
BIN
docs/content/images/kamaji-addon-ingress-ic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/content/images/kamaji-addon-ingress-lb-dark.png
Normal file
BIN
docs/content/images/kamaji-addon-ingress-lb-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/content/images/kamaji-addon-ingress-lb.png
Normal file
BIN
docs/content/images/kamaji-addon-ingress-lb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
4
go.mod
@@ -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
8
go.sum
@@ -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=
|
||||
|
||||
@@ -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{
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
115
internal/resources/datastore/datastore_storage_config_test.go
Normal file
115
internal/resources/datastore/datastore_storage_config_test.go
Normal 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"
|
||||
})
|
||||
})
|
||||
})
|
||||
23
internal/resources/datastore/datastore_suite_test.go
Normal file
23
internal/resources/datastore/datastore_suite_test.go
Normal 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")
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
16
internal/webhook/handlers/handlers_suite_test.go
Normal file
16
internal/webhook/handlers/handlers_suite_test.go
Normal 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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
78
internal/webhook/handlers/tcp_defaults_test.go
Normal file
78
internal/webhook/handlers/tcp_defaults_test.go
Normal 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())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user