mirror of
https://github.com/cozystack/cozystack.git
synced 2026-03-11 17:38:55 +00:00
Compare commits
18 Commits
lineage-co
...
v0.36.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53fbe7c2ee | ||
|
|
18ff789256 | ||
|
|
3d02fbfba4 | ||
|
|
8c6fc68367 | ||
|
|
9d2fe2605f | ||
|
|
edb3e92585 | ||
|
|
7118232490 | ||
|
|
19f81a2d32 | ||
|
|
b93fe65992 | ||
|
|
541347d321 | ||
|
|
1827d29412 | ||
|
|
a1a107a90b | ||
|
|
6cd0a3409e | ||
|
|
f5c575d12f | ||
|
|
d10b3635cc | ||
|
|
cdf53e89e9 | ||
|
|
37720b9609 | ||
|
|
ce522284c4 |
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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. |
|
||||
|
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
99
internal/shared/crdmem/memory.go
Normal file
99
internal/shared/crdmem/memory.go
Normal 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
|
||||
}
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.0@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.1@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -168,11 +168,7 @@
|
||||
"systemDisk": {
|
||||
"description": "System disk configuration",
|
||||
"type": "object",
|
||||
"default": {
|
||||
"image": "ubuntu",
|
||||
"storage": "5Gi",
|
||||
"storageClass": "replicated"
|
||||
},
|
||||
"default": {},
|
||||
"required": [
|
||||
"image",
|
||||
"storage"
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/matchbox:v0.36.0-beta.4@sha256:764c547a352c9c1d0442e43cdfd0ef50b216bd7f6e5514c777b1d90c4d95da92
|
||||
ghcr.io/cozystack/cozystack/matchbox:v0.36.1@sha256:ecf30f70d9a4b708f68fab52ba3a2ecc0787bb2e79906d76b770bb51f8d6ad6c
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/grafana:1.13.0@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399
|
||||
ghcr.io/cozystack/cozystack/grafana:1.13.1@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:b9a401defb90a822087e50e7ab6afd9b4db7e71728030f92c7d320ac46889053
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:899ea667b3e575244d512cade23f30cc93d768b070f9c2bebcb440e443444bdb
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,4 +64,4 @@ global:
|
||||
images:
|
||||
kubeovn:
|
||||
repository: kubeovn
|
||||
tag: v1.14.5@sha256:8968977ba60e1fb14121984899ddf38f7fe4ea800806a7a30db007110064c84b
|
||||
tag: v1.14.5@sha256:7035b06406493c3385645319a50f2198c6bfb343d2e8c3f0707e769db1e960f7
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
187
pkg/registry/apps/application/rest_defaulting.go
Normal file
187
pkg/registry/apps/application/rest_defaulting.go
Normal 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
|
||||
}
|
||||
124
pkg/registry/apps/application/rest_defaulting_test.go
Normal file
124
pkg/registry/apps/application/rest_defaulting_test.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user