Compare commits

..

1 Commits

Author SHA1 Message Date
Andrei Kvapil
ca61c71084 Replace Ancestor tracking webhook with controller
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 12:48:17 +02:00
74 changed files with 1327 additions and 566 deletions

View File

@@ -28,7 +28,7 @@ jobs:
- name: Install generate
run: |
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v0.9.0/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v0.8.5/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
- name: Run pre-commit hooks
run: |

View File

@@ -30,5 +30,3 @@ This list is sorted in chronological order, based on the submission date.
| [Bootstack](https://bootstack.app/) | @mrkhachaturov | 2024-08-01| At Bootstack, we utilize a Kubernetes operator specifically designed to simplify and streamline cloud infrastructure creation.|
| [gohost](https://gohost.kz/) | @karabass_off | 2024-02-01 | Our company has been working in the market of Kazakhstan for more than 15 years, providing clients with a standard set of services: VPS/VDC, IaaS, shared hosting, etc. Now we are expanding the lineup by introducing Bare Metal Kubenetes cluster under Cozystack management. |
| [Urmanac](https://urmanac.com) | @kingdonb | 2024-12-04 | Urmanac is the future home of a hosting platform for the knowledge base of a community of personal server enthusiasts. We use Cozystack to provide support services for web sites hosted using both conventional deployments and on SpinKube, with WASM. |
| [Hidora](https://hikube.cloud) | @matthieu-robin | 2025-09-17 | Hidora is a Swiss cloud provider delivering managed services and infrastructure solutions through datacenters located in Switzerland, ensuring data sovereignty and reliability. Its sovereign cloud platform, Hikube, is designed to run workloads with high availability across multiple datacenters, providing enterprises with a secure and scalable foundation for their applications based on Cozystack. |
|

View File

@@ -38,6 +38,7 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/lineagelabeler"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
@@ -67,6 +68,7 @@ func main() {
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var watchResources string
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -86,6 +88,9 @@ func main() {
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.StringVar(&cozystackVersion, "cozystack-version", "unknown",
"Version of Cozystack")
flag.StringVar(&watchResources, "watch-resources",
"v1/Pod,v1/Service,v1/Secret,v1/PersistentVolumeClaim",
"Comma-separated list of resources to watch in the form 'group/version/Kind'.")
opts := zap.Options{
Development: false,
}
@@ -214,6 +219,15 @@ func main() {
os.Exit(1)
}
if err := (&lineagelabeler.LineageLabelerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
WatchResourceCSV: watchResources,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "LineageLabeler")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

View File

@@ -0,0 +1,367 @@
package lineagelabeler
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/shared/crdmem"
"github.com/cozystack/cozystack/pkg/lineage"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var ErrNoAncestors = errors.New("no ancestors")
type LineageLabelerReconciler struct {
client.Client
Scheme *runtime.Scheme
WatchResourceCSV string
dynClient dynamic.Interface
mapper meta.RESTMapper
appMap atomic.Value
once sync.Once
mem *crdmem.Memory
}
type chartRef struct{ repo, chart string }
type appRef struct{ groupVersion, kind, prefix string }
func (r *LineageLabelerReconciler) initMapping() {
r.once.Do(func() {
r.appMap.Store(make(map[chartRef]appRef))
})
}
func (r *LineageLabelerReconciler) currentMap() map[chartRef]appRef {
val := r.appMap.Load()
if val == nil {
return map[chartRef]appRef{}
}
return val.(map[chartRef]appRef)
}
func (r *LineageLabelerReconciler) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg := r.currentMap()
s := hr.Spec.Chart.Spec
key := chartRef{s.SourceRef.Name, s.Chart}
if v, ok := cfg[key]; ok {
return v.groupVersion, v.kind, v.prefix, nil
}
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
func parseGVKList(csv string) ([]schema.GroupVersionKind, error) {
csv = strings.TrimSpace(csv)
if csv == "" {
return nil, fmt.Errorf("watch resource list is empty")
}
parts := strings.Split(csv, ",")
out := make([]schema.GroupVersionKind, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
s := strings.Split(p, "/")
if len(s) == 2 {
out = append(out, schema.GroupVersionKind{Group: "", Version: s[0], Kind: s[1]})
continue
}
if len(s) == 3 {
out = append(out, schema.GroupVersionKind{Group: s[0], Version: s[1], Kind: s[2]})
continue
}
return nil, fmt.Errorf("invalid resource token %q, expected 'group/version/Kind' or 'v1/Kind'", p)
}
return out, nil
}
func (r *LineageLabelerReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.initMapping()
cfg := rest.CopyConfig(mgr.GetConfig())
dc, err := dynamic.NewForConfig(cfg)
if err != nil {
return err
}
disco, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return err
}
cached := memory.NewMemCacheClient(disco)
r.dynClient = dc
r.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cached)
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return err
}
if len(gvks) == 0 {
return fmt.Errorf("no resources to watch")
}
b := ctrl.NewControllerManagedBy(mgr).Named("lineage-labeler")
nsPred := predicate.NewPredicateFuncs(func(obj client.Object) bool {
ns := obj.GetNamespace()
return ns != "" && strings.HasPrefix(ns, "tenant-")
})
primary := gvks[0]
primaryObj := &unstructured.Unstructured{}
primaryObj.SetGroupVersionKind(primary)
b = b.For(primaryObj,
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
for _, gvk := range gvks[1:] {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
b = b.Watches(u,
&handler.EnqueueRequestForObject{},
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
}
b = b.Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
_ = r.refreshAppMap(ctx)
return nil
}),
)
_ = r.refreshAppMap(context.Background())
return b.Complete(r)
}
func (r *LineageLabelerReconciler) refreshAppMap(ctx context.Context) error {
var items []cozyv1alpha1.CozystackResourceDefinition
var err error
if r.mem != nil {
items, err = r.mem.ListFromCacheOrAPI(ctx, r.Client)
} else {
var list cozyv1alpha1.CozystackResourceDefinitionList
err = r.Client.List(ctx, &list)
items = list.Items
}
if err != nil {
return err
}
newMap := make(map[chartRef]appRef, len(items))
for _, crd := range items {
k := chartRef{
repo: crd.Spec.Release.Chart.SourceRef.Name,
chart: crd.Spec.Release.Chart.Name,
}
v := appRef{
groupVersion: "apps.cozystack.io/v1alpha1",
kind: crd.Spec.Application.Kind,
prefix: crd.Spec.Release.Prefix,
}
if _, exists := newMap[k]; exists {
continue
}
newMap[k] = v
}
r.appMap.Store(newMap)
return nil
}
func (r *LineageLabelerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
if req.Namespace == "" || !strings.HasPrefix(req.Namespace, "tenant-") {
return ctrl.Result{}, nil
}
if len(r.currentMap()) == 0 {
_ = r.refreshAppMap(ctx)
if len(r.currentMap()) == 0 {
return ctrl.Result{RequeueAfter: 2 * time.Second}, nil
}
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return ctrl.Result{}, err
}
var obj *unstructured.Unstructured
found := false
for _, gvk := range gvks {
mapping, mErr := r.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if mErr != nil {
continue
}
ns := req.Namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
ns = ""
}
res, gErr := r.dynClient.Resource(mapping.Resource).Namespace(ns).Get(ctx, req.Name, metav1.GetOptions{})
if gErr != nil {
if apierrors.IsNotFound(gErr) {
continue
}
continue
}
obj = res
found = true
break
}
if !found || obj == nil {
return ctrl.Result{}, nil
}
existing := obj.GetLabels()
if existing == nil {
existing = map[string]string{}
}
keys := []string{
"apps.cozystack.io/application.group",
"apps.cozystack.io/application.kind",
"apps.cozystack.io/application.name",
}
allPresent := true
for _, k := range keys {
if _, ok := existing[k]; !ok {
allPresent = false
break
}
}
if allPresent {
return ctrl.Result{}, nil
}
labels, warn, err := r.computeLabels(ctx, obj)
if err != nil {
if errors.Is(err, ErrNoAncestors) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if warn != "" {
l.V(1).Info("lineage ambiguous; using first ancestor", "name", req.NamespacedName)
}
for k, v := range labels {
existing[k] = v
}
obj.SetLabels(existing)
// Server-Side Apply: claim ownership of our label keys
gvk := obj.GroupVersionKind()
patch := &unstructured.Unstructured{}
patch.SetGroupVersionKind(gvk)
patch.SetNamespace(obj.GetNamespace())
patch.SetName(obj.GetName())
patch.SetLabels(map[string]string{
"apps.cozystack.io/application.group": existing["apps.cozystack.io/application.group"],
"apps.cozystack.io/application.kind": existing["apps.cozystack.io/application.kind"],
"apps.cozystack.io/application.name": existing["apps.cozystack.io/application.name"],
})
// Use controller-runtime client with Apply patch type and field owner
if err := r.Patch(ctx, patch,
client.Apply,
client.FieldOwner("cozystack/lineage"),
client.ForceOwnership(false),
); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{RequeueAfter: 500 * time.Millisecond}, nil
}
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *LineageLabelerReconciler) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, string, error) {
owners := lineage.WalkOwnershipGraph(ctx, r.dynClient, r.mapper, r, o)
if len(owners) == 0 {
return nil, "", ErrNoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, r.dynClient, r.mapper)
if err != nil {
return nil, "", err
}
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion())
if err != nil {
return nil, "", fmt.Errorf("invalid APIVersion %s: %w", obj.GetAPIVersion(), err)
}
var warn string
if len(owners) > 1 {
warn = "ambiguous"
}
group := gv.Group
if len(group) > 63 {
group = trimDNSLabel(group[:63])
}
return map[string]string{
"apps.cozystack.io/application.group": group,
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
}, warn, nil
}
func trimDNSLabel(s string) string {
for len(s) > 0 {
b := s[len(s)-1]
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') {
return s
}
s = s[:len(s)-1]
}
return s
}

