Compare commits

..

1 Commits

Author SHA1 Message Date
Andrei Kvapil
156c1e8524 PoC: Move workloadmonitors to controller
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-04 19:31:31 +01:00
132 changed files with 2425 additions and 4850 deletions

View File

@@ -33,9 +33,6 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Run unit tests
run: make unit-tests
- name: Set up Docker config
run: |
if [ -d ~/.docker ]; then

View File

@@ -1,4 +1,4 @@
.PHONY: manifests repos assets unit-tests helm-unit-tests
.PHONY: manifests repos assets
build-deps:
@command -V find docker skopeo jq gh helm > /dev/null
@@ -46,11 +46,6 @@ test:
make -C packages/core/testing apply
make -C packages/core/testing test
unit-tests: helm-unit-tests
helm-unit-tests:
hack/helm-unit-tests.sh
prepare-env:
make -C packages/core/testing apply
make -C packages/core/testing prepare-cluster

View File

@@ -59,6 +59,10 @@ type CozystackResourceDefinitionSpec struct {
// Dashboard configuration for this resource
Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"`
// WorkloadMonitors configuration for this resource
// List of WorkloadMonitor templates to be created for each application instance
WorkloadMonitors []WorkloadMonitorTemplate `json:"workloadMonitors,omitempty"`
}
type CozystackResourceDefinitionChart struct {
@@ -110,17 +114,18 @@ type CozystackResourceDefinitionRelease struct {
// - {{ .namespace }}: The namespace of the resource being processed
//
// Example YAML:
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
//
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
type CozystackResourceDefinitionResourceSelector struct {
metav1.LabelSelector `json:",inline"`
// ResourceNames is a list of resource names to match
@@ -191,3 +196,47 @@ type CozystackResourceDefinitionDashboard struct {
// +optional
Module bool `json:"module,omitempty"`
}
// ---- WorkloadMonitor types ----
// WorkloadMonitorTemplate defines a template for creating WorkloadMonitor resources
// for application instances. Fields support Go template syntax with the following variables:
// - {{ .Release.Name }}: The name of the Helm release
// - {{ .Release.Namespace }}: The namespace of the Helm release
// - {{ .Chart.Version }}: The version of the Helm chart
// - {{ .Values.<path> }}: Any value from the Helm values
type WorkloadMonitorTemplate struct {
// Name is the name of the WorkloadMonitor.
// Supports Go template syntax (e.g., "{{ .Release.Name }}-keeper")
// +required
Name string `json:"name"`
// Kind specifies the kind of the workload (e.g., "postgres", "kafka")
// +required
Kind string `json:"kind"`
// Type specifies the type of the workload (e.g., "postgres", "zookeeper")
// +required
Type string `json:"type"`
// Selector is a map of label key-value pairs for matching workloads.
// Supports Go template syntax in values (e.g., "app.kubernetes.io/instance: {{ .Release.Name }}")
// +required
Selector map[string]string `json:"selector"`
// Replicas is a Go template expression that evaluates to the desired number of replicas.
// Example: "{{ .Values.replicas }}" or "{{ .Values.clickhouseKeeper.replicas }}"
// +optional
Replicas string `json:"replicas,omitempty"`
// MinReplicas is a Go template expression that evaluates to the minimum number of replicas.
// Example: "1" or "{{ div .Values.replicas 2 | add1 }}"
// +optional
MinReplicas string `json:"minReplicas,omitempty"`
// Condition is a Go template expression that must evaluate to "true" for the monitor to be created.
// Example: "{{ .Values.clickhouseKeeper.enabled }}"
// If empty, the monitor is always created.
// +optional
Condition string `json:"condition,omitempty"`
}

View File

@@ -244,6 +244,13 @@ func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDe
*out = new(CozystackResourceDefinitionDashboard)
(*in).DeepCopyInto(*out)
}
if in.WorkloadMonitors != nil {
in, out := &in.WorkloadMonitors, &out.WorkloadMonitors
*out = make([]WorkloadMonitorTemplate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec.
@@ -461,6 +468,28 @@ func (in *WorkloadMonitorStatus) DeepCopy() *WorkloadMonitorStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadMonitorTemplate) DeepCopyInto(out *WorkloadMonitorTemplate) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitorTemplate.
func (in *WorkloadMonitorTemplate) DeepCopy() *WorkloadMonitorTemplate {
if in == nil {
return nil
}
out := new(WorkloadMonitorTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadStatus) DeepCopyInto(out *WorkloadStatus) {
*out = *in

View File

@@ -192,6 +192,14 @@ func main() {
os.Exit(1)
}
if err = (&controller.WorkloadMonitorFromCRDReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "WorkloadMonitorFromCRD")
os.Exit(1)
}
if err = (&controller.WorkloadReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),

View File

@@ -80,41 +80,58 @@ EOF
# Wait for the machine deployment to scale to 2 replicas (timeout after 1 minute)
kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=1m --for=jsonpath='{.status.replicas}'=2
# Get the admin kubeconfig and save it to a file
kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig-${test_name}
kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig
# Update the kubeconfig to use localhost for the API server
yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig-${test_name}
yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig
# Set up port forwarding to the Kubernetes API server for a 200 second timeout
bash -c 'timeout 300s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
# Verify the Kubernetes version matches what we expect (retry for up to 20 seconds)
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
# Wait for the nodes to be ready (timeout after 2 minutes)
timeout 3m bash -c '
until [ "$(kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
until [ "$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
sleep 2
done
'
# Verify the nodes are ready
kubectl --kubeconfig tenantkubeconfig-${test_name} wait node --all --timeout=2m --for=condition=Ready
kubectl --kubeconfig tenantkubeconfig-${test_name} get nodes -o wide
kubectl --kubeconfig tenantkubeconfig wait node --all --timeout=2m --for=condition=Ready
kubectl --kubeconfig tenantkubeconfig get nodes -o wide
# Verify the kubelet version matches what we expect
versions=$(kubectl --kubeconfig "tenantkubeconfig-${test_name}" \
get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
versions=$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
node_ok=true
case "$k8s_version" in
v1.32*)
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
;;
esac
for v in $versions; do
case "$v" in
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
# acceptable
case "$k8s_version" in
v1.32|v1.32.*)
case "$v" in
v1.32 | v1.32.* | v1.32-* | v1.33 | v1.33.* | v1.33-*)
;;
*)
node_ok=false
break
;;
esac
;;
*)
node_ok=false
break
case "$v" in
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
;;
*)
node_ok=false
break
;;
esac
;;
esac
done

View File

@@ -118,7 +118,7 @@ EOF
}
@test "Check Cozystack API service" {
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io apiservices/v1alpha1.core.cozystack.io --timeout=2m
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io --timeout=2m
}
@test "Configure Tenant and wait for applications" {

View File

@@ -9,7 +9,6 @@
@test "Test OpenAPI v3 endpoint" {
kubectl get -v7 --raw '/openapi/v3/apis/apps.cozystack.io/v1alpha1' > /dev/null
kubectl get -v7 --raw '/openapi/v3/apis/core.cozystack.io/v1alpha1' > /dev/null
}
@test "Test OpenAPI v2 endpoint (protobuf)" {
@@ -19,26 +18,3 @@
curl -sS --fail 'http://localhost:21234/openapi/v2?timeout=32s' -H 'Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf' > /dev/null
)
}
@test "Test kinds" {
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
if [ "$val" != "TenantList" ]; then
echo "Expected kind to be TenantList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind')
if [ "$val" != "Tenant" ]; then
echo "Expected kind to be Tenant, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind')
if [ "$val" != "IngressList" ]; then
echo "Expected kind to be IngressList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind')
if [ "$val" != "Ingress" ]; then
echo "Expected kind to be Ingress, got $val"
exit 1
fi
}

View File

