Compare commits

...

18 Commits

Author SHA1 Message Date
Andrei Kvapil
53fbe7c2ee Release v0.36.1 (#1434)
This PR prepares the release `v0.36.1`.
2025-09-18 05:26:48 +02:00
cozystack-bot
18ff789256 Prepare release v0.36.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-18 02:58:44 +00:00
Andrei Kvapil
3d02fbfba4 [cozystack-api] Update defaulting API schemas (#1433)
## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[cozystack-api] Update defaulting API schemas
```
2025-09-18 04:54:07 +02:00
Andrei Kvapil
8c6fc68367 [cozystack-api] Update defaulting API schemas
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-18 04:49:50 +02:00
Andrei Kvapil
9d2fe2605f [cozystack-api] Implement Kubernetes-like defaulting (#1432)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[]
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Application specs now get recursive, Kubernetes-like defaulting:
missing fields in nested objects and arrays are auto-populated safely
without mutating shared defaults.
- No changes to public APIs; existing manifests remain compatible while
gaining broader defaulting.

- **Tests**
- Added unit tests validating defaulting behavior, per-item defaults,
and non-creation of absent keys.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-18 03:01:39 +02:00
Andrei Kvapil
edb3e92585 [cozystack-api] Implement Kubernetes-like defaulting
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-18 02:32:16 +02:00
Andrei Kvapil
7118232490 Update ADOPTERS.md (#1429)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[]
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added Hidora to the Adopters list, including contact
(@matthieu-robin), date (2025-09-17), and a description highlighting
Hikube’s Swiss-based, multi-datacenter, sovereign cloud capabilities.
Users can reference these details for real-world usage context.
* Updated the table with a spacer row to maintain formatting
consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:59:54 +02:00
Andrei Kvapil
19f81a2d32 [seaweedfs] fix seaweedfs migration (#1430)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[]
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
  - None
- Bug Fixes
- Prevented duplicate or incorrect chart version updates during
migrations.
- Avoided failures when configuration values are missing or not
correctly structured.
- Preserved existing volume settings (size and storage class) during
updates.
- Refactor
- Reorganized migration steps to validate and create parent
configuration before modifying nested fields.
- Made the migration process more defensive and order-aware for smoother
upgrades.
- Chores
- Improved migration scripts for reliability during version and
configuration updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:59:42 +02:00
Andrei Kvapil
b93fe65992 [seaweedfs] fix seaweedfs migration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 22:58:43 +02:00
Andrei Kvapil
541347d321 [dashboard] Fix bitnami dependencies (#1431)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[]
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Chores
- Updated container base images for the dashboard and APIs to maintained
legacy variants to improve build stability and align with security
patching.
- No user-facing changes: functionality, performance, and UI remain
unchanged.
- Runtime versions are consistent with previous releases; deployment
artifacts are equivalent.
- Existing workflows and configurations continue to work as before; no
action required from users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:58:03 +02:00
Andrei Kvapil
1827d29412 [dashboard] Fix bitnami dependencies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 22:34:31 +02:00
Andrei Kvapil
a1a107a90b Release v0.36.0 (#1428)
This PR prepares the release `v0.36.0`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Chores
- Promoted images from v0.36.0-beta.4 to stable v0.36.0 across the
platform (installer, controller, API, dashboard, Kamaji,
kubeovn-webhook, kubeovn-plunger, object storage components, SeaweedFS
sidecar, matchbox, e2e).
- Updated image digests for multiple components to latest builds (nginx
cache, KubeVirt CSI driver, S3 manager, KubeOVN).
- Style
  - Dashboard now displays app version v0.36.0.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 16:45:35 +02:00
Matthieu ROBIN
6cd0a3409e Update ADOPTERS.md
Signed-off-by: Matthieu ROBIN <info@matthieurobin.com>
2025-09-17 14:26:55 +02:00
cozystack-bot
f5c575d12f Prepare release v0.36.0
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-17 11:45:04 +00:00
Andrei Kvapil
d10b3635cc [cozystack-controller] Implement cache for CozystackResourceDefinitions (#1427)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does

This PR introduces shared cache for CozystackResourceDefinitions and
warbs it up before making decidion on restart cozystack-api server.

Reastart logic was also updated to trigger restart only if consistent
hash from the configuration has been changed.

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[cozystack-controller] Implement cache for CozystackResourceDefinitions
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Smarter, hash-based restarts for the API component, triggered only
when configuration truly changes.
- Debounced restart behavior to avoid rapid, repeated restarts during
bursts of updates.

- Performance
- Introduces an internal in-memory configuration cache to speed up
evaluations and reduce API calls.
  - Cache is primed at startup for faster, more responsive operations.

- Bug Fixes
- Eliminates unnecessary restarts when there are no effective config
changes, improving stability and reducing disruption.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 13:40:07 +02:00
Andrei Kvapil
cdf53e89e9 Replace Ancestor tracking webhook with controller
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 12:55:43 +02:00
Andrei Kvapil
37720b9609 Revert "[cozystack-controller] Ancestor tracking webhook" (#1425)
Reverts cozystack/cozystack#1400

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* New Features
  * None
* Refactor
* Removed the lineage mutating admission webhook and its controller
logic; objects are no longer auto-labeled/mutated.
* Deployment now targets the cozy-system namespace and no longer exposes
a webhook port or mounts webhook certs.
* Chores
* Removed Service and cert-manager resources previously used for webhook
TLS; cert-manager is no longer required.
* Tests
  * Removed lineage-related tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 12:49:30 +02:00
Andrei Kvapil
ce522284c4 Revert "[cozystack-controller] Ancestor tracking webhook" 2025-09-17 12:46:00 +02:00
77 changed files with 769 additions and 1282 deletions

View File

@@ -28,7 +28,7 @@ jobs:
- name: Install generate
run: |
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
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
- name: Run pre-commit hooks
run: |

View File

@@ -30,3 +30,5 @@ 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,7 +38,6 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
lcw "github.com/cozystack/cozystack/internal/lineagecontrollerwebhook"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
@@ -215,20 +214,6 @@ func main() {
os.Exit(1)
}
// special one that's both a webhook and a reconciler
lineageControllerWebhook := &lcw.LineageControllerWebhook{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err := lineageControllerWebhook.SetupWithManagerAsController(mgr); err != nil {
setupLog.Error(err, "unable to setup controller", "controller", "LineageController")
os.Exit(1)
}
if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil {
setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

View File

@@ -2,14 +2,25 @@ package controller
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"sort"
"sync"
"time"
"github.com/cozystack/cozystack/internal/shared/crdmem"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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"
@@ -20,85 +31,55 @@ type CozystackResourceDefinitionReconciler struct {
client.Client
Scheme *runtime.Scheme
// Configurable debounce duration
Debounce time.Duration
// Internal state for debouncing
mu sync.Mutex
lastEvent time.Time // Time of last CRUD event on CozystackResourceDefinition
lastHandled time.Time // Last time the Deployment was actually restarted
lastEvent time.Time
lastHandled time.Time
mem *crdmem.Memory
}
// Reconcile handles the logic to restart the target Deployment only once,
// even if multiple events occur close together
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
logger := log.FromContext(ctx)
// Only respond to our target deployment
if req.Namespace != "cozy-system" || req.Name != "cozystack-api" {
crd := &cozyv1alpha1.CozystackResourceDefinition{}
err := r.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err == nil {
if r.mem != nil {
r.mem.Upsert(crd)
}
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return ctrl.Result{}, nil
}
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
// No events received yet — nothing to do
if le.IsZero() {
return ctrl.Result{}, nil
}
// Wait until the debounce duration has passed since the last event
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
// Already handled this event — skip restart
if !lh.Before(le) {
return ctrl.Result{}, nil
}
// Perform the restart by patching the deployment annotation
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"}, deploy); err != nil {
log.Error(err, "Failed to get Deployment cozy-system/cozystack-api")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
patch := client.MergeFrom(deploy.DeepCopy())
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = make(map[string]string)
}
deploy.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
if err := r.Patch(ctx, deploy, patch); err != nil {
log.Error(err, "Failed to patch Deployment annotation")
if err != nil && !apierrors.IsNotFound(err) {
return ctrl.Result{}, err
}
// Mark this event as handled
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
log.Info("Deployment cozy-system/cozystack-api successfully restarted")
if apierrors.IsNotFound(err) && r.mem != nil {
r.mem.Delete(req.Name)
}
if req.Namespace == "cozy-system" && req.Name == "cozystack-api" {
return r.debouncedRestart(ctx, logger)
}
return ctrl.Result{}, nil
}
// SetupWithManager configures how the controller listens to events
func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
}
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
Named("cozystack-restart-controller").
Named("cozystackresource-controller").
For(&cozyv1alpha1.CozystackResourceDefinition{}, builder.WithPredicates()).
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
@@ -115,3 +96,88 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage
).
Complete(r)
}
type crdHashView struct {
Name string `json:"name"`
Spec cozyv1alpha1.CozystackResourceDefinitionSpec `json:"spec"`
}
func (r *CozystackResourceDefinitionReconciler) computeConfigHash() (string, error) {
if r.mem == nil {
return "", nil
}
snapshot := r.mem.Snapshot()
sort.Slice(snapshot, func(i, j int) bool { return snapshot[i].Name < snapshot[j].Name })
views := make([]crdHashView, 0, len(snapshot))
for i := range snapshot {
views = append(views, crdHashView{
Name: snapshot[i].Name,
Spec: snapshot[i].Spec,
})
}
b, err := json.Marshal(views)
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:]), nil
}
func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Context, logger logr.Logger) (ctrl.Result, error) {
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
if le.IsZero() {
return ctrl.Result{}, nil
}
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
if !lh.Before(le) {
return ctrl.Result{}, nil
}
newHash, err := r.computeConfigHash()
if err != nil {
return ctrl.Result{}, err
}
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"}, deploy); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = map[string]string{}
}
oldHash := deploy.Spec.Template.Annotations["cozystack.io/config-hash"]
if oldHash == newHash && oldHash != "" {
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("No changes in CRD config; skipping restart", "hash", newHash)
return ctrl.Result{}, nil
}
patch := client.MergeFrom(deploy.DeepCopy())
deploy.Spec.Template.Annotations["cozystack.io/config-hash"] = newHash
if err := r.Patch(ctx, deploy, patch); err != nil {
return ctrl.Result{}, err
}
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("Updated cozystack-api podTemplate config-hash; rollout triggered",
"old", oldHash, "new", newHash)
return ctrl.Result{}, nil
}

View File

@@ -1,40 +0,0 @@
package lineagecontrollerwebhook
import (
"fmt"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
)
type chartRef struct {
repo string
chart string
}
type appRef struct {
groupVersion string
kind string
prefix string
}
type runtimeConfig struct {
chartAppMap map[chartRef]appRef
}
func (l *LineageControllerWebhook) initConfig() {
l.initOnce.Do(func() {
if l.config.Load() == nil {
l.config.Store(&runtimeConfig{chartAppMap: make(map[chartRef]appRef)})
}
})
}
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg := l.config.Load().(*runtimeConfig).chartAppMap
s := &hr.Spec.Chart.Spec
val, ok := cfg[chartRef{s.SourceRef.Name, s.Chart}]
if !ok {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
return val.groupVersion, val.kind, val.prefix, nil
}

View File

@@ -1,42 +0,0 @@
package lineagecontrollerwebhook
import (
"context"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch
func (c *LineageControllerWebhook) SetupWithManagerAsController(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(c)
}
func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crds := &cozyv1alpha1.CozystackResourceDefinitionList{}
if err := c.List(ctx, crds, &client.ListOptions{Namespace: "cozy-system"}); err != nil {
l.Error(err, "failed reading CozystackResourceDefinitions")
return ctrl.Result{}, err
}
newConfig := make(map[chartRef]appRef)
for _, crd := range crds.Items {
k := chartRef{
crd.Spec.Release.Chart.SourceRef.Name,
crd.Spec.Release.Chart.Name,
}
newRef := appRef{"apps.cozystack.io/v1alpha1", crd.Spec.Application.Kind, crd.Spec.Release.Prefix}
if oldRef, exists := newConfig[k]; exists {
l.Info("duplicate chart mapping detected; ignoring subsequent entry", "key", k, "retained value", oldRef, "ignored value", newRef)
continue
}
newConfig[k] = newRef
}
c.config.Store(&runtimeConfig{newConfig})
return ctrl.Result{}, nil
}

View File

@@ -1,23 +0,0 @@
package lineagecontrollerwebhook
import (
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// +kubebuilder:webhook:path=/mutate-lineage,mutating=true,failurePolicy=Fail,sideEffects=None,groups="",resources=pods,secrets,services,persistentvolumeclaims,verbs=create;update,versions=v1,name=mlineage.cozystack.io,admissionReviewVersions={v1}
type LineageControllerWebhook struct {
client.Client
Scheme *runtime.Scheme
decoder admission.Decoder
dynClient dynamic.Interface
mapper meta.RESTMapper
config atomic.Value
initOnce sync.Once
}

View File

@@ -1,166 +0,0 @@
package lineagecontrollerwebhook
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/cozystack/cozystack/pkg/lineage"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
var (
NoAncestors = fmt.Errorf("no managed apps found in lineage")
AncestryAmbiguous = fmt.Errorf("object ancestry is ambiguous")
)
// SetupWithManager registers the handler with the webhook server.
func (h *LineageControllerWebhook) SetupWithManagerAsWebhook(mgr ctrl.Manager) error {
cfg := rest.CopyConfig(mgr.GetConfig())
var err error
h.dynClient, err = dynamic.NewForConfig(cfg)
if err != nil {
return err
}
discoClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return err
}
cachedDisco := memory.NewMemCacheClient(discoClient)
h.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDisco)
h.initConfig()
// Register HTTP path -> handler.
mgr.GetWebhookServer().Register("/mutate-lineage", &admission.Webhook{Handler: h})
return nil
}
// InjectDecoder lets controller-runtime give us a decoder for AdmissionReview requests.
func (h *LineageControllerWebhook) InjectDecoder(d admission.Decoder) error {
h.decoder = d
return nil
}
// Handle is called for each AdmissionReview that matches the webhook config.
func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
logger := log.FromContext(ctx).WithValues(
"gvk", req.Kind.String(),
"namespace", req.Namespace,
"name", req.Name,
"operation", req.Operation,
)
warn := make(admission.Warnings, 0)
obj := &unstructured.Unstructured{}
if err := h.decodeUnstructured(req, obj); err != nil {
return admission.Errored(400, fmt.Errorf("decode object: %w", err))
}
labels, err := h.computeLabels(ctx, obj)
for {
if err != nil && errors.Is(err, NoAncestors) {
return admission.Allowed("object not managed by app")
}
if err != nil && errors.Is(err, AncestryAmbiguous) {
warn = append(warn, "object ancestry ambiguous, using first ancestor found")
break
}
if err != nil {
return admission.Errored(500, fmt.Errorf("error computing lineage labels: %w", err))
}
if err == nil {
break
}
}
h.applyLabels(obj, labels)
mutated, err := json.Marshal(obj)
if err != nil {
return admission.Errored(500, fmt.Errorf("marshal mutated pod: %w", err))
}
logger.V(1).Info("mutated pod", "namespace", obj.GetNamespace(), "name", obj.GetName())
return admission.PatchResponseFromRaw(req.Object.Raw, mutated).WithWarnings(warn...)
}
func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, error) {
owners := lineage.WalkOwnershipGraph(ctx, h.dynClient, h.mapper, h, o)
if len(owners) == 0 {
return nil, NoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, h.dynClient, h.mapper)
if err != nil {
return nil, err
}
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion())
if err != nil {
// should never happen, we got an APIVersion right from the API
return nil, fmt.Errorf("could not parse APIVersion %s to a group and version: %w", obj.GetAPIVersion(), err)
}
if len(owners) > 1 {
err = AncestryAmbiguous
}
return map[string]string{
// truncate apigroup to first 63 chars
"apps.cozystack.io/application.group": func(s string) string {
if len(s) < 63 {
return s
}
s = s[:63]
for b := s[62]; !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')); s = s[:len(s)-1] {
b = s[len(s)-1]
}
return s
}(gv.Group),
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
}, err
}
func (h *LineageControllerWebhook) applyLabels(o client.Object, labels map[string]string) {
existing := o.GetLabels()
if existing == nil {
existing = make(map[string]string)
}
for k, v := range labels {
existing[k] = v
}
o.SetLabels(existing)
}
func (h *LineageControllerWebhook) decodeUnstructured(req admission.Request, out *unstructured.Unstructured) error {
if h.decoder != nil {
if err := h.decoder.Decode(req, out); err == nil {
return nil
}
if req.Kind.Group != "" || req.Kind.Kind != "" || req.Kind.Version != "" {
out.SetGroupVersionKind(schema.GroupVersionKind{
Group: req.Kind.Group,
Version: req.Kind.Version,
Kind: req.Kind.Kind,
})
if err := h.decoder.Decode(req, out); err == nil {
return nil
}
}
}
if len(req.Object.Raw) == 0 {
return errors.New("empty admission object")
}
return json.Unmarshal(req.Object.Raw, &out.Object)
}

View File

@@ -0,0 +1,99 @@
package crdmem
import (
"context"
"sync"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Memory struct {
mu sync.RWMutex
data map[string]cozyv1alpha1.CozystackResourceDefinition
primed bool
primeOnce sync.Once
}
func New() *Memory {
return &Memory{data: make(map[string]cozyv1alpha1.CozystackResourceDefinition)}
}
var (
global *Memory
globalOnce sync.Once
)
func Global() *Memory {
globalOnce.Do(func() { global = New() })
return global
}
func (m *Memory) Upsert(obj *cozyv1alpha1.CozystackResourceDefinition) {
if obj == nil {
return
}
m.mu.Lock()
m.data[obj.Name] = *obj.DeepCopy()
m.mu.Unlock()
}
func (m *Memory) Delete(name string) {
m.mu.Lock()
delete(m.data, name)
m.mu.Unlock()
}
func (m *Memory) Snapshot() []cozyv1alpha1.CozystackResourceDefinition {
m.mu.RLock()
defer m.mu.RUnlock()
out := make([]cozyv1alpha1.CozystackResourceDefinition, 0, len(m.data))
for _, v := range m.data {
out = append(out, v)
}
return out
}
func (m *Memory) IsPrimed() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.primed
}
type runnable func(context.Context) error
func (r runnable) Start(ctx context.Context) error { return r(ctx) }
func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error {
var errOut error
m.primeOnce.Do(func() {
errOut = mgr.Add(runnable(func(ctx context.Context) error {
if ok := mgr.GetCache().WaitForCacheSync(ctx); !ok {
return nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := mgr.GetClient().List(ctx, &list); err == nil {
for i := range list.Items {
m.Upsert(&list.Items[i])
}
m.mu.Lock()
m.primed = true
m.mu.Unlock()
}
return nil
}))
})
return errOut
}
func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if m.IsPrimed() {
return m.Snapshot(), nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := c.List(ctx, &list); err != nil {
return nil, err
}
return list.Items, nil
}

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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each Clickhouse replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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` | `{}` |
| `clickhouseKeeper` | Clickhouse Keeper configuration | `*object` | `null` |
| `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,16 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"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 * * *"
},
"default": {},
"required": [
"cleanupStrategy",
"enabled",
@@ -71,12 +62,7 @@
"clickhouseKeeper": {
"description": "Clickhouse Keeper configuration",
"type": "object",
"default": {
"enabled": true,
"replicas": 3,
"resourcesPreset": "micro",
"size": "1Gi"
},
"default": {},
"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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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,15 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"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 * * * *"
},
"default": {},
"required": [
"destinationPath",
"enabled",
@@ -64,11 +56,7 @@
"bootstrap": {
"description": "Bootstrap (recovery) configuration",
"type": "object",
"default": {
"enabled": false,
"oldName": "",
"recoveryTime": ""
},
"default": {},
"properties": {
"enabled": {
"description": "Restore database cluster from a backup",
@@ -93,10 +81,7 @@
"quorum": {
"description": "Configuration for the quorum-based synchronous replication",
"type": "object",
"default": {
"maxSyncReplicas": 0,
"minSyncReplicas": 0
},
"default": {},
"required": [
"maxSyncReplicas",
"minSyncReplicas"

View File

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

View File

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

View File

@@ -10,13 +10,7 @@
"kafka": {
"description": "Kafka configuration",
"type": "object",
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": ""
},
"default": {},
"required": [
"replicas",
"resourcesPreset",
@@ -132,13 +126,7 @@
"zookeeper": {
"description": "Zookeeper configuration",
"type": "object",
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "5Gi",
"storageClass": ""
},
"default": {},
"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.0
version: 0.29.1
# 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` | `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` | `""` |
| 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` | `""` |
### Cluster Addons

View File

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

View File

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

View File

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

View File

@@ -5,46 +5,7 @@
"addons": {
"description": "Cluster addons configuration",
"type": "object",
"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": {}
}
},
"default": {},
"required": [
"certManager",
"cilium",
@@ -61,10 +22,7 @@
"certManager": {
"description": "Cert-manager: automatically creates and manages SSL/TLS certificate",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -86,9 +44,7 @@
"cilium": {
"description": "Cilium CNI plugin",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -104,9 +60,7 @@
"coredns": {
"description": "Coredns",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -122,10 +76,7 @@
"fluxcd": {
"description": "Flux CD",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -147,9 +98,7 @@
"gatewayAPI": {
"description": "Gateway API",
"type": "object",
"default": {
"enabled": false
},
"default": {},
"required": [
"enabled"
],
@@ -164,10 +113,7 @@
"gpuOperator": {
"description": "GPU-operator: NVIDIA GPU Operator",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -189,12 +135,7 @@
"ingressNginx": {
"description": "Ingress-NGINX Controller",
"type": "object",
"default": {
"enabled": false,
"exposeMethod": "Proxied",
"hosts": {},
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"exposeMethod",
@@ -234,10 +175,7 @@
"monitoringAgents": {
"description": "MonitoringAgents",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -259,10 +197,7 @@
"velero": {
"description": "Velero",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -284,9 +219,7 @@
"verticalPodAutoscaler": {
"description": "VerticalPodAutoscaler",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -304,27 +237,7 @@
"controlPlane": {
"description": "Control Plane Configuration",
"type": "object",
"default": {
"apiServer": {
"resources": {},
"resourcesPreset": "medium"
},
"controllerManager": {
"resources": {},
"resourcesPreset": "micro"
},
"konnectivity": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"replicas": 2,
"scheduler": {
"resources": {},
"resourcesPreset": "micro"
}
},
"default": {},
"required": [
"apiServer",
"controllerManager",
@@ -336,10 +249,7 @@
"apiServer": {
"description": "Control plane API server configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "medium"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -397,10 +307,7 @@
"controllerManager": {
"description": "Controller Manager configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -458,12 +365,7 @@
"konnectivity": {
"description": "Konnectivity configuration.",
"type": "object",
"default": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"default": {},
"required": [
"server"
],
@@ -471,10 +373,7 @@
"server": {
"description": "Konnectivity server configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -539,10 +438,7 @@
"scheduler": {
"description": "Scheduler configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -609,7 +505,7 @@
"default": {
"md0": {
"ephemeralStorage": "20Gi",
"gpus": {},
"gpus": [],
"instanceType": "u1.medium",
"maxReplicas": 10,
"minReplicas": 0,
@@ -631,6 +527,7 @@
"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": [
{
@@ -645,6 +542,7 @@
"gpus": {
"description": "List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
@@ -660,19 +558,23 @@
},
"instanceType": {
"description": "Virtual machine instance type",
"type": "string"
"type": "string",
"default": "u1.medium"
},
"maxReplicas": {
"description": "Maximum amount of replicas",
"type": "integer"
"type": "integer",
"default": 10
},
"minReplicas": {
"description": "Minimum amount of replicas",
"type": "integer"
"type": "integer",
"default": 0
},
"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]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
## @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
## @field resources.cpu {*quantity} CPU available to each worker node
## @field resources.memory {*quantity} Memory (RAM) available to each worker node
## @field node.gpus {[]gpu} List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)
## @field nodeGroup.gpus {[]gpu default={}} 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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each MariaDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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,16 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"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 * * *"
},
"default": {},
"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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each NATS replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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,10 +5,7 @@
"config": {
"description": "NATS configuration",
"type": "object",
"default": {
"merge": {},
"resolver": {}
},
"default": {},
"properties": {
"merge": {
"description": "Additional configuration to merge into NATS config (see example)",
@@ -32,10 +29,7 @@
"jetstream": {
"description": "Jetstream configuration",
"type": "object",
"default": {
"enabled": true,
"size": "10Gi"
},
"default": {},
"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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each PostgreSQL replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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,15 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"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 * * * *"
},
"default": {},
"properties": {
"destinationPath": {
"description": "Path to store the backup (i.e. s3://bucket/path/to/folder)",
@@ -55,11 +47,7 @@
"bootstrap": {
"description": "Bootstrap configuration",
"type": "object",
"default": {
"enabled": false,
"oldName": "",
"recoveryTime": ""
},
"default": {},
"required": [
"enabled",
"oldName"
@@ -125,11 +113,7 @@
"postgresql": {
"description": "PostgreSQL server configuration",
"type": "object",
"default": {
"parameters": {
"max_connections": 100
}
},
"default": {},
"required": [
"parameters"
],
@@ -137,9 +121,7 @@
"parameters": {
"description": "PostgreSQL server parameters",
"type": "object",
"default": {
"max_connections": 100
},
"default": {},
"required": [
"max_connections"
],
@@ -156,10 +138,7 @@
"quorum": {
"description": "Quorum configuration for synchronous replication",
"type": "object",
"default": {
"maxSyncReplicas": 0,
"minSyncReplicas": 0
},
"default": {},
"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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each RabbitMQ replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each Redis replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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,14 +10,7 @@
"httpAndHttps": {
"description": "HTTP and HTTPS configuration",
"type": "object",
"default": {
"endpoints": {},
"mode": "tcp",
"targetPorts": {
"http": 80,
"https": 443
}
},
"default": {},
"required": [
"mode",
"targetPorts"
@@ -43,10 +36,7 @@
"targetPorts": {
"description": "Target ports configuration",
"type": "object",
"default": {
"http": 80,
"https": 443
},
"default": {},
"required": [
"http",
"https"

View File

@@ -71,7 +71,8 @@ kubernetes 0.26.2 8ddbe32e
kubernetes 0.26.3 c02a3818
kubernetes 0.27.0 6cd5e746
kubernetes 0.28.0 7f477eec
kubernetes 0.29.0 HEAD
kubernetes 0.29.0 87b23161
kubernetes 0.29.1 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` | `{}` |
| `resources` | Resources | `*object` | `null` |
| `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,11 +168,7 @@
"systemDisk": {
"description": "System disk configuration",
"type": "object",
"default": {
"image": "ubuntu",
"storage": "5Gi",
"storageClass": "replicated"
},
"default": {},
"required": [
"image",
"storage"

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` | `{}` |
| `resources` | Resources | `*object` | `null` |
| `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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each VPN server replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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.0-beta.4@sha256:51cb9828af3bdceac289de3b1161625065db22535a961958530e9c751880ee96
image: ghcr.io/cozystack/cozystack/installer:v0.36.1@sha256:1579855349bef729209e8668b96dbb03e0fc80b74bcae2c25f3ed5f3ae6d2f7f

View File

@@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.0-beta.4@sha256:c70ad3321fc14ca831c84bf6e7e6e5409bfe8130703173c277ca51db740c6cb3
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.1@sha256:150efd626321c9389415da5779504be4f10e70beafaeb1b7c162b08b3d50b51f

View File

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

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` | `{}` |
| `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` | `null` |
| `resources.cpu` | The number of CPU cores allocated | `*quantity` | `4` |
| `resources.memory` | The amount of memory allocated | `*quantity` | `1Gi` |

View File

@@ -10,10 +10,7 @@
"resources": {
"description": "Resource configuration for etcd",
"type": "object",
"default": {
"cpu": 4,
"memory": "1Gi"
},
"default": {},
"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` | `{}` |
| `resources` | Explicit CPU and memory configuration for each ingress-nginx replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `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.0
version: 1.13.1

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` | `""` |
| `metricsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `10Gi` |
| `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` | `""` |
| `logsStorages[i].storage` | Persistent Volume size for the storage instance | `string` | `""` |
| `logsStorages[i].storageClassName` | StorageClass used to store the data | `*string` | `null` |
| 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` |
### Alerta configuration

View File

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

View File

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

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.0-beta.4@sha256:24451989b15b6801b33ad355a5507307d0333bf9afd240f1db0aca9c92f6b2ad
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.1@sha256:890c6f38d22fa8cba423d086686bd55c20b3d0c27881cf4b7a7801f1b0685112

View File

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

View File

@@ -56,7 +56,8 @@ monitoring 1.10.1 8c86905b
monitoring 1.11.0 4369b031
monitoring 1.12.0 0e47e1e8
monitoring 1.12.1 c02a3818
monitoring 1.13.0 HEAD
monitoring 1.13.0 87b23161
monitoring 1.13.1 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:b9a401defb90a822087e50e7ab6afd9b4db7e71728030f92c7d320ac46889053
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:899ea667b3e575244d512cade23f30cc93d768b070f9c2bebcb440e443444bdb

View File

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

View File

@@ -1,45 +0,0 @@
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,6 +2,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: cozystack-controller
namespace: cozy-system
labels:
app: cozystack-controller
spec:
@@ -28,15 +29,3 @@ 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

@@ -1,33 +0,0 @@
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

@@ -1,15 +0,0 @@
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.0-beta.4@sha256:f4f8fa8e2f33f66d90b99398025d0da1328b530a622a9b420c993e51e1302992
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.36.1@sha256:6a6144430bdec901b4046840a484f0d9effc570f4f7cef2e5740332b98e3077a
debug: false
disableTelemetry: false
cozystackVersion: "v0.36.0-beta.4"
cozystackVersion: "v0.36.1"

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.0-beta.4",
"appVersion": "v0.36.1",
"authProxyEnabled": {{ .Values.authProxy.enabled }},
"oauthLoginURI": {{ .Values.authProxy.oauthLoginURI | quote }},
"oauthLogoutURI": {{ .Values.authProxy.oauthLogoutURI | quote }},

View File

@@ -1,4 +1,4 @@
FROM bitnami/node:20.15.1 AS build
FROM bitnamilegacy/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 bitnami/nginx:1.25.2
FROM bitnamilegacy/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 bitnami/golang:1.23.4 AS builder
FROM bitnamilegacy/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.0-beta.4
tag: v0.36.1
digest: "sha256:54906b3d2492c8603a347a5938b6db36e5ed5c4149111cae1804ac9110361947"
frontend:
image:
@@ -48,8 +48,8 @@ kubeapps:
image:
registry: ghcr.io/cozystack/cozystack
repository: kubeapps-apis
tag: v0.36.0-beta.4
digest: "sha256:c4b268996c96d23bc11b6d109fb7fb51faf05576ee326097889eca72c851b656"
tag: v0.36.1
digest: "sha256:95cec1059acc1e307f9346bdf570cfc1ca5962bdb803ff08ccd17712d1d8f485"
pluginConfig:
flux:
packages:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,193 +0,0 @@
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

@@ -1,53 +0,0 @@
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)
}
}

View File

@@ -1,49 +0,0 @@
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,7 +45,6 @@ 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"
@@ -89,12 +88,24 @@ 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 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)
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
}
}
}
@@ -1205,28 +1216,3 @@ 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

@@ -0,0 +1,187 @@
/*
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

@@ -0,0 +1,124 @@
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,9 +22,15 @@ kubectl get helmreleases.helm.toolkit.fluxcd.io -A \
# JSON Patch
| [
{ op:"add", path:"/spec/chart/spec/version", value:"0.7.0" },
(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),
(if ($v.volume? | type) != "object" then {op:"add", path:"/spec/values/volume", value:{}} else empty end),
(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.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),