View File

@@ -27,7 +27,7 @@ For more details, read [Restic: Effective Backup from Stdin](https://blog.aenix.
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of Clickhouse replicas | `int` | `2` |
| `shards` | Number of Clickhouse shards | `int` | `1` |
| `resources` | Explicit CPU and memory configuration for each Clickhouse replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each Clickhouse replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
@@ -65,7 +65,7 @@ For more details, read [Restic: Effective Backup from Stdin](https://blog.aenix.
| Name | Description | Type | Value |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `clickhouseKeeper` | Clickhouse Keeper configuration | `*object` | `null` |
| `clickhouseKeeper` | Clickhouse Keeper configuration | `*object` | `{}` |
| `clickhouseKeeper.enabled` | Deploy ClickHouse Keeper for cluster coordination | `*bool` | `true` |
| `clickhouseKeeper.size` | Persistent Volume Claim size, available for application data | `*quantity` | `1Gi` |
| `clickhouseKeeper.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -5,7 +5,16 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {},
"default": {
"cleanupStrategy": "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m",
"enabled": false,
"resticPassword": "\u003cpassword\u003e",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3Bucket": "s3.example.org/clickhouse-backups",
"s3Region": "us-east-1",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * *"
},
"required": [
"cleanupStrategy",
"enabled",
@@ -62,7 +71,12 @@
"clickhouseKeeper": {
"description": "Clickhouse Keeper configuration",
"type": "object",
"default": {},
"default": {
"enabled": true,
"replicas": 3,
"resourcesPreset": "micro",
"size": "1Gi"
},
"required": [
"resourcesPreset"
],

View File

@@ -11,7 +11,7 @@ Internally, FerretDB service is backed by Postgres.
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -5,7 +5,15 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {},
"default": {
"destinationPath": "s3://bucket/path/to/folder/",
"enabled": false,
"endpointURL": "http://minio-gateway-service:9000",
"retentionPolicy": "30d",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * * *"
},
"required": [
"destinationPath",
"enabled",
@@ -56,7 +64,11 @@
"bootstrap": {
"description": "Bootstrap (recovery) configuration",
"type": "object",
"default": {},
"default": {
"enabled": false,
"oldName": "",
"recoveryTime": ""
},
"properties": {
"enabled": {
"description": "Restore database cluster from a backup",
@@ -81,7 +93,10 @@
"quorum": {
"description": "Configuration for the quorum-based synchronous replication",
"type": "object",
"default": {},
"default": {
"maxSyncReplicas": 0,
"minSyncReplicas": 0
},
"required": [
"maxSyncReplicas",
"minSyncReplicas"

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15

View File

@@ -18,7 +18,11 @@
"haproxy": {
"description": "HAProxy configuration",
"type": "object",
"default": {},
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "nano"
},
"required": [
"replicas",
"resources",
@@ -82,7 +86,11 @@
"nginx": {
"description": "Nginx configuration",
"type": "object",
"default": {},
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "nano"
},
"required": [
"replicas",
"resourcesPreset"

View File

@@ -10,7 +10,13 @@
"kafka": {
"description": "Kafka configuration",
"type": "object",
"default": {},
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": ""
},
"required": [
"replicas",
"resourcesPreset",
@@ -126,7 +132,13 @@
"zookeeper": {
"description": "Zookeeper configuration",
"type": "object",
"default": {},
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "5Gi",
"storageClass": ""
},
"required": [
"replicas",
"resourcesPreset",

View File

@@ -16,7 +16,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.29.1
version: 0.29.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to

View File

@@ -91,21 +91,21 @@ See the reference for components utilized in this service:
### Application-specific parameters
| Name | Description | Type | Value |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | ----------- |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.33` |
| `host` | Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty. | `string` | `""` |
| `nodeGroups` | Worker nodes configuration | `map[string]object` | `{...}` |
| `nodeGroups[name].minReplicas` | Minimum amount of replicas | `int` | `0` |
| `nodeGroups[name].maxReplicas` | Maximum amount of replicas | `int` | `10` |
| `nodeGroups[name].instanceType` | Virtual machine instance type | `string` | `u1.medium` |
| `nodeGroups[name].ephemeralStorage` | Ephemeral storage size | `quantity` | `20Gi` |
| `nodeGroups[name].roles` | List of node's roles | `[]string` | `[]` |
| `nodeGroups[name].resources` | Resources available to each worker node | `object` | `{}` |
| `nodeGroups[name].resources.cpu` | CPU available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].resources.memory` | Memory (RAM) available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].gpus` | List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM) | `[]object` | `{}` |
| `nodeGroups[name].gpus[i].name` | Name of GPU, such as "nvidia.com/AD102GL_L40S" | `string` | `""` |
| Name | Description | Type | Value |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.33` |
| `host` | Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty. | `string` | `""` |
| `nodeGroups` | Worker nodes configuration | `map[string]object` | `{...}` |
| `nodeGroups[name].minReplicas` | Minimum amount of replicas | `int` | `0` |
| `nodeGroups[name].maxReplicas` | Maximum amount of replicas | `int` | `0` |
| `nodeGroups[name].instanceType` | Virtual machine instance type | `string` | `""` |
| `nodeGroups[name].ephemeralStorage` | Ephemeral storage size | `quantity` | `""` |
| `nodeGroups[name].roles` | List of node's roles | `[]string` | `[]` |
| `nodeGroups[name].resources` | Resources available to each worker node | `object` | `{}` |
| `nodeGroups[name].resources.cpu` | CPU available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].resources.memory` | Memory (RAM) available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].gpus` | List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM) | `[]object` | `[]` |
| `nodeGroups[name].gpus.name` | Name of GPU, such as "nvidia.com/AD102GL_L40S" | `string` | `""` |
### Cluster Addons

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.1@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.0@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.1@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.1@sha256:f0e1d9f9e91be8e4a22be9fbe01a8b0e81aba4230b865fba9608ef7f9fb5745f
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.0@sha256:3a3bc912f70ccba1e9f92a0754179dbdc4c01f24073467b6d1406c77da794863

View File

@@ -5,7 +5,46 @@
"addons": {
"description": "Cluster addons configuration",
"type": "object",
"default": {},
"default": {
"certManager": {
"enabled": false,
"valuesOverride": {}
},
"cilium": {
"valuesOverride": {}
},
"coredns": {
"valuesOverride": {}
},
"fluxcd": {
"enabled": false,
"valuesOverride": {}
},
"gatewayAPI": {
"enabled": false
},
"gpuOperator": {
"enabled": false,
"valuesOverride": {}
},
"ingressNginx": {
"enabled": false,
"exposeMethod": "Proxied",
"hosts": {},
"valuesOverride": {}
},
"monitoringAgents": {
"enabled": false,
"valuesOverride": {}
},
"velero": {
"enabled": false,
"valuesOverride": {}
},
"verticalPodAutoscaler": {
"valuesOverride": {}
}
},
"required": [
"certManager",
"cilium",
@@ -22,7 +61,10 @@
"certManager": {
"description": "Cert-manager: automatically creates and manages SSL/TLS certificate",
"type": "object",
"default": {},
"default": {
"enabled": false,
"valuesOverride": {}
},
"required": [
"enabled",
"valuesOverride"
@@ -44,7 +86,9 @@
"cilium": {
"description": "Cilium CNI plugin",
"type": "object",
"default": {},
"default": {
"valuesOverride": {}
},
"required": [
"valuesOverride"
],
@@ -60,7 +104,9 @@
"coredns": {
"description": "Coredns",
"type": "object",
"default": {},
"default": {
"valuesOverride": {}
},
"required": [
"valuesOverride"
],
@@ -76,7 +122,10 @@
"fluxcd": {
"description": "Flux CD",
"type": "object",
"default": {},
"default": {
"enabled": false,
"valuesOverride": {}
},
"required": [
"enabled",
"valuesOverride"
@@ -98,7 +147,9 @@
"gatewayAPI": {
"description": "Gateway API",
"type": "object",
"default": {},
"default": {
"enabled": false
},
"required": [
"enabled"
],
@@ -113,7 +164,10 @@
"gpuOperator": {
"description": "GPU-operator: NVIDIA GPU Operator",
"type": "object",
"default": {},
"default": {
"enabled": false,
"valuesOverride": {}
},
"required": [
"enabled",
"valuesOverride"
@@ -135,7 +189,12 @@
"ingressNginx": {
"description": "Ingress-NGINX Controller",
"type": "object",
"default": {},
"default": {
"enabled": false,
"exposeMethod": "Proxied",
"hosts": {},
"valuesOverride": {}
},
"required": [
"enabled",
"exposeMethod",
@@ -175,7 +234,10 @@
"monitoringAgents": {
"description": "MonitoringAgents",
"type": "object",
"default": {},
"default": {
"enabled": false,
"valuesOverride": {}
},
"required": [
"enabled",
"valuesOverride"
@@ -197,7 +259,10 @@
"velero": {
"description": "Velero",
"type": "object",
"default": {},
"default": {
"enabled": false,
"valuesOverride": {}
},
"required": [
"enabled",
"valuesOverride"
@@ -219,7 +284,9 @@
"verticalPodAutoscaler": {
"description": "VerticalPodAutoscaler",
"type": "object",
"default": {},
"default": {
"valuesOverride": {}
},
"required": [
"valuesOverride"
],
@@ -237,7 +304,27 @@
"controlPlane": {
"description": "Control Plane Configuration",
"type": "object",
"default": {},
"default": {
"apiServer": {
"resources": {},
"resourcesPreset": "medium"
},
"controllerManager": {
"resources": {},
"resourcesPreset": "micro"
},
"konnectivity": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"replicas": 2,
"scheduler": {
"resources": {},
"resourcesPreset": "micro"
}
},
"required": [
"apiServer",
"controllerManager",
@@ -249,7 +336,10 @@
"apiServer": {
"description": "Control plane API server configuration.",
"type": "object",
"default": {},
"default": {
"resources": {},
"resourcesPreset": "medium"
},
"required": [
"resources",
"resourcesPreset"
@@ -307,7 +397,10 @@
"controllerManager": {
"description": "Controller Manager configuration.",
"type": "object",
"default": {},
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"required": [
"resources",
"resourcesPreset"
@@ -365,7 +458,12 @@
"konnectivity": {
"description": "Konnectivity configuration.",
"type": "object",
"default": {},
"default": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"required": [
"server"
],
@@ -373,7 +471,10 @@
"server": {
"description": "Konnectivity server configuration.",
"type": "object",
"default": {},
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"required": [
"resources",
"resourcesPreset"
@@ -438,7 +539,10 @@
"scheduler": {
"description": "Scheduler configuration.",
"type": "object",
"default": {},
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"required": [
"resources",
"resourcesPreset"
@@ -505,7 +609,7 @@
"default": {
"md0": {
"ephemeralStorage": "20Gi",
"gpus": [],
"gpus": {},
"instanceType": "u1.medium",
"maxReplicas": 10,
"minReplicas": 0,
@@ -527,7 +631,6 @@
"properties": {
"ephemeralStorage": {
"description": "Ephemeral storage size",
"default": "20Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
@@ -542,7 +645,6 @@
"gpus": {
"description": "List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
@@ -558,23 +660,19 @@
},
"instanceType": {
"description": "Virtual machine instance type",
"type": "string",
"default": "u1.medium"
"type": "string"
},
"maxReplicas": {
"description": "Maximum amount of replicas",
"type": "integer",
"default": 10
"type": "integer"
},
"minReplicas": {
"description": "Minimum amount of replicas",
"type": "integer",
"default": 0
"type": "integer"
},
"resources": {
"description": "Resources available to each worker node",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available to each worker node",

View File

@@ -9,17 +9,17 @@ version: "v1.33"
## @param host {string} Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty.
host: ""
## @param nodeGroups {map[string]nodeGroup} Worker nodes configuration
## @field nodeGroup {nodeGroup} Node configuration
## @field nodeGroup.minReplicas {int default=0} Minimum amount of replicas
## @field nodeGroup.maxReplicas {int default=10} Maximum amount of replicas
## @field nodeGroup.instanceType {string default="u1.medium"} Virtual machine instance type
## @field nodeGroup.ephemeralStorage {quantity default="20Gi"} Ephemeral storage size
## @field nodeGroup.roles {[]string default=[]} List of node's roles
## @field nodeGroup.resources {resources default={}} Resources available to each worker node
## @param nodeGroups {map[string]node} Worker nodes configuration
## @field node {node} Node configuration
## @field node.minReplicas {int} Minimum amount of replicas
## @field node.maxReplicas {int} Maximum amount of replicas
## @field node.instanceType {string} Virtual machine instance type
## @field node.ephemeralStorage {quantity} Ephemeral storage size
## @field node.roles {[]string} List of node's roles
## @field node.resources {resources} Resources available to each worker node
## @field resources.cpu {*quantity} CPU available to each worker node
## @field resources.memory {*quantity} Memory (RAM) available to each worker node
## @field nodeGroup.gpus {[]gpu default={}} List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)
## @field node.gpus {[]gpu} List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)
## @field gpu.name {string} Name of GPU, such as "nvidia.com/AD102GL_L40S"
##
nodeGroups:

View File

@@ -72,7 +72,7 @@ more details:
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of MariaDB replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each MariaDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each MariaDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -5,7 +5,16 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {},
"default": {
"cleanupStrategy": "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m",
"enabled": false,
"resticPassword": "\u003cpassword\u003e",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3Bucket": "s3.example.org/mysql-backups",
"s3Region": "us-east-1",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * *"
},
"required": [
"cleanupStrategy",
"enabled",

View File

@@ -10,7 +10,7 @@ It provides a data layer for cloud native applications, IoT messaging, and micro
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each NATS replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each NATS replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -5,7 +5,10 @@
"config": {
"description": "NATS configuration",
"type": "object",
"default": {},
"default": {
"merge": {},
"resolver": {}
},
"properties": {
"merge": {
"description": "Additional configuration to merge into NATS config (see example)",
@@ -29,7 +32,10 @@
"jetstream": {
"description": "Jetstream configuration",
"type": "object",
"default": {},
"default": {
"enabled": true,
"size": "10Gi"
},
"required": [
"enabled",
"size"

View File

@@ -69,7 +69,7 @@ See:
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of Postgres replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each PostgreSQL replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each PostgreSQL replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -5,7 +5,15 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {},
"default": {
"destinationPath": "s3://bucket/path/to/folder/",
"enabled": false,
"endpointURL": "http://minio-gateway-service:9000",
"retentionPolicy": "30d",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * * *"
},
"properties": {
"destinationPath": {
"description": "Path to store the backup (i.e. s3://bucket/path/to/folder)",
@@ -47,7 +55,11 @@
"bootstrap": {
"description": "Bootstrap configuration",
"type": "object",
"default": {},
"default": {
"enabled": false,
"oldName": "",
"recoveryTime": ""
},
"required": [
"enabled",
"oldName"
@@ -113,7 +125,11 @@
"postgresql": {
"description": "PostgreSQL server configuration",
"type": "object",
"default": {},
"default": {
"parameters": {
"max_connections": 100
}
},
"required": [
"parameters"
],
@@ -121,7 +137,9 @@
"parameters": {
"description": "PostgreSQL server parameters",
"type": "object",
"default": {},
"default": {
"max_connections": 100
},
"required": [
"max_connections"
],
@@ -138,7 +156,10 @@
"quorum": {
"description": "Quorum configuration for synchronous replication",
"type": "object",
"default": {},
"default": {
"maxSyncReplicas": 0,
"minSyncReplicas": 0
},
"required": [
"maxSyncReplicas",
"minSyncReplicas"

View File

@@ -16,7 +16,7 @@ The service utilizes official RabbitMQ operator. This ensures the reliability an
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of RabbitMQ replicas | `int` | `3` |
| `resources` | Explicit CPU and memory configuration for each RabbitMQ replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each RabbitMQ replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -16,7 +16,7 @@ Service utilizes the Spotahome Redis Operator for efficient management and orche
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of Redis replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each Redis replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each Redis replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -10,7 +10,14 @@
"httpAndHttps": {
"description": "HTTP and HTTPS configuration",
"type": "object",
"default": {},
"default": {
"endpoints": {},
"mode": "tcp",
"targetPorts": {
"http": 80,
"https": 443
}
},
"required": [
"mode",
"targetPorts"
@@ -36,7 +43,10 @@
"targetPorts": {
"description": "Target ports configuration",
"type": "object",
"default": {},
"default": {
"http": 80,
"https": 443
},
"required": [
"http",
"https"

View File

@@ -71,8 +71,7 @@ kubernetes 0.26.2 8ddbe32e
kubernetes 0.26.3 c02a3818
kubernetes 0.27.0 6cd5e746
kubernetes 0.28.0 7f477eec
kubernetes 0.29.0 87b23161
kubernetes 0.29.1 HEAD
kubernetes 0.29.0 HEAD
mysql 0.1.0 263e47be
mysql 0.2.0 c24a103f
mysql 0.3.0 53f2365e

View File

@@ -50,7 +50,7 @@ virtctl ssh <user>@<vm>
| `systemDisk.storageClass` | StorageClass used to store the data | `*string` | `replicated` |
| `gpus` | List of GPUs to attach | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU to attach. This should match the GPU resource name in the cluster. | `string` | `""` |
| `resources` | Resources | `*object` | `null` |
| `resources` | Resources | `*object` | `{}` |
| `resources.cpu` | The number of CPU cores allocated to the virtual machine | `*quantity` | `null` |
| `resources.sockets` | The number of CPU sockets allocated to the virtual machine (used to define vCPU topology) | `*quantity` | `null` |
| `resources.memory` | The amount of memory allocated to the virtual machine | `*quantity` | `null` |

View File

@@ -168,7 +168,11 @@
"systemDisk": {
"description": "System disk configuration",
"type": "object",
"default": {},
"default": {
"image": "ubuntu",
"storage": "5Gi",
"storageClass": "replicated"
},
"required": [
"image",
"storage"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -49,7 +49,7 @@ virtctl ssh <user>@<vm>
| `disks[i].bus` | Disk bus type, such as "sata" | `*string` | `null` |
| `gpus` | List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM) | `[]object` | `[]` |
| `gpus[i].name` | Name of GPU, such as "nvidia.com/AD102GL_L40S" | `string` | `""` |
| `resources` | Resources | `*object` | `null` |
| `resources` | Resources | `*object` | `{}` |
| `resources.cpu` | The number of CPU cores allocated to the virtual machine | `*quantity` | `null` |
| `resources.memory` | The amount of memory allocated to the virtual machine | `*quantity` | `null` |
| `resources.sockets` | The number of CPU sockets allocated to the virtual machine (used to define vCPU topology) | `*quantity` | `null` |

View File

@@ -22,7 +22,7 @@ Furthermore, Shadowbox is compatible with standard Shadowsocks clients, providin
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of VPN server replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each VPN server replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each VPN server replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -1,2 +1,2 @@
cozystack:
image: ghcr.io/cozystack/cozystack/installer:v0.36.2@sha256:052e82d1bdb8f04cda10a13084a5f539427f7466a8c938fe3ee238f81c43354b
image: ghcr.io/cozystack/cozystack/installer:v0.36.0-beta.4@sha256:51cb9828af3bdceac289de3b1161625065db22535a961958530e9c751880ee96

View File

@@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.2@sha256:5bab1f1699da5bd1e26e7d5231cf8984503594bf7d04ff8f56cc3c4674b6ab31
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.0-beta.4@sha256:c70ad3321fc14ca831c84bf6e7e6e5409bfe8130703173c277ca51db740c6cb3

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v0.36.2@sha256:cdf9d1cea38728cb17b1ab2807b6cb9adf30d4c7e80eaea0f73ab27680eb2d7a
ghcr.io/cozystack/cozystack/matchbox:v0.36.0-beta.4@sha256:764c547a352c9c1d0442e43cdfd0ef50b216bd7f6e5514c777b1d90c4d95da92

View File

@@ -4,12 +4,12 @@
### Common parameters
| Name | Description | Type | Value |
| ------------------ | ----------------------------------- | ----------- | ------ |
| `size` | Persistent Volume size | `*quantity` | `4Gi` |
| `storageClass` | StorageClass used to store the data | `*string` | `""` |
| `replicas` | Number of etcd replicas | `*int` | `3` |
| `resources` | Resource configuration for etcd | `*object` | `null` |
| `resources.cpu` | The number of CPU cores allocated | `*quantity` | `4` |
| `resources.memory` | The amount of memory allocated | `*quantity` | `1Gi` |
| Name | Description | Type | Value |
| ------------------ | ----------------------------------- | ----------- | ----- |
| `size` | Persistent Volume size | `*quantity` | `4Gi` |
| `storageClass` | StorageClass used to store the data | `*string` | `""` |
| `replicas` | Number of etcd replicas | `*int` | `3` |
| `resources` | Resource configuration for etcd | `*object` | `{}` |
| `resources.cpu` | The number of CPU cores allocated | `*quantity` | `4` |
| `resources.memory` | The amount of memory allocated | `*quantity` | `1Gi` |

View File

@@ -10,7 +10,10 @@
"resources": {
"description": "Resource configuration for etcd",
"type": "object",
"default": {},
"default": {
"cpu": 4,
"memory": "1Gi"
},
"properties": {
"cpu": {
"description": "The number of CPU cores allocated",

View File

@@ -9,7 +9,7 @@
| `replicas` | Number of ingress-nginx replicas | `int` | `2` |
| `whitelist` | List of client networks | `[]*string` | `[]` |
| `cloudflareProxy` | Restoring original visitor IPs when Cloudflare proxied is enabled | `bool` | `false` |
| `resources` | Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources` | Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -3,4 +3,4 @@ name: monitoring
description: Monitoring and observability stack
icon: /logos/monitoring.svg
type: application
version: 1.13.1
version: 1.13.0