@@ -1,59 +0,0 @@
#!/bin/sh
set -eu
# Script to run unit tests for all Helm charts.
# It iterates through directories in packages/apps, packages/extra,
# packages/system, and packages/library and runs the 'test' Makefile
# target if it exists.
FAILED_DIRS_FILE="$(mktemp)"
trap 'rm -f "$FAILED_DIRS_FILE"' EXIT
tests_found=0
check_and_run_test() {
dir="$1"
makefile="$dir/Makefile"
if [ ! -f "$makefile" ]; then
return 0
fi
if make -C "$dir" -n test >/dev/null 2>&1; then
echo "Running tests in $dir"
tests_found=$((tests_found + 1))
if ! make -C "$dir" test; then
printf '%s\n' "$dir" >> "$FAILED_DIRS_FILE"
return 1
fi
fi
return 0
}
for package_dir in packages/apps packages/extra packages/system packages/library; do
if [ ! -d "$package_dir" ]; then
echo "Warning: Directory $package_dir does not exist, skipping..." >&2
continue
fi
for dir in "$package_dir"/*; do
[ -d "$dir" ] || continue
check_and_run_test "$dir" || true
done
done
if [ "$tests_found" -eq 0 ]; then
echo "No directories with 'test' Makefile targets found."
exit 0
fi
if [ -s "$FAILED_DIRS_FILE" ]; then
echo "ERROR: Tests failed in the following directories:" >&2
while IFS= read -r dir; do
echo " - $dir" >&2
done < "$FAILED_DIRS_FILE"
exit 1
fi
echo "All Helm unit tests passed."

View File

@@ -11,7 +11,6 @@ import (
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD
@@ -46,24 +45,15 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
}
}
// Build schema with multilineString for string fields without enum
l := log.FromContext(ctx)
schema, err := buildMultilineStringSchema(crd.Spec.Application.OpenAPISchema)
if err != nil {
// If schema parsing fails, log the error and use an empty schema
l.Error(err, "failed to build multiline string schema, using empty schema", "crd", crd.Name)
schema = map[string]any{}
}
spec := map[string]any{
"customizationId": customizationID,
"hidden": hidden,
"sort": sort,
"schema": schema,
"schema": map[string]any{}, // {}
"strategy": "merge",
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
return err
}
@@ -83,94 +73,3 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
})
return err
}
// buildMultilineStringSchema parses OpenAPI schema and creates schema with multilineString
// for all string fields inside spec that don't have enum
func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) {
if openAPISchema == "" {
return map[string]any{}, nil
}
var root map[string]any
if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil {
return nil, fmt.Errorf("cannot parse openAPISchema: %w", err)
}
props, _ := root["properties"].(map[string]any)
if props == nil {
return map[string]any{}, nil
}
schema := map[string]any{
"properties": map[string]any{},
}
// Process spec properties recursively
processSpecProperties(props, schema["properties"].(map[string]any))
return schema, nil
}
// processSpecProperties recursively processes spec properties and adds multilineString type
// for string fields without enum
func processSpecProperties(props map[string]any, schemaProps map[string]any) {
for pname, raw := range props {
sub, ok := raw.(map[string]any)
if !ok {
continue
}
typ, _ := sub["type"].(string)
switch typ {
case "string":
// Check if this string field has enum
if !hasEnum(sub) {
// Add multilineString type for this field
if schemaProps[pname] == nil {
schemaProps[pname] = map[string]any{}
}
fieldSchema := schemaProps[pname].(map[string]any)
fieldSchema["type"] = "multilineString"
}
case "object":
// Recursively process nested objects
if childProps, ok := sub["properties"].(map[string]any); ok {
fieldSchema, ok := schemaProps[pname].(map[string]any)
if !ok {
fieldSchema = map[string]any{}
schemaProps[pname] = fieldSchema
}
nestedSchemaProps, ok := fieldSchema["properties"].(map[string]any)
if !ok {
nestedSchemaProps = map[string]any{}
fieldSchema["properties"] = nestedSchemaProps
}
processSpecProperties(childProps, nestedSchemaProps)
}
case "array":
// Check if array items are objects with properties
if items, ok := sub["items"].(map[string]any); ok {
if itemProps, ok := items["properties"].(map[string]any); ok {
// Create array item schema
fieldSchema, ok := schemaProps[pname].(map[string]any)
if !ok {
fieldSchema = map[string]any{}
schemaProps[pname] = fieldSchema
}
itemSchema, ok := fieldSchema["items"].(map[string]any)
if !ok {
itemSchema = map[string]any{}
fieldSchema["items"] = itemSchema
}
itemSchemaProps, ok := itemSchema["properties"].(map[string]any)
if !ok {
itemSchemaProps = map[string]any{}
itemSchema["properties"] = itemSchemaProps
}
processSpecProperties(itemProps, itemSchemaProps)
}
}
}
}
}

View File

@@ -1,155 +0,0 @@
package dashboard
import (
"encoding/json"
"testing"
)
func TestBuildMultilineStringSchema(t *testing.T) {
// Test OpenAPI schema with various field types
openAPISchema := `{
"properties": {
"simpleString": {
"type": "string",
"description": "A simple string field"
},
"stringWithEnum": {
"type": "string",
"enum": ["option1", "option2"],
"description": "String with enum should be skipped"
},
"numberField": {
"type": "number",
"description": "Number field should be skipped"
},
"nestedObject": {
"type": "object",
"properties": {
"nestedString": {
"type": "string",
"description": "Nested string should get multilineString"
},
"nestedStringWithEnum": {
"type": "string",
"enum": ["a", "b"],
"description": "Nested string with enum should be skipped"
}
}
},
"arrayOfObjects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"itemString": {
"type": "string",
"description": "String in array item"
}
}
}
}
}
}`
schema, err := buildMultilineStringSchema(openAPISchema)
if err != nil {
t.Fatalf("buildMultilineStringSchema failed: %v", err)
}
// Marshal to JSON for easier inspection
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
t.Fatalf("Failed to marshal schema: %v", err)
}
t.Logf("Generated schema:\n%s", schemaJSON)
// Verify that simpleString has multilineString type
props, ok := schema["properties"].(map[string]any)
if !ok {
t.Fatal("schema.properties is not a map")
}
// Check simpleString
simpleString, ok := props["simpleString"].(map[string]any)
if !ok {
t.Fatal("simpleString not found in properties")
}
if simpleString["type"] != "multilineString" {
t.Errorf("simpleString should have type multilineString, got %v", simpleString["type"])
}
// Check stringWithEnum should not be present (or should not have multilineString)
if stringWithEnum, ok := props["stringWithEnum"].(map[string]any); ok {
if stringWithEnum["type"] == "multilineString" {
t.Error("stringWithEnum should not have multilineString type")
}
}
// Check numberField should not be present
if numberField, ok := props["numberField"].(map[string]any); ok {
if numberField["type"] != nil {
t.Error("numberField should not have any type override")
}
}
// Check nested object
nestedObject, ok := props["nestedObject"].(map[string]any)
if !ok {
t.Fatal("nestedObject not found in properties")
}
nestedProps, ok := nestedObject["properties"].(map[string]any)
if !ok {
t.Fatal("nestedObject.properties is not a map")
}
// Check nestedString
nestedString, ok := nestedProps["nestedString"].(map[string]any)
if !ok {
t.Fatal("nestedString not found in nestedObject.properties")
}
if nestedString["type"] != "multilineString" {
t.Errorf("nestedString should have type multilineString, got %v", nestedString["type"])
}
// Check array of objects
arrayOfObjects, ok := props["arrayOfObjects"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects not found in properties")
}
items, ok := arrayOfObjects["items"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects.items is not a map")
}
itemProps, ok := items["properties"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects.items.properties is not a map")
}
itemString, ok := itemProps["itemString"].(map[string]any)
if !ok {
t.Fatal("itemString not found in arrayOfObjects.items.properties")
}
if itemString["type"] != "multilineString" {
t.Errorf("itemString should have type multilineString, got %v", itemString["type"])
}
}
func TestBuildMultilineStringSchemaEmpty(t *testing.T) {
schema, err := buildMultilineStringSchema("")
if err != nil {
t.Fatalf("buildMultilineStringSchema failed on empty string: %v", err)
}
if len(schema) != 0 {
t.Errorf("Expected empty schema for empty input, got %v", schema)
}
}
func TestBuildMultilineStringSchemaInvalidJSON(t *testing.T) {
schema, err := buildMultilineStringSchema("{invalid json")
if err == nil {
t.Error("Expected error for invalid JSON")
}
if schema != nil {
t.Errorf("Expected nil schema for invalid JSON, got %v", schema)
}
}

View File

@@ -44,9 +44,6 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
if flags.Secrets {
tabs = append(tabs, secretsTab(kind))
}
if prefix, ok := vncTabPrefix(kind); ok {
tabs = append(tabs, vncTab(prefix))
}
tabs = append(tabs, yamlTab(plural))
// Use unified factory creation
@@ -153,27 +150,6 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
}),
paramsList,
}
if kind == "VirtualPrivateCloud" {
rightColStack = append(rightColStack,
antdFlexVertical("vpc-subnets-block", 4, []any{
antdText("vpc-subnets-label", true, "Subnets", nil),
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "vpc-subnets-table",
"baseprefix": "/openapi-ui",
"clusterNamePartOfUrl": "{2}",
"customizationId": "virtualprivatecloud-subnets",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/configmaps",
"fieldSelector": map[string]any{
"metadata.name": "virtualprivatecloud-{6}-subnets",
},
"pathToItems": []any{"items"},
},
},
}),
)
}
return map[string]any{
"key": "details",
@@ -245,7 +221,7 @@ func workloadsTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -270,7 +246,7 @@ func servicesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -296,7 +272,7 @@ func ingressesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-networking.k8s.io.v1.ingresses",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -317,12 +293,12 @@ func secretsTab(kind string) map[string]any {
"type": "EnrichedTable",
"data": map[string]any{
"id": "secrets-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -355,36 +331,6 @@ func yamlTab(plural string) map[string]any {
}
}
func vncTabPrefix(kind string) (string, bool) {
switch kind {
case "VirtualMachine":
return "virtual-machine", true
case "VMInstance":
return "vm-instance", true
default:
return "", false
}
}
func vncTab(prefix string) map[string]any {
return map[string]any{
"key": "vnc",
"label": "VNC",
"children": []any{
map[string]any{
"type": "VMVNC",
"data": map[string]any{
"id": "vm-vnc",
"cluster": "{2}",
"namespace": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
"substractHeight": float64(400),
"vmName": fmt.Sprintf("%s-{reqsJsonPath[0]['.metadata.name']['-']}", prefix),
},
},
},
}
}
// ---------------- OpenAPI → Right column ----------------
func buildOpenAPIParamsBlocks(schemaJSON string, keysOrder [][]string) []any {

View File

@@ -15,7 +15,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
managerpkg "sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -54,19 +53,10 @@ func NewManager(c client.Client, scheme *runtime.Scheme) *Manager {
}
func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
if err := ctrl.NewControllerManagedBy(mgr).
return ctrl.NewControllerManagedBy(mgr).
Named("dashboard-reconciler").
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(m); err != nil {
return err
}
return mgr.Add(managerpkg.RunnableFunc(func(ctx context.Context) error {
if !mgr.GetCache().WaitForCacheSync(ctx) {
return fmt.Errorf("dashboard static resources cache sync failed")
}
return m.ensureStaticResources(ctx)
}))
Complete(m)
}
func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

View File

@@ -122,7 +122,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das
}
}
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecrets" {
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" {
data["additionalPrinterColumnsTrimLengths"] = []any{
map[string]any{
"key": "Name",
@@ -1046,15 +1046,6 @@ func createConverterBytesColumn(name, jsonPath string) map[string]any {
}
}
// createFlatMapColumn creates a flatMap column that expands a map into separate rows
func createFlatMapColumn(name, jsonPath string) map[string]any {
return map[string]any{
"name": name,
"type": "flatMap",
"jsonPath": jsonPath,
}
}
// ---------------- Factory UI helper functions ----------------
// labelsEditor creates a Labels editor component

View File

@@ -173,22 +173,14 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createStringColumn("OBSERVED", ".status.observedReplicas"),
}),
// Factory details v1alpha1 core cozystack io tenantsecrets
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecrets", []any{
// Factory details v1alpha1 core cozystack io tenantsecretstables
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createFlatMapColumn("Data", ".data"),
createStringColumn("Key", "_flatMapData_Key"),
createSecretBase64Column("Value", "._flatMapData_Value"),
createStringColumn("Key", ".data.key"),
createSecretBase64Column("Value", ".data.value"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Virtual private cloud subnets
createCustomColumnsOverride("virtualprivatecloud-subnets", []any{
createFlatMapColumn("Data", ".data"),
createStringColumn("Subnet Parameters", "_flatMapData_Key"),
createStringColumn("Values", "_flatMapData_Value"),
}),
// Factory ingress details rules
createCustomColumnsOverride("factory-kube-ingress-details-rules", []any{
createStringColumn("Host", ".host"),
@@ -1063,7 +1055,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-kube-service-details-endpointslice",
"fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices",
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}",
},
"pathToItems": ".items[*].endpoints",
@@ -1404,7 +1396,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloads",
"fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads",
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}",
},
"pathToItems": []any{"items"},

View File

@@ -0,0 +1,439 @@
package controller
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"text/template"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// WorkloadMonitorFromCRDReconciler reconciles HelmReleases and creates WorkloadMonitors
// based on CozystackResourceDefinition templates
type WorkloadMonitorFromCRDReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch
// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=get;list;watch
// +kubebuilder:rbac:groups=cozystack.io,resources=workloadmonitors,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch
const (
WorkloadMonitorOwnerLabel = "workloadmonitor.cozystack.io/owned-by-crd"
WorkloadMonitorSourceLabel = "workloadmonitor.cozystack.io/helm-release"
)
// Reconcile processes HelmRelease resources and creates corresponding WorkloadMonitors
func (r *WorkloadMonitorFromCRDReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Get the HelmRelease
hr := &helmv2.HelmRelease{}
if err := r.Get(ctx, req.NamespacedName, hr); err != nil {
if errors.IsNotFound(err) {
// HelmRelease deleted - cleanup will be handled by owner references
return ctrl.Result{}, nil
}
logger.Error(err, "unable to fetch HelmRelease")
return ctrl.Result{}, err
}
// Skip system HelmReleases
if strings.HasPrefix(hr.Name, "tenant-") {
return ctrl.Result{}, nil
}
// Find the matching CozystackResourceDefinition
crd, err := r.findCRDForHelmRelease(ctx, hr)
if err != nil {
if errors.IsNotFound(err) {
// No CRD found for this HelmRelease - skip
logger.V(1).Info("No CozystackResourceDefinition found for HelmRelease", "name", hr.Name)
return ctrl.Result{}, nil
}
logger.Error(err, "unable to find CozystackResourceDefinition")
return ctrl.Result{}, err
}
// If CRD doesn't have WorkloadMonitors, cleanup any existing ones we created
if len(crd.Spec.WorkloadMonitors) == 0 {
if err := r.cleanupWorkloadMonitors(ctx, hr); err != nil {
logger.Error(err, "failed to cleanup WorkloadMonitors")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// Get the HelmRelease values for template rendering
values, err := r.getHelmReleaseValues(ctx, hr)
if err != nil {
logger.Error(err, "unable to get HelmRelease values")
return ctrl.Result{}, err
}
// Create/update WorkloadMonitors based on templates
if err := r.reconcileWorkloadMonitors(ctx, hr, crd, values); err != nil {
logger.Error(err, "failed to reconcile WorkloadMonitors")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// findCRDForHelmRelease finds the CozystackResourceDefinition for a given HelmRelease
func (r *WorkloadMonitorFromCRDReconciler) findCRDForHelmRelease(ctx context.Context, hr *helmv2.HelmRelease) (*cozyv1alpha1.CozystackResourceDefinition, error) {
// List all CozystackResourceDefinitions
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := r.List(ctx, &crdList); err != nil {
return nil, err
}
// Match by chart name and prefix
for i := range crdList.Items {
crd := &crdList.Items[i]
if crd.Spec.Release.Chart.Name == hr.Spec.Chart.Spec.Chart {
// Check if HelmRelease name matches the prefix
if strings.HasPrefix(hr.Name, crd.Spec.Release.Prefix) {
return crd, nil
}
}
}
return nil, errors.NewNotFound(schema.GroupResource{Group: "cozystack.io", Resource: "cozystackresourcedefinitions"}, "")
}
// getHelmReleaseValues extracts the values from HelmRelease spec
func (r *WorkloadMonitorFromCRDReconciler) getHelmReleaseValues(ctx context.Context, hr *helmv2.HelmRelease) (map[string]interface{}, error) {
if hr.Spec.Values == nil {
return make(map[string]interface{}), nil
}
// Convert apiextensionsv1.JSON to map
values := make(map[string]interface{})
if err := json.Unmarshal(hr.Spec.Values.Raw, &values); err != nil {
return nil, fmt.Errorf("failed to unmarshal values: %w", err)
}
return values, nil
}
// reconcileWorkloadMonitors creates or updates WorkloadMonitors based on CRD templates
func (r *WorkloadMonitorFromCRDReconciler) reconcileWorkloadMonitors(
ctx context.Context,
hr *helmv2.HelmRelease,
crd *cozyv1alpha1.CozystackResourceDefinition,
values map[string]interface{},
) error {
logger := log.FromContext(ctx)
// Get chart version from HelmRelease
chartVersion := ""
if hr.Status.History != nil && len(hr.Status.History) > 0 {
chartVersion = hr.Status.History[0].ChartVersion
}
// Template context
templateData := map[string]interface{}{
"Release": map[string]interface{}{
"Name": hr.Name,
"Namespace": hr.Namespace,
},
"Chart": map[string]interface{}{
"Version": chartVersion,
},
"Values": values,
}
// Track which monitors we should have
expectedMonitors := make(map[string]bool)
// Process each WorkloadMonitor template
for _, tmpl := range crd.Spec.WorkloadMonitors {
// Check condition
if tmpl.Condition != "" {
shouldCreate, err := evaluateCondition(tmpl.Condition, templateData)
if err != nil {
logger.Error(err, "failed to evaluate condition", "template", tmpl.Name, "condition", tmpl.Condition)
continue
}
if !shouldCreate {
logger.V(1).Info("Skipping WorkloadMonitor due to condition", "template", tmpl.Name)
continue
}
}
// Render monitor name
monitorName, err := renderTemplate(tmpl.Name, templateData)
if err != nil {
logger.Error(err, "failed to render monitor name", "template", tmpl.Name)
continue
}
expectedMonitors[monitorName] = true
// Render selector values
selector := make(map[string]string)
for key, valueTmpl := range tmpl.Selector {
renderedValue, err := renderTemplate(valueTmpl, templateData)
if err != nil {
logger.Error(err, "failed to render selector value", "key", key, "template", valueTmpl)
continue
}
selector[key] = renderedValue
}
// Render replicas
var replicas *int32
if tmpl.Replicas != "" {
replicasStr, err := renderTemplate(tmpl.Replicas, templateData)
if err != nil {
logger.Error(err, "failed to render replicas", "template", tmpl.Replicas)
} else {
if replicasInt, err := strconv.ParseInt(replicasStr, 10, 32); err == nil {
replicas = pointer.Int32(int32(replicasInt))
}
}
}
// Render minReplicas
var minReplicas *int32
if tmpl.MinReplicas != "" {
minReplicasStr, err := renderTemplate(tmpl.MinReplicas, templateData)
if err != nil {
logger.Error(err, "failed to render minReplicas", "template", tmpl.MinReplicas)
} else {
if minReplicasInt, err := strconv.ParseInt(minReplicasStr, 10, 32); err == nil {
minReplicas = pointer.Int32(int32(minReplicasInt))
}
}
}
// Create or update WorkloadMonitor
monitor := &cozyv1alpha1.WorkloadMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: monitorName,
Namespace: hr.Namespace,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, monitor, func() error {
// Set labels
if monitor.Labels == nil {
monitor.Labels = make(map[string]string)
}
monitor.Labels[WorkloadMonitorOwnerLabel] = "true"
monitor.Labels[WorkloadMonitorSourceLabel] = hr.Name
// Set owner reference to HelmRelease for automatic cleanup
if err := controllerutil.SetControllerReference(hr, monitor, r.Scheme); err != nil {
return err
}
// Update spec
monitor.Spec.Selector = selector
monitor.Spec.Kind = tmpl.Kind
monitor.Spec.Type = tmpl.Type
monitor.Spec.Version = chartVersion
monitor.Spec.Replicas = replicas
monitor.Spec.MinReplicas = minReplicas
return nil
})
if err != nil {
logger.Error(err, "failed to create/update WorkloadMonitor", "name", monitorName)
continue
}
logger.V(1).Info("WorkloadMonitor reconciled", "name", monitorName)
}
// Cleanup WorkloadMonitors that are no longer in templates
if err := r.cleanupUnexpectedMonitors(ctx, hr, expectedMonitors); err != nil {
logger.Error(err, "failed to cleanup unexpected WorkloadMonitors")
return err
}
return nil
}
// cleanupWorkloadMonitors removes all WorkloadMonitors created for a HelmRelease
func (r *WorkloadMonitorFromCRDReconciler) cleanupWorkloadMonitors(ctx context.Context, hr *helmv2.HelmRelease) error {
return r.cleanupUnexpectedMonitors(ctx, hr, make(map[string]bool))
}
// cleanupUnexpectedMonitors removes WorkloadMonitors that are no longer expected
func (r *WorkloadMonitorFromCRDReconciler) cleanupUnexpectedMonitors(
ctx context.Context,
hr *helmv2.HelmRelease,
expectedMonitors map[string]bool,
) error {
logger := log.FromContext(ctx)
// List all WorkloadMonitors in the namespace that we created
var monitorList cozyv1alpha1.WorkloadMonitorList
labelSelector := labels.SelectorFromSet(labels.Set{
WorkloadMonitorOwnerLabel: "true",
WorkloadMonitorSourceLabel: hr.Name,
})
if err := r.List(ctx, &monitorList,
client.InNamespace(hr.Namespace),
client.MatchingLabelsSelector{Selector: labelSelector},
); err != nil {
return err
}
// Delete monitors that are not expected
for i := range monitorList.Items {
monitor := &monitorList.Items[i]
if !expectedMonitors[monitor.Name] {
logger.Info("Deleting unexpected WorkloadMonitor", "name", monitor.Name)
if err := r.Delete(ctx, monitor); err != nil && !errors.IsNotFound(err) {
logger.Error(err, "failed to delete WorkloadMonitor", "name", monitor.Name)
}
}
}
return nil
}
// renderTemplate renders a Go template string with the given data
func renderTemplate(tmplStr string, data interface{}) (string, error) {
// Check if it's already a simple value (no template markers)
if !strings.Contains(tmplStr, "{{") {
return tmplStr, nil
}
// Add Sprig functions for compatibility with Helm templates
tmpl, err := template.New("").Funcs(getTemplateFuncs()).Parse(tmplStr)
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
return strings.TrimSpace(buf.String()), nil
}
// evaluateCondition evaluates a template condition (should return "true" or non-empty for true)
func evaluateCondition(condition string, data interface{}) (bool, error) {
result, err := renderTemplate(condition, data)
if err != nil {
return false, err
}
// Check for truthy values
result = strings.TrimSpace(strings.ToLower(result))
return result == "true" || result == "1" || result == "yes", nil
}
// getTemplateFuncs returns template functions compatible with Helm
func getTemplateFuncs() template.FuncMap {
return template.FuncMap{
// Math functions
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b int) int { return a * b },
"div": func(a, b int) int {
if b == 0 {
return 0
}
return a / b
},
"add1": func(a int) int { return a + 1 },
"sub1": func(a int) int { return a - 1 },
// String functions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"trim": strings.TrimSpace,
"trimAll": func(cutset, s string) string { return strings.Trim(s, cutset) },
"replace": func(old, new string, n int, s string) string { return strings.Replace(s, old, new, n) },
// Logic functions
"default": func(defaultVal, val interface{}) interface{} {
if val == nil || val == "" {
return defaultVal
}
return val
},
"empty": func(val interface{}) bool {
return val == nil || val == ""
},
"not": func(val bool) bool {
return !val
},
}
}
// SetupWithManager sets up the controller with the Manager
func (r *WorkloadMonitorFromCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Named("workloadmonitor-from-crd-controller").
For(&helmv2.HelmRelease{}).
Owns(&cozyv1alpha1.WorkloadMonitor{}).
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(r.mapCRDToHelmReleases),
).
Complete(r)
}
// mapCRDToHelmReleases maps CRD changes to HelmRelease reconcile requests
func (r *WorkloadMonitorFromCRDReconciler) mapCRDToHelmReleases(ctx context.Context, obj client.Object) []reconcile.Request {
crd, ok := obj.(*cozyv1alpha1.CozystackResourceDefinition)
if !ok {
return nil
}
// List all HelmReleases
var hrList helmv2.HelmReleaseList
if err := r.List(ctx, &hrList); err != nil {
return nil
}
var requests []reconcile.Request
for i := range hrList.Items {
hr := &hrList.Items[i]
// Skip tenant HelmReleases
if strings.HasPrefix(hr.Name, "tenant-") {
continue
}
// Match by chart name and prefix
if crd.Spec.Release.Chart.Name == hr.Spec.Chart.Spec.Chart {
if strings.HasPrefix(hr.Name, crd.Spec.Release.Prefix) {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: hr.Name,
Namespace: hr.Namespace,
},
})
}
}
}
return requests
}

View File

@@ -1,28 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: clickhouse
type: clickhouse
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}
{{- if .Values.clickhouseKeeper.enabled }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-keeper
spec:
replicas: {{ .Values.clickhouseKeeper.replicas }}
minReplicas: 1
kind: clickhouse
type: clickhouse
selector:
app: {{ $.Release.Name }}-keeper
version: {{ $.Chart.Version }}
{{- end }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: ferretdb
type: ferretdb
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,20 +0,0 @@
{{- if .Values.monitoring.enabled }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ .Release.Name }}
labels:
app.kubernetes.io/name: foundationdb
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.cluster.processCounts.storage }}
minReplicas: {{ include "foundationdb.minReplicas" . }}
kind: foundationdb
type: foundationdb
selector:
foundationdb.org/fdb-cluster-name: {{ .Release.Name }}
foundationdb.org/fdb-process-class: storage
version: {{ .Chart.Version }}
{{- end }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:b7633717cd7449c0042ae92d8ca9b36e4d69566561f5c7d44e21058e7d05c6d5
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af

View File

@@ -1,39 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-haproxy
spec:
replicas: {{ .Values.haproxy.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app: {{ $.Release.Name }}-haproxy
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-nginx
spec:
replicas: {{ .Values.nginx.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app: {{ $.Release.Name }}-nginx-cache
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,30 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: kafka
type: kafka
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
app.kubernetes.io/name: kafka
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-zookeeper
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: kafka
type: zookeeper
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
app.kubernetes.io/name: zookeeper
version: {{ $.Chart.Version }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c8b08084a86251cdd18e237de89b695bca0e4f7eb1f1f6ddc2b903b4d74ea5ff

View File

@@ -182,33 +182,6 @@ metadata:
spec:
template:
spec:
files:
- path: /usr/bin/update-k8s.sh
owner: root:root
permissions: "0755"
content: |
#!/usr/bin/env bash
set -euo pipefail
# Expected to be passed in via preKubeadmCommands
: "${KUBELET_VERSION:?KUBELET_VERSION must be set, e.g. v1.31.0}"
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64) ARCH=amd64 ;;
aarch64) ARCH=arm64 ;;
esac
# Use your internal mirror here for real-world use.
BASE_URL="https://dl.k8s.io/release/${KUBELET_VERSION}/bin/linux/${ARCH}"
echo "Installing kubelet and kubeadm ${KUBELET_VERSION} for ${ARCH}..."
curl -fsSL "${BASE_URL}/kubelet" -o /root/kubelet
curl -fsSL "${BASE_URL}/kubeadm" -o /root/kubeadm
chmod 0755 /root/kubelet
chmod 0755 /root/kubeadm
if /root/kubelet --version ; then mv /root/kubelet /usr/bin/kubelet ; fi
if /root/kubeadm version ; then mv /root/kubeadm /usr/bin/kubeadm ; fi
diskSetup:
filesystems:
- device: /dev/vdb
@@ -232,7 +205,6 @@ spec:
{{- end }}
{{- end }}
preKubeadmCommands:
- KUBELET_VERSION={{ include "kubernetes.versionMap" $}} /usr/bin/update-k8s.sh || true
- sed -i 's|root:x:|root::|' /etc/passwd
- systemctl stop containerd.service
- mkdir -p /ephemeral/kubelet /ephemeral/containerd

View File

@@ -1,89 +0,0 @@
---
apiVersion: batch/v1
kind: Job
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
name: {{ .Release.Name }}-cleanup
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-cleanup
restartPolicy: Never
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: "NoSchedule"
containers:
- name: kubectl
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- kubectl -n {{ .Release.Namespace }} delete datavolumes
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}"
--ignore-not-found=true
kubectl -n {{ .Release.Namespace }} delete services
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}"
--field-selector spec.type=LoadBalancer
--ignore-not-found=true
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-cleanup
annotations:
helm.sh/hook: post-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
rules:
- apiGroups:
- "cdi.kubevirt.io"
resources:
- datavolumes
verbs:
- get
- list
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup
namespace: {{ .Release.Namespace }}

View File

@@ -24,26 +24,26 @@ spec:
command:
- /bin/sh
- -c
- >-
kubectl
--namespace={{ .Release.Namespace }}
patch
helmrelease
{{ .Release.Name }}-cilium
{{ .Release.Name }}-gateway-api-crds
{{ .Release.Name }}-csi
{{ .Release.Name }}-cert-manager
{{ .Release.Name }}-cert-manager-crds
{{ .Release.Name }}-vertical-pod-autoscaler
{{ .Release.Name }}-vertical-pod-autoscaler-crds
{{ .Release.Name }}-ingress-nginx
{{ .Release.Name }}-fluxcd-operator
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
- |
kubectl
--namespace={{ .Release.Namespace }}
patch
helmrelease
{{ .Release.Name }}-cilium
{{ .Release.Name }}-gateway-api-crds
{{ .Release.Name }}-csi
{{ .Release.Name }}-cert-manager
{{ .Release.Name }}-cert-manager-crds
{{ .Release.Name }}-vertical-pod-autoscaler
{{ .Release.Name }}-vertical-pod-autoscaler-crds
{{ .Release.Name }}-ingress-nginx
{{ .Release.Name }}-fluxcd-operator
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
---
apiVersion: v1
kind: ServiceAccount
@@ -51,7 +51,7 @@ metadata:
name: {{ .Release.Name }}-flux-teardown
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -75,7 +75,6 @@ rules:
- {{ .Release.Name }}-csi
- {{ .Release.Name }}-cert-manager
- {{ .Release.Name }}-cert-manager-crds
- {{ .Release.Name }}-gateway-api-crds
- {{ .Release.Name }}-vertical-pod-autoscaler
- {{ .Release.Name }}-vertical-pod-autoscaler-crds
- {{ .Release.Name }}-ingress-nginx

View File

@@ -37,10 +37,6 @@ spec:
# automaticFailover: true
{{- end }}
podMetadata:
labels:
"policy.cozystack.io/allow-to-apiserver": "true"
metrics:
enabled: true
exporter:

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: mysql
type: mysql
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,14 +1,6 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
{{- $passwords := dict }}
{{- with (dig "data" (dict) $existingSecret) }}
{{- range $k, $v := . }}
{{- $_ := set $passwords $k (b64dec $v) }}
{{- end }}
{{- end }}
{{- range $user, $u := .Values.users }}
{{- if $u.password }}
{{- $_ := set $passwords $user $u.password }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: nats
type: nats
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}-system
version: {{ $.Chart.Version }}

View File

@@ -79,17 +79,3 @@ spec:
policy.cozystack.io/allow-to-apiserver: "true"
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: {{ $.Release.Name }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: postgres
type: postgres
selector:
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: rabbitmq
type: rabbitmq
selector:
app.kubernetes.io/name: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -68,34 +68,3 @@ spec:
auth:
secretPath: {{ .Release.Name }}-auth
{{- end }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-redis
namespace: {{ $.Release.Namespace }}
spec:
minReplicas: 1
replicas: {{ .Values.replicas }}
kind: redis
type: redis
selector:
app.kubernetes.io/component: redis
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-sentinel
namespace: {{ $.Release.Namespace }}
spec:
minReplicas: 2
replicas: 3
kind: redis
type: sentinel
selector:
app.kubernetes.io/component: sentinel
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: tcp-balancer
type: haproxy
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -20,7 +20,11 @@ metadata:
name: allow-external-communication
namespace: {{ include "tenant.name" . }}
spec:
endpointSelector: {}
endpointSelector:
matchExpressions:
- key: policy.cozystack.io/allow-external-communication
operator: NotIn
values: ["false"]
ingress:
- fromEntities:
- world

View File

@@ -35,6 +35,7 @@ rules:
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -122,7 +123,7 @@ metadata:
name: {{ include "tenant.name" . }}-view
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "view" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-view
@@ -192,6 +193,7 @@ rules:
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -200,7 +202,7 @@ metadata:
name: {{ include "tenant.name" . }}-use
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "use" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-use
@@ -291,6 +293,7 @@ rules:
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -299,7 +302,7 @@ metadata:
name: {{ include "tenant.name" . }}-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "admin" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "admin" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-admin
@@ -365,6 +368,7 @@ rules:
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -373,7 +377,7 @@ metadata:
name: {{ include "tenant.name" . }}-super-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-super-admin

View File

@@ -28,3 +28,27 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -62,6 +62,7 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -1,12 +0,0 @@
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: 0
minReplicas: 0
kind: vm-disk
type: vm-disk
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -28,3 +28,27 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -26,6 +26,7 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -5,13 +5,13 @@ As the service evolves, it will provide more ways to isolate your workloads.
## Service details
To function, the service requires kube-ovn and multus CNI to be present, so by default it will only work on `paas-full` bundle.
Kube-ovn provides VPC and Subnet resources and performs isolation and networking maintenance such as DHCP. Under the hood it uses ovn virtual routers and virtual switches.
Multus enables a multi-nic capability, so a pod or a VM could have two or more network interfaces.
Currently every workload will have a connection to a default management network which will also have a default gateway, and the majority of traffic will go through it.
The service utilizes kube-ovn VPC and Subnet resources, which use ovn logical routers and logical switches under the hood.
Currently every workload will have a connection to a default management network which will also have a default gateway, and the majority of traffic will be going through it.
VPC subnets are for now an additional dedicated networking spaces.
A VM or a pod may be connected to multiple secondary Subnets at once.
Each secondary connection will be represented as an additional network interface.
## Deployment notes
VPC name must be unique within a tenant.
@@ -19,9 +19,7 @@ Subnet name and ip address range must be unique within a VPC.
Subnet ip address space must not overlap with the default management network ip address range, subsets of 172.16.0.0/12 are recommended.
Currently there are no fail-safe checks, however they are planned for the future.
Different VPCs may have subnets with overlapping ip address ranges.
A VM or a pod may be connected to multiple secondary Subnets at once. Each secondary connection will be represented as an additional network interface.
Different VPCs may have subnets with ovelapping ip address ranges.
## Parameters

View File

@@ -1 +0,0 @@
../../../library/cozy-lib

View File

@@ -60,33 +60,13 @@ kind: ConfigMap
metadata:
name: {{ $.Release.Name }}-subnets
labels:
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.kind: VirtualPrivateCloud
apps.cozystack.io/application.name: {{ trimPrefix "virtualprivatecloud-" .Release.Name }}
cozystack.io/vpcId: {{ $vpcId }}
cozystack.io/tenantName: {{ $.Release.Namespace }}
data:
{{- range $subnetName, $subnetConfig := .Values.subnets }}
{{ $subnetName }}.ID: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
{{ $subnetName }}.CIDR: {{ $subnetConfig.cidr }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "{{ .Release.Name }}-subnets"
subjects: {{- include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" .Release.Namespace ) | nindent 2 }}
roleRef:
kind: Role
name: "{{ .Release.Name }}-subnets"
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: "{{ .Release.Name }}-subnets"
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get","list","watch"]
resourceNames: ["{{ .Release.Name }}-subnets"]
subnets: |
{{- range $subnetName, $subnetConfig := .Values.subnets }}
- subnetName: {{ $subnetName }}
subnetId: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
subnetCIDR: {{ $subnetConfig.cidr }}
{{- end }}

View File

@@ -1,12 +0,0 @@
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: vpn
type: vpn
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,2 +1,2 @@
cozystack:
image: ghcr.io/cozystack/cozystack/installer:v0.38.0@sha256:1a902ebd15fe375079098c088dd5b40475926c8d9576faf6348433f0fd86a963
image: ghcr.io/cozystack/cozystack/installer:v0.37.0@sha256:256c5a0f0ae2fc3ad6865b9fda74c42945b38a5384240fa29554617185b60556

View File

@@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.0@sha256:cb17739b46eca263b2a31c714a3cb211da6f9de259b1641c2fc72c91bdfc93bb
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.37.0@sha256:10afd0a6c39248ec41d0e59ff1bc6c29bd0075b7cc9a512b01cf603ef39c33ea

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v0.38.0@sha256:9ff2bdcf802445f6c1cabdf0e6fc32ee10043b1067945232a91088abad63f583
ghcr.io/cozystack/cozystack/matchbox:v0.37.0@sha256:5cca5f56b755285aefa11b1052fe55e1aa83b25bae34aef80cdb77ff63091044

View File

@@ -1,6 +1,6 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }}
{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" | nospace }}
{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:

View File

@@ -1,16 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
namespace: {{ $.Release.Namespace }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: {{ div .Values.replicas 2 | add1 }}
kind: ingress
type: controller
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx-system
app.kubernetes.io/name: ingress-nginx
version: {{ $.Chart.Version }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.0@sha256:4548d85e7e69150aaf52fbb17fb9487e9714bdd8407aff49762cf39b9d0ab29c
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.37.0@sha256:f166f09cdc9cdbb758209883819ab8261a3793bc1d7a6b6685efd5a2b2930847

View File

@@ -4,5 +4,3 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
test:
$(MAKE) -C ../../tests/cozy-lib-tests/ test

View File

@@ -154,7 +154,7 @@
{{- $resources := index . 1 }}
{{- $global := index . 2 }}
{{- $presetMap := include "cozy-lib.resources.unsanitizedPreset" $preset | fromYaml }}
{{- $mergedMap := deepCopy (default (dict) $resources) | mergeOverwrite $presetMap }}
{{- $mergedMap := deepCopy $resources | mergeOverwrite $presetMap }}
{{- include "cozy-lib.resources.sanitize" (list $mergedMap $global) }}
{{- end }}
@@ -174,46 +174,15 @@
{{- end }}
{{- define "cozy-lib.resources.flatten" -}}
{{- $out := dict -}}
{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}}
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- with include "cozy-lib.resources.flattenResource" (list $section $k) }}
{{- $_ := set $out . $v }}
{{- end }}
{{- end }}
{{- end }}
{{- $out | toYaml }}
{{- $out := dict -}}
{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}}
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- $key := printf "%s.%s" $section $k }}
{{- if ne $key "limits.storage" }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- end }}
{{- end }}
{{/*
This is a helper function that takes an argument like `list "limits" "services.loadbalancers"`
or `list "limits" "storage"` or `list "requests" "cpu"` and returns "services.loadbalancers",
"", and "requests.cpu", respectively, thus transforming them to an acceptable format for k8s
ResourceQuotas objects.
*/}}
{{- define "cozy-lib.resources.flattenResource" }}
{{- $rawQuotaKeys := list
"pods"
"services"
"services.loadbalancers"
"services.nodeports"
"services.clusterip"
"configmaps"
"secrets"
"persistentvolumeclaims"
"replicationcontrollers"
"resourcequotas"
-}}
{{- $section := index . 0 }}
{{- $type := index . 1 }}
{{- $out := "" }}
{{- if and (eq $section "limits") (eq $type "storage") }}
{{- $out = "" }}
{{- else if and (eq $section "limits") (has $type $rawQuotaKeys) }}
{{- $out = $type }}
{{- else if not (has $type $rawQuotaKeys) }}
{{- $out = printf "%s.%s" $section $type }}
{{- end }}
{{- $out -}}
{{- $out | toYaml }}
{{- end }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:f21b1c37872221323cee0490f9c58e04fa360c2b8c68700ab0455bc39f3ad160
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:7348bec610f08bd902c88c9a9f28fdd644727e2728a1e4103f88f0c99febd5e7

View File

@@ -1 +0,0 @@
apiserver.local.config/

View File

@@ -4,18 +4,6 @@ NAMESPACE=cozy-system
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
run-local:
openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca"
openssl req -out /tmp/client.csr -new -newkey rsa:2048 -nodes -keyout /tmp/client.key -subj "/C=US/ST=SomeState/L=L/OU=Dev/CN=development/O=system:masters"
openssl x509 -req -days 365 -in /tmp/client.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/client.crt
openssl req -out /tmp/apiserver.csr -new -newkey rsa:2048 -nodes -keyout /tmp/apiserver.key -subj "/CN=cozystack-api" -config cozystack-api-openssl.cnf
openssl x509 -req -days 365 -in /tmp/apiserver.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/apiserver.crt -extensions v3_req -extfile cozystack-api-openssl.cnf
CGO_ENABLED=0 go build -o /tmp/cozystack-api ../../../cmd/cozystack-api/main.go
/tmp/cozystack-api --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
debug:
dlv debug ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
image: image-cozystack-api
image-cozystack-api:

View File

@@ -1,13 +0,0 @@
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[ req_distinguished_name ]
CN = cozystack-api
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 127.0.0.1

View File

@@ -1,5 +1,5 @@
cozystackAPI:
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.0@sha256:5eb5d6369c7c7ba0fa6b34b7c5022faa15c860b72e441b5fbde3eceda94efc88
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.37.0@sha256:19d89e8afb90ce38ab7e42ecedfc28402f7c0b56f30957db957c5415132ff6ca
localK8sAPIEndpoint:
enabled: true
replicas: 2

View File

@@ -1,6 +1,6 @@
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.0@sha256:4628a3711b6a6fc2e446255ee172cd268b28b07c65e98c302ea8897574dcbf22
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.37.0@sha256:845b8e68cbc277c2303080bcd55597e4334610d396dad258ad56fd906530acc3
debug: false
disableTelemetry: false
cozystackVersion: "v0.38.0"
cozystackVersion: "v0.37.0"
cozystackAPIKind: "DaemonSet"

View File

@@ -671,6 +671,62 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
workloadMonitors:
description: |-
WorkloadMonitors configuration for this resource
List of WorkloadMonitor templates to be created for each application instance
items:
description: |-
WorkloadMonitorTemplate defines a template for creating WorkloadMonitor resources
for application instances. Fields support Go template syntax with the following variables:
- {{ .Release.Name }}: The name of the Helm release
- {{ .Release.Namespace }}: The namespace of the Helm release
- {{ .Chart.Version }}: The version of the Helm chart
- {{ .Values.<path> }}: Any value from the Helm values
properties:
condition:
description: |-
Condition is a Go template expression that must evaluate to "true" for the monitor to be created.
Example: "{{ .Values.clickhouseKeeper.enabled }}"
If empty, the monitor is always created.
type: string
kind:
description: Kind specifies the kind of the workload (e.g.,
"postgres", "kafka")
type: string
minReplicas:
description: |-
MinReplicas is a Go template expression that evaluates to the minimum number of replicas.
Example: "1" or "{{ div .Values.replicas 2 | add1 }}"
type: string
name:
description: |-
Name is the name of the WorkloadMonitor.
Supports Go template syntax (e.g., "{{ .Release.Name }}-keeper")
type: string
replicas:
description: |-
Replicas is a Go template expression that evaluates to the desired number of replicas.
Example: "{{ .Values.replicas }}" or "{{ .Values.clickhouseKeeper.replicas }}"
type: string
selector:
additionalProperties:
type: string
description: |-
Selector is a map of label key-value pairs for matching workloads.
Supports Go template syntax in values (e.g., "app.kubernetes.io/instance: {{ .Release.Name }}")
type: object
type:
description: Type specifies the type of the workload (e.g.,
"postgres", "zookeeper")
type: string
required:
- kind
- name
- selector
- type
type: object
type: array
required:
- application
- release

View File

@@ -37,3 +37,19 @@ spec:
include:
- resourceNames:
- chendpoint-clickhouse-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: clickhouse
type: clickhouse
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-keeper"
kind: clickhouse
type: clickhouse
selector:
app: "{{ .Release.Name }}-keeper"
replicas: "{{ .Values.clickhouseKeeper.replicas }}"
minReplicas: "1"
condition: "{{ .Values.clickhouseKeeper.enabled }}"

View File

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- ferretdb-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: ferretdb
type: ferretdb
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -28,3 +28,13 @@ spec:
- database
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF84NThfMzA3MikiLz4KPHBhdGggZD0iTTEzNS43ODQgNzUuNjQ0NkwxMzUuOTM5IDg3Ljc2MzhMODkuNjg0NiA4MS41MzYyTDYyLjA4NjggODQuNTA3OUwzNS4zNDE3IDgxLjQzMjlMOC43NTE2NyA4NC41ODU0TDguNzI1ODMgODEuNTEwNEwzNS4zNjc2IDc3LjU4MjZWNjQuMTcxM0w2Mi4yOTM1IDcwLjczNDhMNjIuMzQ1MiA4MS4yNzc4TDg5LjQ3NzkgNzcuNjg2TDg5LjQwMDQgNjQuMTk3MkwxMzUuNzg0IDc1LjY0NDZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNODkuNDc3OCA4Ni4wMzI1TDEzNS44ODggOTAuODM4OFYxMDIuNzI2SDguNjQ4MjVMOC41MTkwNCA5OS41NzNIMzUuMjY0MUMzNS4yNjQxIDk5LjU3MyAzNS4yNjQxIDkwLjczNTUgMzUuMjY0MSA4Ni4wNTgzQzQ0LjI1NjcgODYuOTM2OSA2Mi4wODY3IDg4LjY5NDEgNjIuMDg2NyA4OC42OTQxVjk5LjI2MjlIODkuNDc3OFY4Ni4wMzI1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYyLjI5MzQgNjYuODg0Nkw2Mi4yMTU4IDYzLjYyODZDNjIuMjE1OCA2My42Mjg2IDc5LjgxMzMgNTguMzU3MSA4OC45MDkyIDU1LjY2OTdDODguOTA5MiA1MS4zMDI2IDg4LjkwOTIgNDcuMDkwNiA4OC45MDkyIDQyQzEwNC44NzkgNDguNDA4NSAxMjAuMjI4IDU0LjYxMDIgMTM1LjczMyA2MC44Mzc4QzEzNS43MzMgNjQuNzEzOSAxMzUuNzMzIDY4LjQzNSAxMzUuNzMzIDcyLjU2OTVDMTE5Ljg0MSA2OC4yMDI0IDEwNC4yODQgNjMuOTEyOSA4OS4xNjc2IDU5Ljc1MjVDNzkuOTY4NCA2Mi4yMDc0IDYyLjI5MzQgNjYuODg0NiA2Mi4yOTM0IDY2Ljg4NDZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzUuMzk2MiA4MS43MDczTDguODA2MTIgODQuODU5OEw4Ljc4MDI3IDgxLjc4NDhMMzUuNDIyIDc3Ljg1N1Y2NC40NDU3TDYyLjM0OCA3MS4wMDkzTDYyLjM5OTYgODEuNTUyMkw4OS41MzIzIDc3Ljk2MDRMODkuNDU0OCA2NC40NzE2TDEzNS44MzkgNzUuOTE5TDEzNS45OTQgODguMDM4Mkw4OS43MzkxIDgxLjgxMDZMNjIuMTQxMiA4NC43ODIzTDM1LjM5NjIgODEuNzA3M1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04OS41MzIzIDg2LjMwNjlMMTM1Ljk0MiA5MS4xMTMzVjEwM0g4LjcwMjdMOC41NzM0OSA5OS44NDc0SDM1LjMxODZDMzUuMzE4NiA5OS44NDc0IDM1LjMxODYgOTEuMDA5OSAzNS4zMTg2IDg2LjMzMjhDNDQuMzExMSA4Ny4yMTE0IDYyLjE0MTIgODguOTY4NSA2Mi4xNDEyIDg4Ljk2ODVWOTkuNTM3M0g4OS41MzIzVjg2LjMwNjlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNjIuMzQ4MyA2Ny4xNTlMNjIuMjcwOCA2My45MDMxQzYyLjI3MDggNjMuOTAzMSA3OS44NjgyIDU4LjYzMTYgODguOTY0MiA1NS45NDQyQzg4Ljk2NDIgNTEuNTc3MSA4OC45NjQyIDQ3LjM2NTEgODguOTY0MiA0Mi4yNzQ0QzEwNC45MzQgNDguNjgyOSAxMjAuMjgzIDU0Ljg4NDcgMTM1Ljc4NyA2MS4xMTIzQzEzNS43ODcgNjQuOTg4NCAxMzUuNzg3IDY4LjcwOTQgMTM1Ljc4NyA3Mi44NDM5QzExOS44OTUgNjguNDc2OSAxMDQuMzM5IDY0LjE4NzMgODkuMjIyNiA2MC4wMjdDODAuMDIzMyA2Mi40ODE4IDYyLjM0ODMgNjcuMTU5IDYyLjM0ODMgNjcuMTU5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF84NThfMzA3MiIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMjkuNSAtMTgpIHJvdGF0ZSgzOS42OTYzKSBzY2FsZSgzMDIuMTY4IDI3NS4yNzEpIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0JFRERGRiIvPgo8c3RvcCBvZmZzZXQ9IjAuMjU5NjE1IiBzdG9wLWNvbG9yPSIjOUVDQ0ZEIi8+CjxzdG9wIG9mZnNldD0iMC41OTEzNDYiIHN0b3AtY29sb3I9IiMzRjlBRkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMEI3MEUwIi8+CjwvcmFkaWFsR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==
# keysOrder: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: foundationdb
type: foundationdb
selector:
foundationdb.org/fdb-cluster-name: "{{ .Release.Name }}"
foundationdb.org/fdb-process-class: storage
replicas: "{{ .Values.cluster.processCounts.storage }}"
minReplicas: "{{ include \"foundationdb.minReplicas\" . }}"
condition: "{{ .Values.monitoring.enabled }}"

View File

@@ -32,3 +32,25 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}-haproxy"
kind: http-cache
type: http-cache
selector:
app: "{{ .Release.Name }}-haproxy"
replicas: "{{ .Values.haproxy.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-nginx"
kind: http-cache
type: http-cache
selector:
app: "{{ .Release.Name }}-nginx-cache"
replicas: "{{ .Values.nginx.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}"
kind: http-cache
type: http-cache
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -37,3 +37,13 @@ spec:
include:
- resourceNames:
- "{{ slice .namespace 7 }}-ingress-controller"
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: ingress
type: controller
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx-system
app.kubernetes.io/name: ingress-nginx
replicas: "{{ .Values.replicas }}"
minReplicas: "{{ div .Values.replicas 2 | add1 }}"