View File

@@ -17,7 +17,7 @@
| `metricsStorages[i].name` | Name of the storage instance | `string` | `""` |
| `metricsStorages[i].retentionPeriod` | Retention period for the metrics in the storage instance | `string` | `""` |
| `metricsStorages[i].deduplicationInterval` | Deduplication interval for the metrics in the storage instance | `string` | `""` |
| `metricsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `10Gi` |
| `metricsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `""` |
| `metricsStorages[i].storageClassName` | StorageClass used to store the data | `*string` | `null` |
| `metricsStorages[i].vminsert` | Configuration for vminsert component of the storage instance | `*object` | `null` |
| `metricsStorages[i].vminsert.minAllowed` | Requests (minimum allowed/available resources) | `*object` | `null` |
@@ -44,13 +44,13 @@
### Logs storage configuration
| Name | Description | Type | Value |
| ---------------------------------- | ----------------------------------------------------- | ---------- | ------------ |
| `logsStorages` | Configuration of logs storage instances | `[]object` | `[...]` |
| `logsStorages[i].name` | Name of the storage instance | `string` | `""` |
| `logsStorages[i].retentionPeriod` | Retention period for the logs in the storage instance | `string` | `1` |
| `logsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `10Gi` |
| `logsStorages[i].storageClassName` | StorageClass used to store the data | `*string` | `replicated` |
| Name | Description | Type | Value |
| ---------------------------------- | ----------------------------------------------------- | ---------- | ------- |
| `logsStorages` | Configuration of logs storage instances | `[]object` | `[...]` |
| `logsStorages[i].name` | Name of the storage instance | `string` | `""` |
| `logsStorages[i].retentionPeriod` | Retention period for the logs in the storage instance | `string` | `""` |
| `logsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `""` |
| `logsStorages[i].storageClassName` | StorageClass used to store the data | `*string` | `null` |
### Alerta configuration

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/grafana:1.13.1@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399
ghcr.io/cozystack/cozystack/grafana:1.13.0@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399