View File

@@ -38,3 +38,20 @@ spec:
include:
- resourceNames:
- kafka-{{ .name }}-kafka-bootstrap
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: kafka
type: kafka
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/name: kafka
replicas: "{{ .Values.kafka.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-zookeeper"
kind: kafka
type: zookeeper
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/name: zookeeper
replicas: "{{ .Values.zookeeper.replicas }}"
minReplicas: "1"

View File

@@ -39,3 +39,11 @@ spec:
- resourceNames:
- mysql-{{ .name }}-primary
- mysql-{{ .name }}-secondary
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: mysql
type: mysql
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- nats-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: nats
type: nats
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}-system"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -49,3 +49,12 @@ spec:
- postgres-{{ .name }}-ro
- postgres-{{ .name }}-rw
- postgres-{{ .name }}-external-write
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: postgres
type: postgres
selector:
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -40,3 +40,11 @@ spec:
include:
- resourceNames:
- rabbitmq-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: rabbitmq
type: rabbitmq
selector:
app.kubernetes.io/name: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -41,3 +41,20 @@ spec:
- rfrm-redis-{{ .name }}
- rfrs-redis-{{ .name }}
- redis-{{ .name }}-external-lb
workloadMonitors:
- name: "{{ .Release.Name }}-redis"
kind: redis
type: redis
selector:
app.kubernetes.io/component: redis
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-sentinel"
kind: redis
type: sentinel
selector:
app.kubernetes.io/component: sentinel
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "3"
minReplicas: "2"

View File

@@ -31,3 +31,11 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: tcp-balancer
type: haproxy
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -32,3 +32,11 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: vm-disk
type: vm-disk
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "0"
minReplicas: "0"

View File

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- vpn-{{ .name }}-vpn
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: vpn
type: vpn
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -3,7 +3,7 @@ ARG NODE_VERSION=20.18.1
FROM node:${NODE_VERSION}-alpine AS builder
WORKDIR /src
ARG COMMIT_REF=ba56271739505284aee569f914fc90e6a9c670da
ARG COMMIT_REF=92906a7f21050cfb8e352f98d36b209c57844f63
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui-k8s-bff/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
ENV PATH=/src/node_modules/.bin:$PATH

View File

@@ -5,7 +5,7 @@ ARG NODE_VERSION=20.18.1
FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder
RUN apk add git
WORKDIR /src
ARG COMMIT=cb2f122caafaa2fd5455750213d9e633017ec555
ARG COMMIT=7086a2d8a07dcf6a94bb4276433db5d84acfcf3b
RUN wget -O- https://github.com/cozystack/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1
COPY openapi-k8s-toolkit/patches /patches
@@ -19,14 +19,14 @@ RUN npm run build
# openapi-ui
# imported from https://github.com/cozystack/openapi-ui
FROM node:${NODE_VERSION}-alpine AS builder
#RUN apk add git
RUN apk add git
WORKDIR /src
ARG COMMIT_REF=3cfbbf2156b6a5e4a1f283a032019530c0c2d37d
ARG COMMIT_REF=fe237518348e94cead6d4f3283b2fce27f26aa12
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
#COPY openapi-ui/patches /patches
#RUN git apply /patches/*.diff
COPY openapi-ui/patches /patches
RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH

View File

@@ -0,0 +1,230 @@
diff --git a/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx b/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
index a7135d4..2fea0bb 100644
--- a/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
+++ b/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
@@ -68,13 +68,60 @@ export const FormObjectFromSwagger: FC<TFormObjectFromSwaggerProps> = ({
properties?: OpenAPIV2.SchemaObject['properties']
required?: string
}
+
+ // Check if the field name exists in additionalProperties.properties
+ // If so, use the type from that property definition
+ const nestedProp = addProps?.properties?.[additionalPropValue] as OpenAPIV2.SchemaObject | undefined
+ let fieldType: string = addProps.type
+ let fieldItems: { type: string } | undefined = addProps.items
+ let fieldNestedProperties = addProps.properties || {}
+ let fieldRequired: string | undefined = addProps.required
+
+ if (nestedProp) {
+ // Use the nested property definition if it exists
+ // Handle type - it can be string or string[] in OpenAPI v2
+ if (nestedProp.type) {
+ if (Array.isArray(nestedProp.type)) {
+ fieldType = nestedProp.type[0] || addProps.type
+ } else if (typeof nestedProp.type === 'string') {
+ fieldType = nestedProp.type
+ } else {
+ fieldType = addProps.type
+ }
+ } else {
+ fieldType = addProps.type
+ }
+
+ // Handle items - it can be ItemsObject or ReferenceObject
+ if (nestedProp.items) {
+ // Check if it's a valid ItemsObject with type property
+ if ('type' in nestedProp.items && typeof nestedProp.items.type === 'string') {
+ fieldItems = { type: nestedProp.items.type }
+ } else {
+ fieldItems = addProps.items
+ }
+ } else {
+ fieldItems = addProps.items
+ }
+
+ fieldNestedProperties = nestedProp.properties || {}
+ // Handle required field - it can be string[] in OpenAPI schema
+ if (Array.isArray(nestedProp.required)) {
+ fieldRequired = nestedProp.required.join(',')
+ } else if (typeof nestedProp.required === 'string') {
+ fieldRequired = nestedProp.required
+ } else {
+ fieldRequired = addProps.required
+ }
+ }
+
inputProps?.addField({
path: Array.isArray(name) ? [...name, String(collapseTitle)] : [name, String(collapseTitle)],
name: additionalPropValue,
- type: addProps.type,
- items: addProps.items,
- nestedProperties: addProps.properties || {},
- required: addProps.required,
+ type: fieldType,
+ items: fieldItems,
+ nestedProperties: fieldNestedProperties,
+ required: fieldRequired,
})
setAddditionalPropValue(undefined)
}
diff --git a/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx b/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
index 487d480..3ca46c1 100644
--- a/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
+++ b/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
@@ -42,7 +42,11 @@ export const FormStringInput: FC<TFormStringInputProps> = ({
const formValue = Form.useWatch(formFieldName)
// Derive multiline based on current local value
- const isMultiline = useMemo(() => isMultilineString(formValue), [formValue])
+ const isMultiline = useMemo(() => {
+ // Normalize value for multiline check
+ const value = typeof formValue === 'string' ? formValue : (formValue === null || formValue === undefined ? '' : String(formValue))
+ return isMultilineString(value)
+ }, [formValue])
const title = (
<>
@@ -77,6 +81,23 @@ export const FormStringInput: FC<TFormStringInputProps> = ({
rules={[{ required: forceNonRequired === false && required?.includes(getStringByName(name)) }]}
validateTrigger="onBlur"
hasFeedback={designNewLayout ? { icons: feedbackIcons } : true}
+ normalize={(value) => {
+ // Normalize value to string - prevent "[object Object]" display
+ if (value === undefined || value === null) {
+ return ''
+ }
+ if (typeof value === 'string') {
+ return value
+ }
+ if (typeof value === 'number' || typeof value === 'boolean') {
+ return String(value)
+ }
+ // If it's an object or array, it shouldn't be in a string field - return empty string
+ if (typeof value === 'object') {
+ return ''
+ }
+ return String(value)
+ }}
>
<Input.TextArea
placeholder={getStringByName(name)}
diff --git a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
index 6f9eb39..835224c 100644
--- a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
+++ b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
@@ -124,8 +124,26 @@ export const materializeAdditionalFromValues = (
*
* This is used when a new field appears in the data but doesn't yet exist in the schema.
*/
- const makeChildFromAP = (ap: any): OpenAPIV2.SchemaObject => {
- const t = ap?.type ?? 'object'
+ const makeChildFromAP = (ap: any, value?: unknown): OpenAPIV2.SchemaObject => {
+ // Determine type based on actual value if not explicitly defined in additionalProperties
+ let t = ap?.type
+ if (!t && value !== undefined && value !== null) {
+ if (Array.isArray(value)) {
+ t = 'array'
+ } else if (typeof value === 'object') {
+ t = 'object'
+ } else if (typeof value === 'string') {
+ t = 'string'
+ } else if (typeof value === 'number') {
+ t = 'number'
+ } else if (typeof value === 'boolean') {
+ t = 'boolean'
+ } else {
+ t = 'object'
+ }
+ }
+ t = t ?? 'object'
+
const child: OpenAPIV2.SchemaObject = { type: t } as any
// Copy common schema details (if present)
@@ -134,6 +152,20 @@ export const materializeAdditionalFromValues = (
if (ap?.required)
(child as any).required = _.cloneDeep(ap.required)
+ // If value is an array and items type is not defined, infer it from the first item
+ if (t === 'array' && Array.isArray(value) && value.length > 0 && !ap?.items) {
+ const firstItem = value[0]
+ if (typeof firstItem === 'string') {
+ ;(child as any).items = { type: 'string' }
+ } else if (typeof firstItem === 'number') {
+ ;(child as any).items = { type: 'number' }
+ } else if (typeof firstItem === 'boolean') {
+ ;(child as any).items = { type: 'boolean' }
+ } else if (typeof firstItem === 'object') {
+ ;(child as any).items = { type: 'object' }
+ }
+ }
+
// Mark as originating from `additionalProperties`
;(child as any).isAdditionalProperties = true
return child
@@ -177,7 +209,16 @@ export const materializeAdditionalFromValues = (
// If the key doesn't exist in schema, create it from `additionalProperties`
if (!schemaNode.properties![k]) {
- schemaNode.properties![k] = makeChildFromAP(ap)
+ // Check if there's a nested property definition in additionalProperties
+ const nestedProp = ap?.properties?.[k]
+ if (nestedProp) {
+ // Use the nested property definition from additionalProperties
+ schemaNode.properties![k] = _.cloneDeep(nestedProp) as any
+ ;(schemaNode.properties![k] as any).isAdditionalProperties = true
+ } else {
+ // Create from additionalProperties with value-based type inference
+ schemaNode.properties![k] = makeChildFromAP(ap, vo[k])
+ }
// If it's an existing additional property, merge any nested structure
} else if ((schemaNode.properties![k] as any).isAdditionalProperties && ap?.properties) {
;(schemaNode.properties![k] as any).properties ??= _.cloneDeep(ap.properties)
diff --git a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
index 2d887c7..d69d711 100644
--- a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
+++ b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
@@ -394,9 +394,11 @@ export const getArrayFormItemFromSwagger = ({
{(fields, { add, remove }, { errors }) => (
<>
{fields.map(field => {
- const fieldType = (
+ const rawFieldType = (
schema.items as (OpenAPIV2.ItemsObject & { properties?: OpenAPIV2.SchemaObject }) | undefined
)?.type
+ // Handle type as string or string[] (OpenAPI v2 allows both)
+ const fieldType = Array.isArray(rawFieldType) ? rawFieldType[0] : rawFieldType
const description = (schema.items as (OpenAPIV2.ItemsObject & { description?: string }) | undefined)
?.description
const entry = schema.items as
@@ -577,7 +579,29 @@ export const getArrayFormItemFromSwagger = ({
type="text"
size="small"
onClick={() => {
- add()
+ // Determine initial value based on item type
+ const fieldType = (
+ schema.items as (OpenAPIV2.ItemsObject & { properties?: OpenAPIV2.SchemaObject }) | undefined
+ )?.type
+
+ let initialValue: unknown
+ // Handle type as string or string[] (OpenAPI v2 allows both)
+ const typeStr = Array.isArray(fieldType) ? fieldType[0] : fieldType
+ if (typeStr === 'string') {
+ initialValue = ''
+ } else if (typeStr === 'number' || typeStr === 'integer') {
+ initialValue = 0
+ } else if (typeStr === 'boolean') {
+ initialValue = false
+ } else if (typeStr === 'array') {
+ initialValue = []
+ } else if (typeStr === 'object') {
+ initialValue = {}
+ } else {
+ initialValue = ''
+ }
+
+ add(initialValue)
}}
>
<PlusIcon />

View File

@@ -0,0 +1,91 @@
diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
index ac56e5f..c6e2350 100644
--- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
+++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
@@ -1,6 +1,6 @@
import React, { FC, useState } from 'react'
import { Button, Alert, Spin, Typography } from 'antd'
-import { filterSelectOptions, Spacer, useBuiltinResources, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
+import { filterSelectOptions, Spacer, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'store/store'
@@ -11,6 +11,11 @@ import {
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
import { Styled } from './styled'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
export const ListInsideClusterAndNs: FC = () => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
@@ -33,9 +38,11 @@ export const ListInsideClusterAndNs: FC = () => {
typeof CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME.length > 0
- const namespacesData = useBuiltinResources({
+ const namespacesData = useApiResources({
clusterName: selectedCluster || '',
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
isEnabled: selectedCluster !== undefined && !isCustomNamespaceResource,
})
diff --git a/src/hooks/useNavSelectorInside.ts b/src/hooks/useNavSelectorInside.ts
index 5736e2b..1ec0f71 100644
--- a/src/hooks/useNavSelectorInside.ts
+++ b/src/hooks/useNavSelectorInside.ts
@@ -1,6 +1,11 @@
-import { TClusterList, TSingleResource, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
+import { TClusterList, TSingleResource, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({
value: name,
@@ -15,9 +20,11 @@ const mappedNamespaceToOptionInSidebar = ({ metadata }: TSingleResource): { valu
export const useNavSelectorInside = (clusterName?: string) => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
- const { data: namespaces } = useBuiltinResources({
+ const { data: namespaces } = useApiResources({
clusterName: clusterName || '',
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
isEnabled: Boolean(clusterName),
})
diff --git a/src/utils/getBacklink.ts b/src/utils/getBacklink.ts
index a862354..f24e2bc 100644
--- a/src/utils/getBacklink.ts
+++ b/src/utils/getBacklink.ts
@@ -28,7 +28,7 @@ export const getFormsBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {
@@ -64,7 +64,7 @@ export const getTablesBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {

View File

@@ -0,0 +1,15 @@
diff --git a/src/components/organisms/Header/organisms/User/User.tsx b/src/components/organisms/Header/organisms/User/User.tsx
index efe7ac3..80b715c 100644
--- a/src/components/organisms/Header/organisms/User/User.tsx
+++ b/src/components/organisms/Header/organisms/User/User.tsx
@@ -23,10 +23,6 @@ export const User: FC = () => {
// key: '1',
// label: <ThemeSelector />,
// },
- {
- key: '2',
- label: <div onClick={() => navigate(`${baseprefix}/inside/clusters`)}>Inside</div>,
- },
{
key: '3',
label: (

View File

@@ -1,6 +1,6 @@
{{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }}
{{- $tenantText := "v0.38.0" }}
{{- $tenantText := "latest" }}
{{- $footerText := "Cozystack" }}
{{- $titleText := "Cozystack Dashboard" }}
{{- $logoText := "" }}

View File

@@ -34,14 +34,6 @@ data:
}
location /k8s {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
rewrite /k8s/(.*) /$1 break;
proxy_pass https://kubernetes.default.svc:443;
}

View File

@@ -45,9 +45,9 @@ spec:
- name: BASE_NAMESPACE_FULL_PATH
value: "/apis/core.cozystack.io/v1alpha1/tenantnamespaces"
- name: LOGGER
value: "true"
value: "TRUE"
- name: LOGGER_WITH_HEADERS
value: "false"
value: "TRUE"
- name: PORT
value: "64231"
image: {{ .Values.openapiUIK8sBff.image | quote }}
@@ -94,8 +94,6 @@ spec:
- env:
- name: BASEPREFIX
value: /openapi-ui
- name: HIDE_INSIDE
value: "true"
- name: CUSTOMIZATION_API_GROUP
value: dashboard.cozystack.io
- name: CUSTOMIZATION_API_VERSION

View File

@@ -1,6 +1,6 @@
openapiUI:
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.38.0@sha256:78570edb9f4e329ffed0f8da3942acee1536323169d56324e57360df66044c28
image: ghcr.io/cozystack/cozystack/openapi-ui:latest@sha256:b942d98ff0ea36e3c6e864b6459b404d37ed68bc2b0ebc5d3007a1be4faf60c5
openapiUIK8sBff:
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.0@sha256:b7f18b86913d94338f1ceb93fca6409d19f565e35d6d6e683ca93441920fec71
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:latest@sha256:5ddc6546baf3acdb8e0572536665fe73053a7f985b05e51366454efa11c201d2
tokenProxy:
image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b
image: ghcr.io/cozystack/cozystack/token-proxy:latest@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b

View File

@@ -1,7 +1,4 @@
strimzi-kafka-operator:
watchAnyNamespace: true
generateNetworkPolicy: false
kubernetesServiceDnsDomain: cozy.local
resources:
limits:
memory: 512Mi
kubernetesServiceDnsDomain: cozy.local

View File

@@ -3,7 +3,7 @@ kamaji:
deploy: false
image:
pullPolicy: IfNotPresent
tag: v0.38.0@sha256:125e4e6a8b86418e891416d29353053ab8b65182b7e443f221b557c11a385280
tag: v0.37.0@sha256:9f4fd5045ede2909fbaf2572e4138fcbd8921071ecf8f08446257fddd0e6f655
repository: ghcr.io/cozystack/cozystack/kamaji
resources:
limits:
@@ -13,4 +13,4 @@ kamaji:
cpu: 100m
memory: 100Mi
extraArgs:
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.0@sha256:125e4e6a8b86418e891416d29353053ab8b65182b7e443f221b557c11a385280
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.37.0@sha256:9f4fd5045ede2909fbaf2572e4138fcbd8921071ecf8f08446257fddd0e6f655

View File

@@ -1,4 +1,4 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.0@sha256:a140bdcc300bcfb63a5d64884d02d802d7669ba96dc65292a06f3b200ff627f8
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.37.0@sha256:9950614571ea77a55925eba0839b6b12c8e5a7a30b8858031a8c6050f261af1a
ovnCentralName: ovn-central

View File

@@ -1,3 +1,3 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.0@sha256:7bfd458299a507f2cf82cddb65941ded6991fd4ba92fd46010cbc8c363126085
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.37.0@sha256:7e63205708e607ce2cedfe2a2cafd323ca51e3ebc71244a21ff6f9016c6c87bc

View File

@@ -44,7 +44,7 @@ kube-ovn:
memory: "50Mi"
limits:
cpu: "1000m"
memory: "2Gi"
memory: "1Gi"
kube-ovn-pinger:
requests:
cpu: "10m"
@@ -65,4 +65,4 @@ global:
images:
kubeovn:
repository: kubeovn
tag: v1.14.11@sha256:1b0f472cf30d5806e3afd10439ce8f9cfe8a004322dbd1911f7d69171fe936e5
tag: v1.14.5@sha256:af10da442a0c6dc7df47a0ef752e2eb5c247bb0b43069fdfcb2aa51511185ea2

View File

@@ -1,3 +1,3 @@
storageClass: replicated
csiDriver:
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c8b08084a86251cdd18e237de89b695bca0e4f7eb1f1f6ddc2b903b4d74ea5ff

View File

@@ -22,13 +22,7 @@ spec:
- GPU
- VMExport
evictionStrategy: LiveMigrate
vmRolloutStrategy: LiveUpdate
workloadUpdateStrategy:
workloadUpdateMethods:
- LiveMigrate
- Evict
batchEvictionInterval: 1m
batchEvictionSize: 10
customizeComponents: {}
imagePullPolicy: IfNotPresent
monitorNamespace: tenant-root
workloadUpdateStrategy: {}

View File

@@ -1,5 +1,5 @@
lineageControllerWebhook:
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.0@sha256:fc2b04f59757904ec1557a39529b84b595114b040ef95d677fd7f21ac3958e0a
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.37.0@sha256:845b8e68cbc277c2303080bcd55597e4334610d396dad258ad56fd906530acc3
debug: false
localK8sAPIEndpoint:
enabled: true

View File

@@ -1,6 +1,6 @@
dependencies:
- name: mariadb-operator-crds
repository: file://../mariadb-operator-crds
version: 25.10.2
digest: sha256:01b102dbdb92970e38346df382ed3e5cd93d02a3b642029e94320256c9bfad42
generated: "2025-10-28T11:29:04.951947063Z"
version: 0.38.1
digest: sha256:0f2ff90b83955a060f581b7db4a0c746338ae3a50d9766877c346c7f61d74cde
generated: "2025-04-15T16:54:07.813989419Z"

View File

@@ -1,10 +1,10 @@
apiVersion: v2
appVersion: 25.10.2
appVersion: 0.38.1
dependencies:
- condition: crds.enabled
name: mariadb-operator-crds
repository: file://../mariadb-operator-crds
version: 25.10.2
version: 0.38.1
description: Run and operate MariaDB in a cloud native way
home: https://github.com/mariadb-operator/mariadb-operator
icon: https://mariadb-operator.github.io/mariadb-operator/assets/mariadb_profile.svg
@@ -21,4 +21,4 @@ maintainers:
name: mmontes11
name: mariadb-operator
type: application
version: 25.10.2
version: 0.38.1

View File

@@ -2,7 +2,7 @@
[//]: # (README.md generated by gotmpl. DO NOT EDIT.)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 25.10.2](https://img.shields.io/badge/Version-25.10.2-informational?style=flat-square) ![AppVersion: 25.10.2](https://img.shields.io/badge/AppVersion-25.10.2-informational?style=flat-square)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.38.1](https://img.shields.io/badge/Version-0.38.1-informational?style=flat-square) ![AppVersion: 0.38.1](https://img.shields.io/badge/AppVersion-0.38.1-informational?style=flat-square)
Run and operate MariaDB in a cloud native way
@@ -16,7 +16,7 @@ helm install mariadb-operator-crds mariadb-operator/mariadb-operator-crds
helm install mariadb-operator mariadb-operator/mariadb-operator
```
Refer to the [helm documentation](https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/helm.md) for further detail.
Refer to the [helm documentation](https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/HELM.md) for further detail.
## Values
@@ -60,15 +60,14 @@ Refer to the [helm documentation](https://github.com/mariadb-operator/mariadb-op
| certController.tolerations | list | `[]` | Tolerations to add to cert-controller container |
| certController.topologySpreadConstraints | list | `[]` | topologySpreadConstraints to add to cert-controller container |
| clusterName | string | `"cluster.local"` | Cluster DNS name |
| config | object | `{"exporterImage":"prom/mysqld-exporter:v0.15.1","exporterMaxscaleImage":"docker-registry2.mariadb.com/mariadb/maxscale-prometheus-exporter-ubi:v0.0.1","galeraLibPath":"/usr/lib/galera/libgalera_smm.so","mariadbDefaultVersion":"11.8","mariadbImage":"docker-registry1.mariadb.com/library/mariadb:11.8.2","mariadbImageName":"docker-registry1.mariadb.com/library/mariadb","maxscaleImage":"docker-registry2.mariadb.com/mariadb/maxscale:23.08.5"}` | Operator configuration |
| config | object | `{"exporterImage":"prom/mysqld-exporter:v0.15.1","exporterMaxscaleImage":"docker-registry2.mariadb.com/mariadb/maxscale-prometheus-exporter-ubi:v0.0.1","galeraLibPath":"/usr/lib/galera/libgalera_smm.so","mariadbDefaultVersion":"11.4","mariadbImage":"docker-registry1.mariadb.com/library/mariadb:11.4.5","maxscaleImage":"docker-registry2.mariadb.com/mariadb/maxscale:23.08.5"}` | Operator configuration |
| config.exporterImage | string | `"prom/mysqld-exporter:v0.15.1"` | Default MariaDB exporter image |
| config.exporterMaxscaleImage | string | `"docker-registry2.mariadb.com/mariadb/maxscale-prometheus-exporter-ubi:v0.0.1"` | Default MaxScale exporter image |
| config.galeraLibPath | string | `"/usr/lib/galera/libgalera_smm.so"` | Galera library path to be used with MariaDB Galera |
| config.mariadbDefaultVersion | string | `"11.8"` | Default MariaDB version to be used when unable to infer it via image tag |
| config.mariadbImage | string | `"docker-registry1.mariadb.com/library/mariadb:11.8.2"` | Default MariaDB image |
| config.mariadbImageName | string | `"docker-registry1.mariadb.com/library/mariadb"` | Default MariaDB image name |
| config.mariadbDefaultVersion | string | `"11.4"` | Default MariaDB version to be used when unable to infer it via image tag |
| config.mariadbImage | string | `"docker-registry1.mariadb.com/library/mariadb:11.4.5"` | Default MariaDB image |
| config.maxscaleImage | string | `"docker-registry2.mariadb.com/mariadb/maxscale:23.08.5"` | Default MaxScale image |
| crds | object | `{"enabled":false}` | CRDs |
| crds | object | `{"enabled":false}` | - CRDs |
| crds.enabled | bool | `false` | Whether the helm chart should create and update the CRDs. It is false by default, which implies that the CRDs must be managed independently with the mariadb-operator-crds helm chart. **WARNING** This should only be set to true during the initial deployment. If this chart manages the CRDs and is later uninstalled, all MariaDB instances will be DELETED. |
| currentNamespaceOnly | bool | `false` | Whether the operator should watch CRDs only in its own namespace or not. |
| extrArgs | list | `[]` | Extra arguments to be passed to the controller entrypoint |

View File

@@ -17,6 +17,6 @@ helm install mariadb-operator-crds mariadb-operator/mariadb-operator-crds
helm install mariadb-operator mariadb-operator/mariadb-operator
```
Refer to the [helm documentation](https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/helm.md) for further detail.
Refer to the [helm documentation](https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/HELM.md) for further detail.
{{ template "chart.valuesSection" . }}

View File

@@ -16,4 +16,4 @@ maintainers:
name: mmontes11
name: mariadb-operator-crds
type: application
version: 25.10.2
version: 0.38.1

View File

@@ -1,4 +1,4 @@
mariadb-operator has been successfully deployed! 🦭
Not sure what to do next? 😅 Check out:
https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/quickstart.md
https://github.com/mariadb-operator/mariadb-operator/blob/main/docs/QUICKSTART.md

View File

@@ -51,10 +51,10 @@ rules:
- patch
- watch
- apiGroups:
- discovery.k8s.io
- ""
resources:
- endpointslices
- endpointslices/restricted
- endpoints
- endpoints/restricted
verbs:
- get
- list

View File

@@ -4,7 +4,6 @@ data:
MARIADB_GALERA_LIB_PATH: "{{ .Values.config.galeraLibPath }}"
MARIADB_DEFAULT_VERSION: "{{ .Values.config.mariadbDefaultVersion }}"
RELATED_IMAGE_MARIADB: "{{ .Values.config.mariadbImage }}"
RELATED_IMAGE_MARIADB_NAME: "{{ .Values.config.mariadbImageName }}"
RELATED_IMAGE_MAXSCALE: "{{ .Values.config.maxscaleImage }}"
RELATED_IMAGE_EXPORTER: "{{ .Values.config.exporterImage }}"
RELATED_IMAGE_EXPORTER_MAXSCALE: "{{ .Values.config.exporterMaxscaleImage }}"

Some files were not shown because too many files have changed in this diff Show More