View File

@@ -5,17 +5,47 @@
"alerta": {
"description": "Configuration for Alerta service",
"type": "object",
"default": {},
"default": {
"alerts": {
"telegram": {
"chatID": "",
"disabledSeverity": "",
"token": ""
}
},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"storage": "10Gi",
"storageClassName": ""
},
"properties": {
"alerts": {
"description": "Configuration for alerts",
"type": "object",
"default": {},
"default": {
"telegram": {
"chatID": "",
"disabledSeverity": "",
"token": ""
}
},
"properties": {
"telegram": {
"description": "Configuration for Telegram alerts",
"type": "object",
"default": {},
"default": {
"chatID": "",
"disabledSeverity": "",
"token": ""
},
"required": [
"chatID",
"disabledSeverity",
@@ -41,11 +71,23 @@
"resources": {
"description": "Resources configuration",
"type": "object",
"default": {},
"default": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"properties": {
"limits": {
"type": "object",
"default": {},
"default": {
"cpu": "1",
"memory": "1Gi"
},
"properties": {
"cpu": {
"description": "CPU limit (maximum available CPU)",
@@ -79,7 +121,10 @@
},
"requests": {
"type": "object",
"default": {},
"default": {
"cpu": "100m",
"memory": "256Mi"
},
"properties": {
"cpu": {
"description": "CPU request (minimum available CPU)",
@@ -127,12 +172,28 @@
"grafana": {
"description": "Configuration for Grafana",
"type": "object",
"default": {},
"default": {
"db": {
"size": "10Gi"
},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
}
},
"properties": {
"db": {
"description": "Database configuration",
"type": "object",
"default": {},
"default": {
"size": "10Gi"
},
"properties": {
"size": {
"description": "Persistent Volume size for the database",
@@ -144,11 +205,23 @@
"resources": {
"description": "Resources configuration",
"type": "object",
"default": {},
"default": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"properties": {
"limits": {
"type": "object",
"default": {},
"default": {
"cpu": "1",
"memory": "1Gi"
},
"properties": {
"cpu": {
"description": "CPU limit (maximum available CPU)",
@@ -182,7 +255,10 @@
},
"requests": {
"type": "object",
"default": {},
"default": {
"cpu": "100m",
"memory": "256Mi"
},
"properties": {
"cpu": {
"description": "CPU request (minimum available CPU)",
@@ -247,18 +323,15 @@
},
"retentionPeriod": {
"description": "Retention period for the logs in the storage instance",
"type": "string",
"default": "1"
"type": "string"
},
"storage": {
"description": "Persistent Volume size for the storage instance",
"type": "string",
"default": "10Gi"
"type": "string"
},
"storageClassName": {
"description": "StorageClass used to store the data",
"type": "string",
"default": "replicated"
"type": "string"
}
}
}
@@ -305,8 +378,7 @@
},
"storage": {
"description": "Persistent Volume size for the storage instance",
"type": "string",
"default": "10Gi"
"type": "string"
},
"storageClassName": {
"description": "StorageClass used to store the data",

View File

@@ -9,7 +9,7 @@ host: ""
## @field metricsStorage.name {string} Name of the storage instance
## @field metricsStorage.retentionPeriod {string} Retention period for the metrics in the storage instance
## @field metricsStorage.deduplicationInterval {string} Deduplication interval for the metrics in the storage instance
## @field metricsStorage.storage {string default="10Gi"} Persistent Volume size for the storage instance
## @field metricsStorage.storage {string} Persistent Volume size for the storage instance
## @field metricsStorage.storageClassName {*string} StorageClass used to store the data
## @field metricsStorage.vminsert {*vmcomponent} Configuration for vminsert component of the storage instance
## @field metricsStorage.vmselect {*vmcomponent} Configuration for vmselect component of the storage instance
@@ -69,9 +69,9 @@ metricsStorages:
## @param logsStorages {[]logsStorage} Configuration of logs storage instances
## @field logsStorage.name {string} Name of the storage instance
## @field logsStorage.retentionPeriod {string default=1} Retention period for the logs in the storage instance
## @field logsStorage.storage {string default="10Gi"} Persistent Volume size for the storage instance
## @field logsStorage.storageClassName {*string default="replicated"} StorageClass used to store the data
## @field logsStorage.retentionPeriod {string} Retention period for the logs in the storage instance
## @field logsStorage.storage {string} Persistent Volume size for the storage instance
## @field logsStorage.storageClassName {*string} StorageClass used to store the data
##
logsStorages:
- name: generic

View File

@@ -13,46 +13,46 @@
### SeaweedFS Components Configuration
| Name | Description | Type | Value |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `db` | Database Configuration | `object` | `{}` |
| `db.replicas` | Number of database replicas | `*int` | `2` |
| `db.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `db.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `db.resources` | Explicit CPU and memory configuration for the database. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `db.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `db.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `db.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `master` | Master service configuration | `*object` | `null` |
| `master.replicas` | Number of master replicas | `*int` | `3` |
| `master.resources` | Explicit CPU and memory configuration for the master. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `master.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `master.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `master.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer` | Filer service configuration | `*object` | `null` |
| `filer.replicas` | Number of filer replicas | `*int` | `2` |
| `filer.resources` | Explicit CPU and memory configuration for the filer. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `filer.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `filer.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `filer.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer.grpcHost` | The hostname used to expose or access the filer service externally. | `*string` | `""` |
| `filer.grpcPort` | The port used to access the filer service externally. | `*int` | `443` |
| `filer.whitelist` | A list of IP addresses or CIDR ranges that are allowed to access the filer service. | `[]*string` | `[]` |
| `volume` | Volume service configuration | `*object` | `null` |
| `volume.replicas` | Number of volume replicas | `*int` | `2` |
| `volume.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `volume.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `volume.resources` | Explicit CPU and memory configuration for the volume. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `volume.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `volume.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `volume.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `volume.zones` | A map of zones for MultiZone topology. Each zone can have its own number of replicas and size. | `map[string]object` | `{}` |
| `volume.zones[name].replicas` | Number of replicas in the zone | `*int` | `null` |
| `volume.zones[name].size` | Zone storage size | `*quantity` | `null` |
| `s3` | S3 service configuration | `*object` | `null` |
| `s3.replicas` | Number of s3 replicas | `*int` | `2` |
| `s3.resources` | Explicit CPU and memory configuration for the s3. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `s3.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `s3.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `s3.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| Name | Description | Type | Value |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `db` | Database Configuration | `object` | `{}` |
| `db.replicas` | Number of database replicas | `*int` | `2` |
| `db.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `db.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `db.resources` | Explicit CPU and memory configuration for the database. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `db.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `db.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `db.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `master` | Master service configuration | `*object` | `{}` |
| `master.replicas` | Number of master replicas | `*int` | `3` |
| `master.resources` | Explicit CPU and memory configuration for the master. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `master.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `master.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `master.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer` | Filer service configuration | `*object` | `{}` |
| `filer.replicas` | Number of filer replicas | `*int` | `2` |
| `filer.resources` | Explicit CPU and memory configuration for the filer. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `filer.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `filer.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `filer.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `filer.grpcHost` | The hostname used to expose or access the filer service externally. | `*string` | `""` |
| `filer.grpcPort` | The port used to access the filer service externally. | `*int` | `443` |
| `filer.whitelist` | A list of IP addresses or CIDR ranges that are allowed to access the filer service. | `[]*string` | `[]` |
| `volume` | Volume service configuration | `*object` | `{}` |
| `volume.replicas` | Number of volume replicas | `*int` | `2` |
| `volume.size` | Persistent Volume size | `*quantity` | `10Gi` |
| `volume.storageClass` | StorageClass used to store the data | `*string` | `""` |
| `volume.resources` | Explicit CPU and memory configuration for the volume. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `volume.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `volume.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `volume.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
| `volume.zones` | A map of zones for MultiZone topology. Each zone can have its own number of replicas and size. | `map[string]object` | `{}` |
| `volume.zones.replicas` | Number of replicas in the zone | `*int` | `null` |
| `volume.zones.size` | Zone storage size | `*quantity` | `null` |
| `s3` | S3 service configuration | `*object` | `{}` |
| `s3.replicas` | Number of s3 replicas | `*int` | `2` |
| `s3.resources` | Explicit CPU and memory configuration for the s3. When left empty, the preset defined in `resourcesPreset` is applied. | `object` | `{}` |
| `s3.resources.cpu` | The number of CPU cores allocated | `*quantity` | `null` |
| `s3.resources.memory` | The amount of memory allocated | `*quantity` | `null` |
| `s3.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.2@sha256:9d990fdcf5ebc85b42d4e3c94196e84b06ea89d6b59e1adf180c22e7da25fd60
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.0-beta.4@sha256:24451989b15b6801b33ad355a5507307d0333bf9afd240f1db0aca9c92f6b2ad

View File

@@ -5,7 +5,13 @@
"db": {
"description": "Database Configuration",
"type": "object",
"default": {},
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": ""
},
"required": [
"resources",
"resourcesPreset"
@@ -86,7 +92,14 @@
"filer": {
"description": "Filer service configuration",
"type": "object",
"default": {},
"default": {
"grpcHost": "",
"grpcPort": 443,
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"whitelist": {}
},
"required": [
"resources",
"resourcesPreset"
@@ -170,7 +183,11 @@
"master": {
"description": "Master service configuration",
"type": "object",
"default": {},
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small"
},
"required": [
"resources",
"resourcesPreset"
@@ -238,7 +255,11 @@
"s3": {
"description": "S3 service configuration",
"type": "object",
"default": {},
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small"
},
"required": [
"resources",
"resourcesPreset"
@@ -311,7 +332,14 @@
"volume": {
"description": "Volume service configuration",
"type": "object",
"default": {},
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": "",
"zones": {}
},
"required": [
"resources",
"resourcesPreset"

View File

@@ -56,8 +56,7 @@ monitoring 1.10.1 8c86905b
monitoring 1.11.0 4369b031
monitoring 1.12.0 0e47e1e8
monitoring 1.12.1 c02a3818
monitoring 1.13.0 87b23161
monitoring 1.13.1 HEAD
monitoring 1.13.0 HEAD
seaweedfs 0.1.0 71514249
seaweedfs 0.2.0 5fb9cfe3
seaweedfs 0.2.1 fde4bcfa

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:1131d7d391585fee6175ca1422919edb4e1fa9a7aa8a8a34d457e6c3a0b8b5d6
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:b9a401defb90a822087e50e7ab6afd9b4db7e71728030f92c7d320ac46889053

View File

@@ -1,5 +1,4 @@
coredns:
image:
repository: registry.k8s.io/coredns/coredns
tag: v1.12.4
replicaCount: 2

View File

@@ -1,2 +1,2 @@
cozystackAPI:
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.36.2@sha256:a9ce8848b0a46e52ce47ad10fcccc4e848740f5cf1d4e6cb78fc4a196e167e1b
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.36.0-beta.4@sha256:c1d6534b36a24f365d64383fd3deff469a71565200ae1789eabf78e3cd9a3601

View File

@@ -0,0 +1,45 @@
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: cozystack-controller-webhook-selfsigned
namespace: {{ .Release.Namespace }}
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cozystack-controller-webhook-ca
namespace: {{ .Release.Namespace }}
spec:
secretName: cozystack-controller-webhook-ca
duration: 43800h # 5 years
commonName: cozystack-controller-webhook-ca
issuerRef:
name: cozystack-controller-webhook-selfsigned
isCA: true
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: cozystack-controller-webhook-ca
namespace: {{ .Release.Namespace }}
spec:
ca:
secretName: cozystack-controller-webhook-ca
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cozystack-controller-webhook
namespace: {{ .Release.Namespace }}
spec:
secretName: cozystack-controller-webhook-cert
duration: 8760h
renewBefore: 720h
issuerRef:
name: cozystack-controller-webhook-ca
commonName: cozystack-controller
dnsNames:
- cozystack-controller
- cozystack-controller.{{ .Release.Namespace }}.svc

View File

@@ -2,7 +2,6 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: cozystack-controller
namespace: cozy-system
labels:
app: cozystack-controller
spec:
@@ -29,3 +28,15 @@ spec:
{{- if .Values.cozystackController.disableTelemetry }}
- --disable-telemetry
{{- end }}
ports:
- name: webhook
containerPort: 9443
volumeMounts:
- name: webhook-certs
mountPath: /tmp/k8s-webhook-server/serving-certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: cozystack-controller-webhook-cert
defaultMode: 0400

View File

@@ -0,0 +1,33 @@
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: lineage
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/cozystack-controller-webhook
labels:
app: cozystack-controller
webhooks:
- name: lineage.cozystack.io
admissionReviewVersions: ["v1"]
sideEffects: None
clientConfig:
service:
name: cozystack-controller
namespace: {{ .Release.Namespace }}
path: /mutate-lineage
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods","secrets", "services", "persistentvolumeclaims"]
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: cozystack.io/system
operator: NotIn
values:
- "true"
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: cozystack-controller
labels:
app: cozystack-controller
spec:
type: ClusterIP
ports:
- port: 443
targetPort: 9443
protocol: TCP
name: webhook
selector:
app: cozystack-controller

View File

@@ -1,5 +1,5 @@
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.36.2@sha256:3c1752a3d33d85915c8f807077f7c42d194c779ea5bfd4ceebf0f1accff670ce
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.36.0-beta.4@sha256:f4f8fa8e2f33f66d90b99398025d0da1328b530a622a9b420c993e51e1302992
debug: false
disableTelemetry: false
cozystackVersion: "v0.36.2"
cozystackVersion: "v0.36.0-beta.4"

View File

@@ -76,7 +76,7 @@ data:
"kubeappsNamespace": {{ .Release.Namespace | quote }},
"helmGlobalNamespace": {{ include "kubeapps.helmGlobalPackagingNamespace" . | quote }},
"carvelGlobalNamespace": {{ .Values.kubeappsapis.pluginConfig.kappController.packages.v1alpha1.globalPackagingNamespace | quote }},
"appVersion": "v0.36.2",
"appVersion": "v0.36.0-beta.4",
"authProxyEnabled": {{ .Values.authProxy.enabled }},
"oauthLoginURI": {{ .Values.authProxy.oauthLoginURI | quote }},
"oauthLogoutURI": {{ .Values.authProxy.oauthLogoutURI | quote }},

View File

@@ -1,4 +1,4 @@
FROM bitnamilegacy/node:20.15.1 AS build
FROM bitnami/node:20.15.1 AS build
WORKDIR /app
ARG COMMIT_REF=4926bc68fabb0914afab574006643c85a597b371
@@ -26,5 +26,5 @@ RUN sed -i \
-e 's/#1b2b32/#2a2d2f/g' \
$(grep -rl "#2d4048\|#25333d\|#fcfdfd\|#f1f6f8\|#e3eaed\|#cbd4d8\|#aeb8bc\|#859399\|#6a7a81\|#4f6169\|#3a4d55\|#2d4048\|#21333b\|#1b2b32")
FROM bitnamilegacy/nginx:1.25.2
FROM bitnami/nginx:1.25.2
COPY --from=build /app/build /app

View File

@@ -9,7 +9,7 @@ RUN apk add --no-cache patch
WORKDIR /source
RUN wget -O- https://github.com/cozystack/kubeapps/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
FROM bitnamilegacy/golang:1.23.4 AS builder
FROM bitnami/golang:1.23.4 AS builder
WORKDIR /go/src/github.com/vmware-tanzu/kubeapps
COPY --from=source /source/go.mod /source/go.sum ./
ARG TARGETOS

View File

@@ -19,7 +19,7 @@ kubeapps:
image:
registry: ghcr.io/cozystack/cozystack
repository: dashboard
tag: v0.36.2
tag: v0.36.0-beta.4
digest: "sha256:54906b3d2492c8603a347a5938b6db36e5ed5c4149111cae1804ac9110361947"
frontend:
image:
@@ -48,8 +48,8 @@ kubeapps:
image:
registry: ghcr.io/cozystack/cozystack
repository: kubeapps-apis
tag: v0.36.2
digest: "sha256:a0f85b493494fe874f84657911617f4d64479705953da6196349d7601965733d"
tag: v0.36.0-beta.4
digest: "sha256:c4b268996c96d23bc11b6d109fb7fb51faf05576ee326097889eca72c851b656"
pluginConfig:
flux:
packages:
@@ -240,7 +240,7 @@ kubeapps:
- application:
kind: FerretDB
singular: ferretdb
plural: ferretdbs
plural: ferretdb
release:
prefix: ferretdb-
labels:

View File

@@ -3,7 +3,7 @@ kamaji:
deploy: false
image:
pullPolicy: IfNotPresent
tag: v0.36.2@sha256:4665919d75578856a64fd141065d0460df925be3daafc093b5b84b3aaf4d7270
tag: v0.36.0-beta.4@sha256:bcf8ccde72e5f8619b626a290c1e4c81018d9a10497e66948afebabd80a64023
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.36.2@sha256:4665919d75578856a64fd141065d0460df925be3daafc093b5b84b3aaf4d7270
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.36.0-beta.4@sha256:bcf8ccde72e5f8619b626a290c1e4c81018d9a10497e66948afebabd80a64023

View File

@@ -1,4 +1,4 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.36.2@sha256:5971d0b14056ee1a4efac8022034471f9afda2e053a401b53920ba9bc1e74e83
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.36.0-beta.4@sha256:fb334a9cf9b6fa606b2530cb4227e0de303761151ff93aa52209dcbcf8b33ef8
ovnCentralName: ovn-central

View File

@@ -1,3 +1,3 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.36.2@sha256:2789e4f76833a15a2950171596d8839b69e95c4c2459698b0bc192957f575ec0
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.36.0-beta.4@sha256:583922648d9e39f4e4d2255a50f5db74b1aa2b1590982e891d0ea6d6cb9ec453

View File

@@ -64,4 +64,4 @@ global:
images:
kubeovn:
repository: kubeovn
tag: v1.14.5@sha256:6bfa3d36e1d7b3992d8e7018e8df159ebe9f675ba02f2903b6e441f516b2f4a4
tag: v1.14.5@sha256:8968977ba60e1fb14121984899ddf38f7fe4ea800806a7a30db007110064c84b

View File

@@ -1,3 +1,3 @@
storageClass: replicated
csiDriver:
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.1@sha256:f0e1d9f9e91be8e4a22be9fbe01a8b0e81aba4230b865fba9608ef7f9fb5745f
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.0@sha256:3a3bc912f70ccba1e9f92a0754179dbdc4c01f24073467b6d1406c77da794863

View File

@@ -1,3 +1,3 @@
objectstorage:
controller:
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.36.2@sha256:aa0000265ae58155aebefedac72d0a6acc45437b8668bb9739bf11edefec067a"
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.36.0-beta.4@sha256:aa0000265ae58155aebefedac72d0a6acc45437b8668bb9739bf11edefec067a"

View File

@@ -118,7 +118,7 @@ seaweedfs:
bucketClassName: "seaweedfs"
region: ""
sidecar:
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.2@sha256:9d990fdcf5ebc85b42d4e3c94196e84b06ea89d6b59e1adf180c22e7da25fd60"
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.0-beta.4@sha256:24451989b15b6801b33ad355a5507307d0333bf9afd240f1db0aca9c92f6b2ad"
certificates:
commonName: "SeaweedFS CA"
ipAddresses: []

193
pkg/lineage/lineage.go Normal file
View File

@@ -0,0 +1,193 @@
package lineage
import (
"context"
"fmt"
"os"
"strings"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/controller-runtime/pkg/log"
)
const (
HRAPIVersion = "helm.toolkit.fluxcd.io/v2"
HRKind = "HelmRelease"
HRLabel = "helm.toolkit.fluxcd.io/name"
)
type ObjectID struct {
APIVersion string
Kind string
Namespace string
Name string
}
func (o ObjectID) GetUnstructured(ctx context.Context, client dynamic.Interface, mapper meta.RESTMapper) (*unstructured.Unstructured, error) {
u, err := getUnstructuredObject(ctx, client, mapper, o.APIVersion, o.Kind, o.Namespace, o.Name)
if err != nil {
return nil, err
}
return u, nil
}
func WalkOwnershipGraph(
ctx context.Context,
client dynamic.Interface,
mapper meta.RESTMapper,
appMapper AppMapper,
obj *unstructured.Unstructured,
memory ...interface{},
) (out []ObjectID) {
id := ObjectID{APIVersion: obj.GetAPIVersion(), Kind: obj.GetKind(), Namespace: obj.GetNamespace(), Name: obj.GetName()}
out = []ObjectID{}
l := log.FromContext(ctx)
l.Info("processing object", "apiVersion", obj.GetAPIVersion(), "kind", obj.GetKind(), "name", obj.GetName())
var visited map[ObjectID]bool
var ok bool
if len(memory) == 1 {
visited, ok = memory[0].(map[ObjectID]bool)
if !ok {
l.Error(
fmt.Errorf("invalid argument"), "could not parse visited map in WalkOwnershipGraph call",
"received", memory[0], "expected", "map[ObjectID]bool",
)
return out
}
}
if len(memory) == 0 {
visited = make(map[ObjectID]bool)
}
if len(memory) != 0 && len(memory) != 1 {
l.Error(
fmt.Errorf("invalid argument count"), "could not parse variadic arguments to WalkOwnershipGraph",
"args passed", len(memory)+5, "expected args", "4|5",
)
return out
}
if visited[id] {
return out
}
visited[id] = true
ownerRefs := obj.GetOwnerReferences()
for _, owner := range ownerRefs {
ownerObj, err := getUnstructuredObject(ctx, client, mapper, owner.APIVersion, owner.Kind, obj.GetNamespace(), owner.Name)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not fetch owner %s/%s (%s): %v\n", obj.GetNamespace(), owner.Name, owner.Kind, err)
continue
}
out = append(out, WalkOwnershipGraph(ctx, client, mapper, appMapper, ownerObj, visited)...)
}
// if object has owners, it couldn't be owned directly by the custom app
if len(ownerRefs) > 0 {
return
}
// I want "if err1 != nil go to next block, if err2 != nil, go to next block, etc semantics",
// like an early return from a function, but if all checks succeed, I don't want to do the rest
// of the function, so it's a `for { if err { break } if othererr { break } if allgood { return }
for {
if obj.GetAPIVersion() != HRAPIVersion || obj.GetKind() != HRKind {
break
}
hr := helmReleaseFromUnstructured(obj)
if hr == nil {
break
}
a, k, p, err := appMapper.Map(hr)
if err != nil {
break
}
ownerObj, err := getUnstructuredObject(ctx, client, mapper, a, k, obj.GetNamespace(), strings.TrimPrefix(obj.GetName(), p))
if err != nil {
break
}
// successfully mapped a HelmRelease to a custom app, no need to continue
out = append(out,
ObjectID{
APIVersion: ownerObj.GetAPIVersion(),
Kind: ownerObj.GetKind(),
Namespace: ownerObj.GetNamespace(),
Name: ownerObj.GetName(),
},
)
return
}
labels := obj.GetLabels()
name, ok := labels[HRLabel]
if !ok {
return
}
ownerObj, err := getUnstructuredObject(ctx, client, mapper, HRAPIVersion, HRKind, obj.GetNamespace(), name)
if err != nil {
return
}
out = append(out, WalkOwnershipGraph(ctx, client, mapper, appMapper, ownerObj, visited)...)
return
}
func getUnstructuredObject(
ctx context.Context,
client dynamic.Interface,
mapper meta.RESTMapper,
apiVersion, kind, namespace, name string,
) (*unstructured.Unstructured, error) {
l := log.FromContext(ctx)
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
l.Error(
err, "failed to parse groupversion",
"apiVersion", apiVersion,
)
return nil, err
}
gvk := schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: kind,
}
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
l.Error(err, "Could not map GVK "+gvk.String())
return nil, err
}
ns := namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
ns = ""
}
ownerObj, err := client.Resource(mapping.Resource).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return ownerObj, nil
}
func helmReleaseFromUnstructured(obj *unstructured.Unstructured) *helmv2.HelmRelease {
if obj.GetAPIVersion() == HRAPIVersion && obj.GetKind() == HRKind {
hr := &helmv2.HelmRelease{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, hr); err == nil {
return hr
}
}
return nil
}

View File

@@ -0,0 +1,53 @@
package lineage
import (
"context"
"fmt"
"os"
"testing"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var (
dynClient dynamic.Interface
mapper meta.RESTMapper
l logr.Logger
ctx context.Context
)
func init() {
cfg := config.GetConfigOrDie()
dynClient, _ = dynamic.NewForConfig(cfg)
discoClient, _ := discovery.NewDiscoveryClientForConfig(cfg)
cachedDisco := memory.NewMemCacheClient(discoClient)
mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDisco)
zapLogger, _ := zap.NewDevelopment()
l = zapr.NewLogger(zapLogger)
ctx = logr.NewContext(context.Background(), l)
}
func TestWalkingOwnershipGraph(t *testing.T) {
obj, err := dynClient.Resource(schema.GroupVersionResource{"", "v1", "pods"}).Namespace(os.Args[1]).Get(ctx, os.Args[2], metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj)
for _, node := range nodes {
fmt.Printf("%#v\n", node)
}
}

49
pkg/lineage/mapper.go Normal file
View File

@@ -0,0 +1,49 @@
package lineage
import (
"fmt"
"strings"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
)
type AppMapper interface {
Map(*helmv2.HelmRelease) (apiVersion, kind, prefix string, err error)
}
type stubMapper struct{}
var stubMapperMap = map[string]string{
"cozystack-extra/bootbox": "apps.cozystack.io/v1alpha1/BootBox/",
"cozystack-apps/bucket": "apps.cozystack.io/v1alpha1/Bucket/bucket-",
"cozystack-apps/clickhouse": "apps.cozystack.io/v1alpha1/ClickHouse/clickhouse-",
"cozystack-extra/etcd": "apps.cozystack.io/v1alpha1/Etcd/",
"cozystack-apps/ferretdb": "apps.cozystack.io/v1alpha1/FerretDB/ferretdb-",
"cozystack-apps/http-cache": "apps.cozystack.io/v1alpha1/HTTPCache/http-cache-",
"cozystack-extra/info": "apps.cozystack.io/v1alpha1/Info/",
"cozystack-extra/ingress": "apps.cozystack.io/v1alpha1/Ingress/",
"cozystack-apps/kafka": "apps.cozystack.io/v1alpha1/Kafka/kafka-",
"cozystack-apps/kubernetes": "apps.cozystack.io/v1alpha1/Kubernetes/kubernetes-",
"cozystack-extra/monitoring": "apps.cozystack.io/v1alpha1/Monitoring/",
"cozystack-apps/mysql": "apps.cozystack.io/v1alpha1/MySQL/mysql-",
"cozystack-apps/nats": "apps.cozystack.io/v1alpha1/NATS/nats-",
"cozystack-apps/postgres": "apps.cozystack.io/v1alpha1/Postgres/postgres-",
"cozystack-apps/rabbitmq": "apps.cozystack.io/v1alpha1/RabbitMQ/rabbitmq-",
"cozystack-apps/redis": "apps.cozystack.io/v1alpha1/Redis/redis-",
"cozystack-extra/seaweedfs": "apps.cozystack.io/v1alpha1/SeaweedFS/",
"cozystack-apps/tcp-balancer": "apps.cozystack.io/v1alpha1/TCPBalancer/tcp-balancer-",
"cozystack-apps/tenant": "apps.cozystack.io/v1alpha1/Tenant/tenant-",
"cozystack-apps/virtual-machine": "apps.cozystack.io/v1alpha1/VirtualMachine/virtual-machine-",
"cozystack-apps/vm-disk": "apps.cozystack.io/v1alpha1/VMDisk/vm-disk-",
"cozystack-apps/vm-instance": "apps.cozystack.io/v1alpha1/VMInstance/vm-instance-",
"cozystack-apps/vpn": "apps.cozystack.io/v1alpha1/VPN/vpn-",
}
func (s *stubMapper) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
val, ok := stubMapperMap[hr.Spec.Chart.Spec.SourceRef.Name+"/"+hr.Spec.Chart.Spec.Chart]
if !ok {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
split := strings.Split(val, "/")
return strings.Join(split[:2], "/"), split[2], split[3], nil
}

View File

@@ -45,6 +45,7 @@ import (
internalapiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
schemadefault "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
// Importing API errors package to construct appropriate error responses
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -88,24 +89,12 @@ type REST struct {
// NewREST creates a new REST storage for Application with specific configuration
func NewREST(dynamicClient dynamic.Interface, config *config.Resource) *REST {
var specSchema *structuralschema.Structural
if raw := strings.TrimSpace(config.Application.OpenAPISchema); raw != "" {
var v1js apiextv1.JSONSchemaProps
if err := json.Unmarshal([]byte(raw), &v1js); err != nil {
klog.Errorf("Failed to unmarshal v1 OpenAPI schema: %v", err)
} else {
scheme := runtime.NewScheme()
_ = internalapiext.AddToScheme(scheme)
_ = apiextv1.AddToScheme(scheme)
var ijs internalapiext.JSONSchemaProps
if err := scheme.Convert(&v1js, &ijs, nil); err != nil {
klog.Errorf("Failed to convert v1->internal JSONSchemaProps: %v", err)
} else if s, err := structuralschema.NewStructural(&ijs); err != nil {
klog.Errorf("Failed to create structural schema: %v", err)
} else {
specSchema = s
}
var js internalapiext.JSONSchemaProps
if err := json.Unmarshal([]byte(raw), &js); err != nil {
klog.Errorf("Failed to unmarshal OpenAPI schema: %v", err)
} else if specSchema, err = structuralschema.NewStructural(&js); err != nil {
klog.Errorf("Failed to create structural schema: %v", err)
}
}
@@ -1216,3 +1205,28 @@ func (e errNotAcceptable) Status() metav1.Status {
Message: e.Error(),
}
}
// applySpecDefaults applies default values to the Application spec based on the schema
func (r *REST) applySpecDefaults(app *appsv1alpha1.Application) error {
if r.specSchema == nil {
return nil
}
var m map[string]any
if app.Spec != nil && len(app.Spec.Raw) > 0 {
if err := json.Unmarshal(app.Spec.Raw, &m); err != nil {
return err
}
}
if m == nil {
m = map[string]any{}
}
schemadefault.Default(m, r.specSchema)
raw, err := json.Marshal(m)
if err != nil {
return err
}
app.Spec = &apiextv1.JSON{Raw: raw}
return nil
}

View File

@@ -1,187 +0,0 @@
/*
Copyright 2024 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package application
import (
"encoding/json"
"fmt"
appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
)
// applySpecDefaults applies default values to the Application spec based on the schema
func (r *REST) applySpecDefaults(app *appsv1alpha1.Application) error {
if r.specSchema == nil {
return nil
}
var m map[string]any
if app.Spec != nil && len(app.Spec.Raw) > 0 {
if err := json.Unmarshal(app.Spec.Raw, &m); err != nil {
return err
}
}
if m == nil {
m = map[string]any{}
}
if err := defaultLikeKubernetes(&m, r.specSchema); err != nil {
return err
}
raw, err := json.Marshal(m)
if err != nil {
return err
}
app.Spec = &apiextv1.JSON{Raw: raw}
return nil
}
func defaultLikeKubernetes(root *map[string]any, s *structuralschema.Structural) error {
v := any(*root)
nv, err := applyDefaults(v, s, true)
if err != nil {
return err
}
obj, ok := nv.(map[string]any)
if !ok && nv != nil {
return fmt.Errorf("internal error: applyDefaults returned non-map type %T for object root", nv)
}
if obj == nil {
obj = map[string]any{}
}
*root = obj
return nil
}
func applyDefaults(v any, s *structuralschema.Structural, top bool) (any, error) {
if s == nil {
return v, nil
}
effType := s.Generic.Type
if effType == "" {
switch {
case len(s.Properties) > 0 || (s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil):
effType = "object"
case s.Items != nil:
effType = "array"
default:
// scalar
}
}
switch effType {
case "object":
mv, isMap := v.(map[string]any)
if !isMap || v == nil {
if s.Generic.Default.Object != nil && !top {
if dm, ok := s.Generic.Default.Object.(map[string]any); ok {
mv = cloneMap(dm)
}
}
if mv == nil {
mv = map[string]any{}
}
}
for name, ps := range s.Properties {
if _, ok := mv[name]; !ok {
if ps.Generic.Default.Object != nil {
mv[name] = clone(ps.Generic.Default.Object)
}
}
if cur, ok := mv[name]; ok {
cv, err := applyDefaults(cur, &ps, false)
if err != nil {
return nil, err
}
mv[name] = cv
}
}
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
ap := s.AdditionalProperties.Structural
for k, cur := range mv {
if _, isKnown := s.Properties[k]; isKnown {
continue
}
cv, err := applyDefaults(cur, ap, false)
if err != nil {
return nil, err
}
mv[k] = cv
}
}
return mv, nil
case "array":
sl, isSlice := v.([]any)
if !isSlice || v == nil {
if s.Generic.Default.Object != nil {
if ds, ok := s.Generic.Default.Object.([]any); ok {
sl = cloneSlice(ds)
}
}
if sl == nil {
sl = []any{}
}
}
if s.Items != nil {
for i := range sl {
cv, err := applyDefaults(sl[i], s.Items, false)
if err != nil {
return nil, err
}
sl[i] = cv
}
}
return sl, nil
default:
if v == nil && s.Generic.Default.Object != nil {
return clone(s.Generic.Default.Object), nil
}
return v, nil
}
}
func clone(x any) any {
switch t := x.(type) {
case map[string]any:
return cloneMap(t)
case []any:
return cloneSlice(t)
default:
return t
}
}
func cloneMap(m map[string]any) map[string]any {
out := make(map[string]any, len(m))
for k, v := range m {
out[k] = clone(v)
}
return out
}
func cloneSlice(s []any) []any {
out := make([]any, len(s))
for i := range s {
out[i] = clone(s[i])
}
return out
}

View File

@@ -1,124 +0,0 @@
package application
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
apischema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
)
func TestApplication(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Application defaulting Suite")
}
var _ = Describe("defaultLikeKubernetes", func() {
var rootSchema *apischema.Structural
BeforeEach(func() {
rootSchema = buildTestSchema()
})
It("applies value-schema defaults to existing map key without merging parent object default", func() {
spec := map[string]any{
"nodeGroups": map[string]any{
"md0": map[string]any{
"minReplicas": 3,
},
},
}
err := defaultLikeKubernetes(&spec, rootSchema)
Expect(err).NotTo(HaveOccurred())
ng := spec["nodeGroups"].(map[string]any)["md0"].(map[string]any)
Expect(ng).To(HaveKeyWithValue("minReplicas", BeNumerically("==", 3)))
Expect(ng).To(HaveKeyWithValue("instanceType", "u1.medium"))
Expect(ng["roles"]).To(ConsistOf("ingress-nginx"))
Expect(ng).NotTo(HaveKey("ephemeralStorage"))
Expect(ng).NotTo(HaveKey("maxReplicas"))
Expect(ng).NotTo(HaveKey("resources"))
})
It("does not create new map keys from parent object default", func() {
spec := map[string]any{
"nodeGroups": map[string]any{},
}
err := defaultLikeKubernetes(&spec, rootSchema)
Expect(err).NotTo(HaveOccurred())
ng := spec["nodeGroups"].(map[string]any)
Expect(ng).NotTo(HaveKey("md0"))
})
})
func buildTestSchema() *apischema.Structural {
instanceType := apischema.Structural{
Generic: apischema.Generic{
Type: "string",
Default: apischema.JSON{Object: "u1.medium"},
},
}
roles := apischema.Structural{
Generic: apischema.Generic{
Type: "array",
Default: apischema.JSON{Object: []any{"ingress-nginx"}},
},
Items: &apischema.Structural{
Generic: apischema.Generic{Type: "string"},
},
}
minReplicas := apischema.Structural{
Generic: apischema.Generic{Type: "integer"},
}
ephemeralStorage := apischema.Structural{
Generic: apischema.Generic{Type: "string"},
}
maxReplicas := apischema.Structural{
Generic: apischema.Generic{Type: "integer"},
}
resources := apischema.Structural{
Generic: apischema.Generic{Type: "object"},
Properties: map[string]apischema.Structural{},
}
nodeGroupsValue := &apischema.Structural{
Generic: apischema.Generic{Type: "object"},
Properties: map[string]apischema.Structural{
"instanceType": instanceType,
"roles": roles,
"minReplicas": minReplicas,
"ephemeralStorage": ephemeralStorage,
"maxReplicas": maxReplicas,
"resources": resources,
},
}
nodeGroups := apischema.Structural{
Generic: apischema.Generic{
Type: "object",
Default: apischema.JSON{Object: map[string]any{
"md0": map[string]any{
"ephemeralStorage": "20Gi",
"maxReplicas": 10,
"minReplicas": 0,
"resources": map[string]any{},
},
}},
},
AdditionalProperties: &apischema.StructuralOrBool{
Structural: nodeGroupsValue,
},
}
return &apischema.Structural{
Generic: apischema.Generic{Type: "object"},
Properties: map[string]apischema.Structural{
"nodeGroups": nodeGroups,
},
}
}

View File

@@ -22,15 +22,9 @@ kubectl get helmreleases.helm.toolkit.fluxcd.io -A \
# JSON Patch
| [
(if $s.chart.spec.version? then
{op:"replace", path:"/spec/chart/spec/version", value:"0.7.0"}
else
{op:"add", path:"/spec/chart/spec/version", value:"0.7.0"}
end),
{ op:"add", path:"/spec/chart/spec/version", value:"0.7.0" },
(if ($v|type) != "object" then {op:"add", path:"/spec/values", value:{}} else empty end),
(if ($v.volume?|type) != "object" then {op:"add", path:"/spec/values/volume", value:{}} else empty end),
(if ($v.volume? | type) != "object" then {op:"add", path:"/spec/values/volume", value:{}} else empty end),
(if $v.size? then {op:"add", path:"/spec/values/volume/size", value:$v.size} else empty end),
(if $v.storageClass? then {op:"add", path:"/spec/values/volume/storageClass", value:$v.storageClass} else empty end),