mirror of
https://github.com/cozystack/cozystack.git
synced 2026-03-14 02:48:57 +00:00
Compare commits
85 Commits
feat/cozyc
...
v1.0.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28c9ad9253 | ||
|
|
8c49cf373d | ||
|
|
c0ea9daca5 | ||
|
|
f0134cf6ae | ||
|
|
5e897ef207 | ||
|
|
c10fdc36b8 | ||
|
|
92bf13a9fc | ||
|
|
c055fcbb48 | ||
|
|
81f2546f44 | ||
|
|
3251991014 | ||
|
|
1f0df5fbcd | ||
|
|
d412bd54f2 | ||
|
|
ca0282d3c7 | ||
|
|
4389b60571 | ||
|
|
1eaf32812d | ||
|
|
2658bfabda | ||
|
|
448b4d9c80 | ||
|
|
80a62bd3ee | ||
|
|
ca330b2aca | ||
|
|
352be923ae | ||
|
|
5356a5260a | ||
|
|
64b4be5c78 | ||
|
|
c79545ba04 | ||
|
|
eda0e8ee50 | ||
|
|
f68fc0c921 | ||
|
|
48ce08f584 | ||
|
|
2675ff326a | ||
|
|
51a5073175 | ||
|
|
05d1c02eff | ||
|
|
f06817d4e8 | ||
|
|
0fefaa246f | ||
|
|
7cea11e57b | ||
|
|
31ae2bb826 | ||
|
|
c976378f2f | ||
|
|
edc32eec51 | ||
|
|
e51f05d850 | ||
|
|
dac1a375e2 | ||
|
|
2a956eb0f9 | ||
|
|
6fbe026927 | ||
|
|
4c3a6987c5 | ||
|
|
30c5696541 | ||
|
|
42780f26d2 | ||
|
|
e9e2121153 | ||
|
|
3033e718dd | ||
|
|
aa8a7eae47 | ||
|
|
5a14dc6f54 | ||
|
|
2b59d4fc97 | ||
|
|
ab26d71cc7 | ||
|
|
66a61bd63e | ||
|
|
f887e34206 | ||
|
|
7a107296e5 | ||
|
|
e16d987403 | ||
|
|
c63fcf50b3 | ||
|
|
4417cc35a0 | ||
|
|
78cc4c0955 | ||
|
|
65c6936e95 | ||
|
|
cd3643b8cc | ||
|
|
2024ec3a8b | ||
|
|
f282f19c1b | ||
|
|
7427bbdaa3 | ||
|
|
da89203a32 | ||
|
|
e0dfc8a321 | ||
|
|
9cbd948b08 | ||
|
|
e5f7bc5c53 | ||
|
|
e89ba43c39 | ||
|
|
a20951def3 | ||
|
|
4c73ac54a0 | ||
|
|
cfb5914cdd | ||
|
|
948346ef6d | ||
|
|
da597225d1 | ||
|
|
7871d425dd | ||
|
|
a9adda5e88 | ||
|
|
880b99f3f7 | ||
|
|
c7290f3521 | ||
|
|
2b1b5e8fa9 | ||
|
|
4f2578a32b | ||
|
|
d8f5083c6d | ||
|
|
211e01bd87 | ||
|
|
d8bb3527de | ||
|
|
1fd1da45b9 | ||
|
|
2c82b22c5e | ||
|
|
b61dc7c988 | ||
|
|
473ac87d70 | ||
|
|
9fa311e5ac | ||
|
|
7994976052 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @kvaps @lllamnyp @lexfrei @androndo @IvanHunters
|
||||
* @kvaps @lllamnyp @lexfrei @androndo @IvanHunters @sircthulhu
|
||||
|
||||
15
Makefile
15
Makefile
@@ -58,10 +58,7 @@ manifests:
|
||||
cozypkg:
|
||||
go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg ./cmd/cozypkg
|
||||
|
||||
cozyctl:
|
||||
go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozyctl/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozyctl ./cmd/cozyctl
|
||||
|
||||
assets: assets-talos assets-cozypkg assets-cozyctl
|
||||
assets: assets-talos assets-cozypkg
|
||||
|
||||
assets-talos:
|
||||
make -C packages/core/talos assets
|
||||
@@ -76,16 +73,6 @@ assets-cozypkg-%:
|
||||
cp LICENSE _out/bin/cozypkg-$*/LICENSE
|
||||
tar -C _out/bin/cozypkg-$* -czf _out/assets/cozypkg-$*.tar.gz LICENSE cozypkg$(EXT)
|
||||
|
||||
assets-cozyctl: assets-cozyctl-linux-amd64 assets-cozyctl-linux-arm64 assets-cozyctl-darwin-amd64 assets-cozyctl-darwin-arm64 assets-cozyctl-windows-amd64 assets-cozyctl-windows-arm64
|
||||
(cd _out/assets/ && sha256sum cozyctl-*.tar.gz) > _out/assets/cozyctl-checksums.txt
|
||||
|
||||
assets-cozyctl-%:
|
||||
$(eval EXT := $(if $(filter windows,$(firstword $(subst -, ,$*))),.exe,))
|
||||
mkdir -p _out/assets
|
||||
GOOS=$(firstword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*)) go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozyctl/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozyctl-$*/cozyctl$(EXT) ./cmd/cozyctl
|
||||
cp LICENSE _out/bin/cozyctl-$*/LICENSE
|
||||
tar -C _out/bin/cozyctl-$* -czf _out/assets/cozyctl-$*.tar.gz LICENSE cozyctl$(EXT)
|
||||
|
||||
test:
|
||||
make -C packages/core/testing apply
|
||||
make -C packages/core/testing test
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func buildRestConfig() (*rest.Config, error) {
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
if globalFlags.kubeconfig != "" {
|
||||
rules.ExplicitPath = globalFlags.kubeconfig
|
||||
}
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
if globalFlags.context != "" {
|
||||
overrides.CurrentContext = globalFlags.context
|
||||
}
|
||||
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides).ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func newScheme() *runtime.Scheme {
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
|
||||
return scheme
|
||||
}
|
||||
|
||||
func newClients() (client.Client, dynamic.Interface, error) {
|
||||
config, err := buildRestConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
scheme := newScheme()
|
||||
|
||||
typedClient, err := client.New(config, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
dynClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create dynamic client: %w", err)
|
||||
}
|
||||
|
||||
return typedClient, dynClient, nil
|
||||
}
|
||||
|
||||
func getNamespace() (string, error) {
|
||||
if globalFlags.namespace != "" {
|
||||
return globalFlags.namespace, nil
|
||||
}
|
||||
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
if globalFlags.kubeconfig != "" {
|
||||
rules.ExplicitPath = globalFlags.kubeconfig
|
||||
}
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
if globalFlags.context != "" {
|
||||
overrides.CurrentContext = globalFlags.context
|
||||
}
|
||||
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
|
||||
ns, _, err := clientConfig.Namespace()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to determine namespace: %w", err)
|
||||
}
|
||||
if ns == "" {
|
||||
ns = "default"
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
// getRestConfig is a convenience function when only the rest.Config is needed
|
||||
// (used by buildRestConfig but also available for other callers).
|
||||
func getRestConfig() (*rest.Config, error) {
|
||||
if globalFlags.kubeconfig != "" || globalFlags.context != "" {
|
||||
return buildRestConfig()
|
||||
}
|
||||
config, err := ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get kubeconfig: %w", err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var consoleCmd = &cobra.Command{
|
||||
Use: "console <type> <name>",
|
||||
Short: "Open a serial console to a VirtualMachine",
|
||||
Long: `Open a serial console to a VirtualMachine using virtctl. Only valid for VirtualMachine or VMInstance kinds.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runConsole,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(consoleCmd)
|
||||
}
|
||||
|
||||
func runConsole(cmd *cobra.Command, args []string) error {
|
||||
vmName, ns, err := resolveVMArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
virtctlArgs := []string{"virtctl", "console", vmName, "-n", ns}
|
||||
return execVirtctl(virtctlArgs)
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// AppDefInfo holds resolved information about an ApplicationDefinition.
|
||||
type AppDefInfo struct {
|
||||
Name string // e.g. "postgres"
|
||||
Kind string // e.g. "Postgres"
|
||||
Plural string // e.g. "postgreses"
|
||||
Singular string // e.g. "postgres"
|
||||
Prefix string // e.g. "postgres-"
|
||||
IsModule bool
|
||||
}
|
||||
|
||||
// AppDefRegistry provides fast lookup of ApplicationDefinitions by plural, singular, or kind.
|
||||
type AppDefRegistry struct {
|
||||
byPlural map[string]*AppDefInfo
|
||||
bySingular map[string]*AppDefInfo
|
||||
byKind map[string]*AppDefInfo
|
||||
all []*AppDefInfo
|
||||
}
|
||||
|
||||
// discoverAppDefs lists all ApplicationDefinitions from the cluster and builds a registry.
|
||||
func discoverAppDefs(ctx context.Context, typedClient client.Client) (*AppDefRegistry, error) {
|
||||
var list cozyv1alpha1.ApplicationDefinitionList
|
||||
if err := typedClient.List(ctx, &list); err != nil {
|
||||
return nil, fmt.Errorf("failed to list ApplicationDefinitions: %w", err)
|
||||
}
|
||||
|
||||
reg := &AppDefRegistry{
|
||||
byPlural: make(map[string]*AppDefInfo),
|
||||
bySingular: make(map[string]*AppDefInfo),
|
||||
byKind: make(map[string]*AppDefInfo),
|
||||
}
|
||||
|
||||
for i := range list.Items {
|
||||
ad := &list.Items[i]
|
||||
info := &AppDefInfo{
|
||||
Name: ad.Name,
|
||||
Kind: ad.Spec.Application.Kind,
|
||||
Plural: ad.Spec.Application.Plural,
|
||||
Singular: ad.Spec.Application.Singular,
|
||||
Prefix: ad.Spec.Release.Prefix,
|
||||
IsModule: ad.Spec.Dashboard != nil && ad.Spec.Dashboard.Module,
|
||||
}
|
||||
reg.all = append(reg.all, info)
|
||||
reg.byPlural[strings.ToLower(info.Plural)] = info
|
||||
reg.bySingular[strings.ToLower(info.Singular)] = info
|
||||
reg.byKind[strings.ToLower(info.Kind)] = info
|
||||
}
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// Resolve looks up an AppDefInfo by name (case-insensitive), checking plural, singular, then kind.
|
||||
func (r *AppDefRegistry) Resolve(name string) *AppDefInfo {
|
||||
lower := strings.ToLower(name)
|
||||
if info, ok := r.byPlural[lower]; ok {
|
||||
return info
|
||||
}
|
||||
if info, ok := r.bySingular[lower]; ok {
|
||||
return info
|
||||
}
|
||||
if info, ok := r.byKind[lower]; ok {
|
||||
return info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveModule looks up an AppDefInfo among modules only.
|
||||
func (r *AppDefRegistry) ResolveModule(name string) *AppDefInfo {
|
||||
lower := strings.ToLower(name)
|
||||
for _, info := range r.all {
|
||||
if !info.IsModule {
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(info.Plural) == lower ||
|
||||
strings.ToLower(info.Singular) == lower ||
|
||||
strings.ToLower(info.Kind) == lower {
|
||||
return info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// All returns all discovered AppDefInfo entries.
|
||||
func (r *AppDefRegistry) All() []*AppDefInfo {
|
||||
return r.all
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1"
|
||||
corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var getCmdFlags struct {
|
||||
target string
|
||||
}
|
||||
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get <type> [name]",
|
||||
Short: "Display one or many resources",
|
||||
Long: `Display one or many resources.
|
||||
|
||||
Built-in types:
|
||||
ns, namespaces Tenant namespaces (cluster-scoped)
|
||||
modules Tenant modules
|
||||
pvc, pvcs PersistentVolumeClaims
|
||||
|
||||
Sub-resource types (use -t to filter by parent application):
|
||||
secrets Secrets
|
||||
services, svc Services
|
||||
ingresses, ing Ingresses
|
||||
workloads WorkloadMonitors
|
||||
|
||||
Application types are discovered dynamically from ApplicationDefinitions.
|
||||
Use -t type/name to filter sub-resources by a specific application.`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
RunE: runGet,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(getCmd)
|
||||
getCmd.Flags().StringVarP(&getCmdFlags.target, "target", "t", "", "Filter sub-resources by application type/name")
|
||||
}
|
||||
|
||||
func runGet(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
resourceType := args[0]
|
||||
var resourceName string
|
||||
if len(args) > 1 {
|
||||
resourceName = args[1]
|
||||
}
|
||||
|
||||
switch strings.ToLower(resourceType) {
|
||||
case "ns", "namespace", "namespaces":
|
||||
return getNamespaces(ctx, resourceName)
|
||||
case "module", "modules":
|
||||
return getModules(ctx, resourceName)
|
||||
case "pvc", "pvcs", "persistentvolumeclaim", "persistentvolumeclaims":
|
||||
return getPVCs(ctx, resourceName)
|
||||
case "secret", "secrets":
|
||||
return getSubResources(ctx, "secrets", resourceName)
|
||||
case "service", "services", "svc":
|
||||
return getSubResources(ctx, "services", resourceName)
|
||||
case "ingress", "ingresses", "ing":
|
||||
return getSubResources(ctx, "ingresses", resourceName)
|
||||
case "workload", "workloads":
|
||||
return getSubResources(ctx, "workloads", resourceName)
|
||||
default:
|
||||
return getApplications(ctx, resourceType, resourceName)
|
||||
}
|
||||
}
|
||||
|
||||
func getNamespaces(ctx context.Context, name string) error {
|
||||
_, dynClient, err := newClients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}
|
||||
|
||||
if name != "" {
|
||||
item, err := dynClient.Resource(gvr).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get namespace %q: %w", name, err)
|
||||
}
|
||||
printNamespaces([]unstructured.Unstructured{*item})
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := dynClient.Resource(gvr).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list namespaces: %w", err)
|
||||
}
|
||||
if len(list.Items) == 0 {
|
||||
printNoResources(os.Stderr, "namespaces")
|
||||
return nil
|
||||
}
|
||||
printNamespaces(list.Items)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getModules(ctx context.Context, name string) error {
|
||||
_, dynClient, err := newClients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns, err := getNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantmodules"}
|
||||
|
||||
if name != "" {
|
||||
item, err := dynClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get module %q: %w", name, err)
|
||||
}
|
||||
printModules([]unstructured.Unstructured{*item})
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := dynClient.Resource(gvr).Namespace(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list modules: %w", err)
|
||||
}
|
||||
if len(list.Items) == 0 {
|
||||
printNoResources(os.Stderr, "modules")
|
||||
return nil
|
||||
}
|
||||
printModules(list.Items)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPVCs(ctx context.Context, name string) error {
|
||||
_, dynClient, err := newClients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns, err := getNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"}
|
||||
|
||||
if name != "" {
|
||||
item, err := dynClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get PVC %q: %w", name, err)
|
||||
}
|
||||
printPVCs([]unstructured.Unstructured{*item})
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := dynClient.Resource(gvr).Namespace(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list PVCs: %w", err)
|
||||
}
|
||||
if len(list.Items) == 0 {
|
||||
printNoResources(os.Stderr, "PVCs")
|
||||
return nil
|
||||
}
|
||||
printPVCs(list.Items)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSubResources(ctx context.Context, subType string, name string) error {
|
||||
typedClient, dynClient, err := newClients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns, err := getNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labelSelector, err := buildSubResourceSelector(ctx, typedClient, getCmdFlags.target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch subType {
|
||||
case "secrets":
|
||||
return getFilteredSecrets(ctx, dynClient, ns, name, labelSelector)
|
||||
case "services":
|
||||
return getFilteredServices(ctx, dynClient, ns, name, labelSelector)
|
||||
case "ingresses":
|
||||
return getFilteredIngresses(ctx, dynClient, ns, name, labelSelector)
|
||||
case "workloads":
|
||||
return getFilteredWorkloads(ctx, dynClient, ns, name, labelSelector)
|
||||
default:
|
||||
return fmt.Errorf("unknown sub-resource type: %s", subType)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSubResourceSelector(ctx context.Context, typedClient client.Client, target string) (string, error) {
|
||||
var selectors []string
|
||||
|
||||
if target == "" {
|
||||
selectors = append(selectors, corev1alpha1.TenantResourceLabelKey+"="+corev1alpha1.TenantResourceLabelValue)
|
||||
return strings.Join(selectors, ","), nil
|
||||
}
|
||||
|
||||
parts := strings.SplitN(target, "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", fmt.Errorf("invalid target format %q, expected type/name", target)
|
||||
}
|
||||
targetType, targetName := parts[0], parts[1]
|
||||
|
||||
// Discover ApplicationDefinitions to resolve the target type
|
||||
registry, err := discoverAppDefs(ctx, typedClient)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check if this is a module reference
|
||||
if strings.ToLower(targetType) == "module" {
|
||||
info := registry.ResolveModule(targetName)
|
||||
if info == nil {
|
||||
return "", fmt.Errorf("unknown module %q", targetName)
|
||||
}
|
||||
selectors = append(selectors,
|
||||
appsv1alpha1.ApplicationKindLabel+"="+info.Kind,
|
||||
appsv1alpha1.ApplicationNameLabel+"="+targetName,
|
||||
corev1alpha1.TenantResourceLabelKey+"="+corev1alpha1.TenantResourceLabelValue,
|
||||
)
|
||||
return strings.Join(selectors, ","), nil
|
||||
}
|
||||
|
||||
info := registry.Resolve(targetType)
|
||||
if info == nil {
|
||||
return "", fmt.Errorf("unknown application type %q", targetType)
|
||||
}
|
||||
|
||||
selectors = append(selectors,
|
||||
appsv1alpha1.ApplicationKindLabel+"="+info.Kind,
|
||||
appsv1alpha1.ApplicationNameLabel+"="+targetName,
|
||||
corev1alpha1.TenantResourceLabelKey+"="+corev1alpha1.TenantResourceLabelValue,
|
||||
)
|
||||
return strings.Join(selectors, ","), nil
|
||||
}
|
||||
|
||||
func getFilteredSecrets(ctx context.Context, dynClient dynamic.Interface, ns, name, labelSelector string) error {
|
||||
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}
|
||||
return getFilteredResources(ctx, dynClient, gvr, ns, name, labelSelector, "secrets", printSecrets)
|
||||
}
|
||||
|
||||
func getFilteredServices(ctx context.Context, dynClient dynamic.Interface, ns, name, labelSelector string) error {
|
||||
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
|
||||
return getFilteredResources(ctx, dynClient, gvr, ns, name, labelSelector, "services", printServices)
|
||||
}
|
||||
|
||||
func getFilteredIngresses(ctx context.Context, dynClient dynamic.Interface, ns, name, labelSelector string) error {
|
||||
gvr := schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}
|
||||
return getFilteredResources(ctx, dynClient, gvr, ns, name, labelSelector, "ingresses", printIngresses)
|
||||
}
|
||||
|
||||
func getFilteredWorkloads(ctx context.Context, dynClient dynamic.Interface, ns, name, labelSelector string) error {
|
||||
gvr := schema.GroupVersionResource{Group: "cozystack.io", Version: "v1alpha1", Resource: "workloadmonitors"}
|
||||
return getFilteredResources(ctx, dynClient, gvr, ns, name, labelSelector, "workloads", printWorkloads)
|
||||
}
|
||||
|
||||
func getFilteredResources(
|
||||
ctx context.Context,
|
||||
dynClient dynamic.Interface,
|
||||
gvr schema.GroupVersionResource,
|
||||
ns, name, labelSelector string,
|
||||
typeName string,
|
||||
printer func([]unstructured.Unstructured),
|
||||
) error {
|
||||
if name != "" {
|
||||
item, err := dynClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %s %q: %w", typeName, name, err)
|
||||
}
|
||||
printer([]unstructured.Unstructured{*item})
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := dynClient.Resource(gvr).Namespace(ns).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: labelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list %s: %w", typeName, err)
|
||||
}
|
||||
if len(list.Items) == 0 {
|
||||
printNoResources(os.Stderr, typeName)
|
||||
return nil
|
||||
}
|
||||
printer(list.Items)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getApplications(ctx context.Context, resourceType, name string) error {
|
||||
typedClient, dynClient, err := newClients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns, err := getNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry, err := discoverAppDefs(ctx, typedClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := registry.Resolve(resourceType)
|
||||
if info == nil {
|
||||
return fmt.Errorf("unknown resource type %q\nUse 'cozyctl get --help' for available types", resourceType)
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{Group: "apps.cozystack.io", Version: "v1alpha1", Resource: info.Plural}
|
||||
|
||||
if name != "" {
|
||||
item, err := dynClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %s %q: %w", info.Singular, name, err)
|
||||
}
|
||||
printApplications([]unstructured.Unstructured{*item})
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := dynClient.Resource(gvr).Namespace(ns).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list %s: %w", info.Plural, err)
|
||||
}
|
||||
if len(list.Items) == 0 {
|
||||
printNoResources(os.Stderr, info.Plural)
|
||||
return nil
|
||||
}
|
||||
printApplications(list.Items)
|
||||
return nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate <type> <name>",
|
||||
Short: "Live-migrate a VirtualMachine to another node",
|
||||
Long: `Live-migrate a VirtualMachine to another node using virtctl. Only valid for VirtualMachine or VMInstance kinds.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runMigrate,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
||||
func runMigrate(cmd *cobra.Command, args []string) error {
|
||||
vmName, ns, err := resolveVMArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
virtctlArgs := []string{"virtctl", "migrate", vmName, "-n", ns}
|
||||
return execVirtctl(virtctlArgs)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var portForwardCmd = &cobra.Command{
|
||||
Use: "port-forward <type/name> [ports...]",
|
||||
Short: "Forward ports to a VirtualMachineInstance",
|
||||
Long: `Forward ports to a VirtualMachineInstance using virtctl. Only valid for VirtualMachine or VMInstance kinds.`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: runPortForward,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(portForwardCmd)
|
||||
}
|
||||
|
||||
func runPortForward(cmd *cobra.Command, args []string) error {
|
||||
vmName, ns, err := resolveVMArgs(args[:1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ports := args[1:]
|
||||
if len(ports) == 0 {
|
||||
return fmt.Errorf("at least one port is required")
|
||||
}
|
||||
|
||||
virtctlArgs := []string{"virtctl", "port-forward", "vmi/" + vmName, "-n", ns}
|
||||
virtctlArgs = append(virtctlArgs, ports...)
|
||||
return execVirtctl(virtctlArgs)
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func newTabWriter() *tabwriter.Writer {
|
||||
return tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||
}
|
||||
|
||||
func printApplications(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tVERSION\tREADY\tSTATUS")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
version, _, _ := unstructured.NestedString(item.Object, "appVersion")
|
||||
ready, status := extractCondition(item)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, version, ready, truncate(status, 48))
|
||||
}
|
||||
}
|
||||
|
||||
func printNamespaces(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME")
|
||||
for _, item := range items {
|
||||
fmt.Fprintln(w, item.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func printModules(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tVERSION\tREADY\tSTATUS")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
version, _, _ := unstructured.NestedString(item.Object, "appVersion")
|
||||
ready, status := extractCondition(item)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, version, ready, truncate(status, 48))
|
||||
}
|
||||
}
|
||||
|
||||
func printPVCs(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tSTATUS\tVOLUME\tCAPACITY\tSTORAGECLASS")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
phase, _, _ := unstructured.NestedString(item.Object, "status", "phase")
|
||||
volume, _, _ := unstructured.NestedString(item.Object, "spec", "volumeName")
|
||||
capacity := ""
|
||||
if cap, ok, _ := unstructured.NestedStringMap(item.Object, "status", "capacity"); ok {
|
||||
capacity = cap["storage"]
|
||||
}
|
||||
sc, _, _ := unstructured.NestedString(item.Object, "spec", "storageClassName")
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", name, phase, volume, capacity, sc)
|
||||
}
|
||||
}
|
||||
|
||||
func printSecrets(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tTYPE\tDATA")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
secretType, _, _ := unstructured.NestedString(item.Object, "type")
|
||||
data, _, _ := unstructured.NestedMap(item.Object, "data")
|
||||
fmt.Fprintf(w, "%s\t%s\t%d\n", name, secretType, len(data))
|
||||
}
|
||||
}
|
||||
|
||||
func printServices(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tTYPE\tCLUSTER-IP\tEXTERNAL-IP\tPORTS")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
svcType, _, _ := unstructured.NestedString(item.Object, "spec", "type")
|
||||
clusterIP, _, _ := unstructured.NestedString(item.Object, "spec", "clusterIP")
|
||||
|
||||
externalIP := "<none>"
|
||||
if lbIngress, ok, _ := unstructured.NestedSlice(item.Object, "status", "loadBalancer", "ingress"); ok && len(lbIngress) > 0 {
|
||||
var ips []string
|
||||
for _, ingress := range lbIngress {
|
||||
if m, ok := ingress.(map[string]interface{}); ok {
|
||||
if ip, ok := m["ip"].(string); ok {
|
||||
ips = append(ips, ip)
|
||||
} else if hostname, ok := m["hostname"].(string); ok {
|
||||
ips = append(ips, hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
externalIP = strings.Join(ips, ",")
|
||||
}
|
||||
}
|
||||
|
||||
ports := formatPorts(item)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", name, svcType, clusterIP, externalIP, ports)
|
||||
}
|
||||
}
|
||||
|
||||
func printIngresses(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tCLASS\tHOSTS\tADDRESS")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
class, _, _ := unstructured.NestedString(item.Object, "spec", "ingressClassName")
|
||||
|
||||
var hosts []string
|
||||
if rules, ok, _ := unstructured.NestedSlice(item.Object, "spec", "rules"); ok {
|
||||
for _, rule := range rules {
|
||||
if m, ok := rule.(map[string]interface{}); ok {
|
||||
if host, ok := m["host"].(string); ok {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hostsStr := "<none>"
|
||||
if len(hosts) > 0 {
|
||||
hostsStr = strings.Join(hosts, ",")
|
||||
}
|
||||
|
||||
address := ""
|
||||
if lbIngress, ok, _ := unstructured.NestedSlice(item.Object, "status", "loadBalancer", "ingress"); ok && len(lbIngress) > 0 {
|
||||
var addrs []string
|
||||
for _, ingress := range lbIngress {
|
||||
if m, ok := ingress.(map[string]interface{}); ok {
|
||||
if ip, ok := m["ip"].(string); ok {
|
||||
addrs = append(addrs, ip)
|
||||
} else if hostname, ok := m["hostname"].(string); ok {
|
||||
addrs = append(addrs, hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
address = strings.Join(addrs, ",")
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, class, hostsStr, address)
|
||||
}
|
||||
}
|
||||
|
||||
func printWorkloads(items []unstructured.Unstructured) {
|
||||
w := newTabWriter()
|
||||
defer w.Flush()
|
||||
|
||||
fmt.Fprintln(w, "NAME\tKIND\tTYPE\tVERSION\tAVAILABLE\tOBSERVED\tOPERATIONAL")
|
||||
for _, item := range items {
|
||||
name := item.GetName()
|
||||
kind, _, _ := unstructured.NestedString(item.Object, "spec", "kind")
|
||||
wType, _, _ := unstructured.NestedString(item.Object, "spec", "type")
|
||||
version, _, _ := unstructured.NestedString(item.Object, "spec", "version")
|
||||
available, _, _ := unstructured.NestedInt64(item.Object, "status", "availableReplicas")
|
||||
observed, _, _ := unstructured.NestedInt64(item.Object, "status", "observedReplicas")
|
||||
operational, ok, _ := unstructured.NestedBool(item.Object, "status", "operational")
|
||||
opStr := ""
|
||||
if ok {
|
||||
opStr = fmt.Sprintf("%t", operational)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%d\t%s\n", name, kind, wType, version, available, observed, opStr)
|
||||
}
|
||||
}
|
||||
|
||||
func printNoResources(w io.Writer, resourceType string) {
|
||||
fmt.Fprintf(w, "No %s found\n", resourceType)
|
||||
}
|
||||
|
||||
func extractCondition(item unstructured.Unstructured) (string, string) {
|
||||
conditions, ok, _ := unstructured.NestedSlice(item.Object, "status", "conditions")
|
||||
if !ok {
|
||||
return "Unknown", ""
|
||||
}
|
||||
for _, c := range conditions {
|
||||
cond, ok := c.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if cond["type"] == "Ready" {
|
||||
ready, _ := cond["status"].(string)
|
||||
message, _ := cond["message"].(string)
|
||||
return ready, message
|
||||
}
|
||||
}
|
||||
return "Unknown", ""
|
||||
}
|
||||
|
||||
func truncate(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen-3] + "..."
|
||||
}
|
||||
|
||||
func formatPorts(item unstructured.Unstructured) string {
|
||||
ports, ok, _ := unstructured.NestedSlice(item.Object, "spec", "ports")
|
||||
if !ok || len(ports) == 0 {
|
||||
return "<none>"
|
||||
}
|
||||
var parts []string
|
||||
for _, p := range ports {
|
||||
port, ok := p.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
portNum, _, _ := unstructured.NestedInt64(port, "port")
|
||||
protocol, _, _ := unstructured.NestedString(port, "protocol")
|
||||
if protocol == "" {
|
||||
protocol = "TCP"
|
||||
}
|
||||
nodePort, _, _ := unstructured.NestedInt64(port, "nodePort")
|
||||
if nodePort > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d:%d/%s", portNum, nodePort, protocol))
|
||||
} else {
|
||||
parts = append(parts, fmt.Sprintf("%d/%s", portNum, protocol))
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Version is set at build time via -ldflags.
|
||||
var Version = "dev"
|
||||
|
||||
var globalFlags struct {
|
||||
kubeconfig string
|
||||
context string
|
||||
namespace string
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "cozyctl",
|
||||
Short: "A CLI for managing Cozystack applications",
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
func Execute() error {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Version = Version
|
||||
rootCmd.PersistentFlags().StringVar(&globalFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file")
|
||||
rootCmd.PersistentFlags().StringVar(&globalFlags.context, "context", "", "Kubernetes context to use")
|
||||
rootCmd.PersistentFlags().StringVarP(&globalFlags.namespace, "namespace", "n", "", "Kubernetes namespace")
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// vmKindPrefix maps application Kind to the release prefix used by KubeVirt VMs.
|
||||
func vmKindPrefix(kind string) (string, bool) {
|
||||
switch kind {
|
||||
case "VirtualMachine":
|
||||
return "virtual-machine", true
|
||||
case "VMInstance":
|
||||
return "vm-instance", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// resolveVMArgs takes CLI args (type, name or type/name), resolves the application type
|
||||
// via discovery, validates it's a VM kind, and returns the full VM name and namespace.
|
||||
func resolveVMArgs(args []string) (string, string, error) {
|
||||
var resourceType, resourceName string
|
||||
|
||||
if len(args) == 1 {
|
||||
// type/name format
|
||||
parts := strings.SplitN(args[0], "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", fmt.Errorf("expected type/name format, got %q", args[0])
|
||||
}
|
||||
resourceType, resourceName = parts[0], parts[1]
|
||||
} else {
|
||||
resourceType = args[0]
|
||||
resourceName = args[1]
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
typedClient, _, err := newClients()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
registry, err := discoverAppDefs(ctx, typedClient)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
info := registry.Resolve(resourceType)
|
||||
if info == nil {
|
||||
return "", "", fmt.Errorf("unknown application type %q", resourceType)
|
||||
}
|
||||
|
||||
prefix, ok := vmKindPrefix(info.Kind)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("resource type %q (Kind=%s) is not a VirtualMachine or VMInstance", resourceType, info.Kind)
|
||||
}
|
||||
|
||||
ns, err := getNamespace()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
vmName := prefix + "-" + resourceName
|
||||
return vmName, ns, nil
|
||||
}
|
||||
|
||||
// execVirtctl replaces the current process with virtctl.
|
||||
func execVirtctl(args []string) error {
|
||||
virtctlPath, err := exec.LookPath("virtctl")
|
||||
if err != nil {
|
||||
return fmt.Errorf("virtctl not found in PATH: %w", err)
|
||||
}
|
||||
|
||||
// Append kubeconfig/context flags if set
|
||||
if globalFlags.kubeconfig != "" {
|
||||
args = append(args, "--kubeconfig", globalFlags.kubeconfig)
|
||||
}
|
||||
if globalFlags.context != "" {
|
||||
args = append(args, "--context", globalFlags.context)
|
||||
}
|
||||
|
||||
if err := syscall.Exec(virtctlPath, args, os.Environ()); err != nil {
|
||||
return fmt.Errorf("failed to exec virtctl: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var vncCmd = &cobra.Command{
|
||||
Use: "vnc <type> <name>",
|
||||
Short: "Open a VNC connection to a VirtualMachine",
|
||||
Long: `Open a VNC connection to a VirtualMachine using virtctl. Only valid for VirtualMachine or VMInstance kinds.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: runVNC,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(vncCmd)
|
||||
}
|
||||
|
||||
func runVNC(cmd *cobra.Command, args []string) error {
|
||||
vmName, ns, err := resolveVMArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
virtctlArgs := []string{"virtctl", "vnc", vmName, "-n", ns}
|
||||
return execVirtctl(virtctlArgs)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/cozystack/cozystack/cmd/cozyctl/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
15
docs/changelogs/v0.40.5.md
Normal file
15
docs/changelogs/v0.40.5.md
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.40.5
|
||||
-->
|
||||
|
||||
## Improvements
|
||||
|
||||
* **[dashboard] Improve dashboard session params**: Improved session parameter handling in the dashboard for better user experience and more reliable session management ([**@lllamnyp**](https://github.com/lllamnyp) in #1913, #1919).
|
||||
|
||||
## Dependencies
|
||||
|
||||
* **Update cozyhr to v1.6.1**: Updated cozyhr to v1.6.1, which fixes a critical bug causing helm-controller v0.37.0+ to unexpectedly uninstall HelmReleases after cozyhr apply by correcting history snapshot fields for helm-controller compatibility ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr#10).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.40.4...v0.40.5](https://github.com/cozystack/cozystack/compare/v0.40.4...v0.40.5)
|
||||
11
docs/changelogs/v0.40.6.md
Normal file
11
docs/changelogs/v0.40.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.40.6
|
||||
-->
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[kubernetes] Fix manifests for kubernetes deployment**: Fixed incorrect manifests that prevented proper Kubernetes deployment, restoring correct application behavior ([**@IvanHunters**](https://github.com/IvanHunters) in #1943, #1944).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.40.5...v0.40.6](https://github.com/cozystack/cozystack/compare/v0.40.5...v0.40.6)
|
||||
11
docs/changelogs/v0.40.7.md
Normal file
11
docs/changelogs/v0.40.7.md
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.40.7
|
||||
-->
|
||||
|
||||
## Security
|
||||
|
||||
* **[dashboard] Verify JWT token**: Added JWT token verification to the dashboard, ensuring that authentication tokens are properly validated before granting access. This prevents unauthorized access through forged or expired tokens ([**@lllamnyp**](https://github.com/lllamnyp) in #1980, #1984).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.40.6...v0.40.7](https://github.com/cozystack/cozystack/compare/v0.40.6...v0.40.7)
|
||||
11
docs/changelogs/v0.41.4.md
Normal file
11
docs/changelogs/v0.41.4.md
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.4
|
||||
-->
|
||||
|
||||
## Dependencies
|
||||
|
||||
* **Update cozyhr to v1.6.1**: Updated cozyhr to v1.6.1, which fixes a critical bug causing helm-controller v0.37.0+ to unexpectedly uninstall HelmReleases after cozyhr apply by correcting history snapshot fields for helm-controller compatibility ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr#10).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.3...v0.41.4](https://github.com/cozystack/cozystack/compare/v0.41.3...v0.41.4)
|
||||
21
docs/changelogs/v0.41.5.md
Normal file
21
docs/changelogs/v0.41.5.md
Normal file
@@ -0,0 +1,21 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.5
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[dashboard] Add "Edit" button to all resources**: Added an "Edit" button across all resource views in the dashboard, allowing users to modify resource configurations directly from the UI ([**@sircthulhu**](https://github.com/sircthulhu) in #1928, #1931).
|
||||
|
||||
* **[dashboard] Add resource quota usage to tenant details page**: Added resource quota usage display to the tenant details page, giving administrators visibility into how much of allocated resources each tenant is consuming ([**@sircthulhu**](https://github.com/sircthulhu) in #1929, #1932).
|
||||
|
||||
* **[branding] Separate values for keycloak**: Separated Keycloak branding values into dedicated configuration, allowing more granular customization of Keycloak appearance without affecting other branding settings ([**@nbykov0**](https://github.com/nbykov0) in #1946).
|
||||
|
||||
* **Add instance profile label to workload monitor**: Added instance profile metadata labels to the workload monitor, enabling better resource tracking and monitoring by instance profile type ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1954, #1957).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[kubernetes] Fix manifests for kubernetes deployment**: Fixed incorrect manifests that prevented proper Kubernetes deployment, restoring correct application behavior ([**@IvanHunters**](https://github.com/IvanHunters) in #1943, #1945).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.4...v0.41.5](https://github.com/cozystack/cozystack/compare/v0.41.4...v0.41.5)
|
||||
17
docs/changelogs/v0.41.6.md
Normal file
17
docs/changelogs/v0.41.6.md
Normal file
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.6
|
||||
-->
|
||||
|
||||
## Improvements
|
||||
|
||||
* **[vm] Allow changing field external after creation**: Users can now modify the external network field on virtual machines after initial creation, providing more flexibility in VM networking configuration without requiring recreation ([**@sircthulhu**](https://github.com/sircthulhu) in #1956, #1962).
|
||||
|
||||
* **[branding] Separate values for keycloak**: Separated Keycloak branding values into dedicated configuration for more granular customization of Keycloak appearance ([**@nbykov0**](https://github.com/nbykov0) in #1947, #1963).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[kubernetes] Fix coredns serviceaccount to match kubernetes bootstrap RBAC**: Configured the CoreDNS chart to create a `kube-dns` ServiceAccount matching the Kubernetes bootstrap ClusterRoleBinding, fixing RBAC errors (`Failed to watch`) when CoreDNS pods restart ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1958, #1978).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.5...v0.41.6](https://github.com/cozystack/cozystack/compare/v0.41.5...v0.41.6)
|
||||
15
docs/changelogs/v0.41.7.md
Normal file
15
docs/changelogs/v0.41.7.md
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.7
|
||||
-->
|
||||
|
||||
## Security
|
||||
|
||||
* **[dashboard] Verify JWT token**: Added JWT token verification to the dashboard, ensuring that authentication tokens are properly validated before granting access. This prevents unauthorized access through forged or expired tokens ([**@lllamnyp**](https://github.com/lllamnyp) in #1980, #1983).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[postgres-operator] Correct PromQL syntax in CNPGClusterOffline alert**: Fixed incorrect PromQL syntax in the `CNPGClusterOffline` alert rule for CloudNativePG, ensuring the alert fires correctly when all instances of a PostgreSQL cluster are offline ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1981, #1989).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.6...v0.41.7](https://github.com/cozystack/cozystack/compare/v0.41.6...v0.41.7)
|
||||
17
docs/changelogs/v0.41.8.md
Normal file
17
docs/changelogs/v0.41.8.md
Normal file
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.8
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[kubernetes] Auto-enable Gateway API support in cert-manager**: cert-manager now automatically enables `enableGatewayAPI` when the Gateway API addon is active in the Kubernetes application. Users no longer need to manually configure this setting, and the option can still be overridden via `valuesOverride` ([**@kvaps**](https://github.com/kvaps) in #1997, #2012).
|
||||
|
||||
* **[vm] Allow switching between instancetype and custom resources**: Users can now switch virtual machines between instancetype-based and custom resource configurations after creation. The upgrade hook atomically patches VM resources, providing more flexibility in adjusting VM sizing without recreation ([**@sircthulhu**](https://github.com/sircthulhu) in #2008, #2013).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[dashboard] Add startupProbe to prevent container restarts on slow hardware**: Added `startupProbe` to both `bff` and `web` containers in the dashboard deployment. On slow hardware, kubelet was killing containers because the `livenessProbe` only allowed ~33 seconds for startup. The `startupProbe` gives containers up to 60 seconds to start before `livenessProbe` kicks in ([**@kvaps**](https://github.com/kvaps) in #1996, #2014).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.7...v0.41.8](https://github.com/cozystack/cozystack/compare/v0.41.7...v0.41.8)
|
||||
15
docs/changelogs/v0.41.9.md
Normal file
15
docs/changelogs/v0.41.9.md
Normal file
@@ -0,0 +1,15 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.41.9
|
||||
-->
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[cozystack-basics] Deny resourcequotas deletion for tenant admin**: Prevented tenant administrators from deleting resource quotas, ensuring that resource limits set by platform administrators cannot be bypassed by tenant-level users ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2076).
|
||||
|
||||
## Dependencies
|
||||
|
||||
* **Update Kube-OVN to v1.15.3**: Updated Kube-OVN CNI to v1.15.3 with latest bug fixes and improvements ([**@kvaps**](https://github.com/kvaps)).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.41.8...v0.41.9](https://github.com/cozystack/cozystack/compare/v0.41.8...v0.41.9)
|
||||
65
docs/changelogs/v1.0.0-rc.1.md
Normal file
65
docs/changelogs/v1.0.0-rc.1.md
Normal file
@@ -0,0 +1,65 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-rc.1
|
||||
-->
|
||||
|
||||
> **⚠️ Release Candidate Warning**: This is a release candidate intended for final validation before the stable v1.0.0 release. Breaking changes are not expected at this stage, but please test thoroughly before deploying to production.
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[harbor] Add managed Harbor container registry**: Added Harbor v2.14.2 as a managed tenant-level container registry service. The application uses CloudNativePG for PostgreSQL, the Redis operator for caching, and S3 via COSI BucketClaim (from SeaweedFS) for registry image storage. Auto-generated admin credentials are persisted across upgrades, TLS is handled by cert-manager, and Trivy vulnerability scanner is included. Users can now deploy a fully managed, production-ready OCI container registry within their tenant ([**@lexfrei**](https://github.com/lexfrei) in #2058).
|
||||
|
||||
* **[kubernetes] Update supported Kubernetes versions to v1.30–v1.35**: Updated the tenant Kubernetes version matrix to v1.30, v1.31, v1.32, v1.33, v1.34, and v1.35 (now the default). EOL versions v1.28 and v1.29 are removed. Kamaji is updated to edge-26.2.4 with full Kubernetes 1.35 support, and the CAPI Kamaji provider is updated to v0.16.0. A compatibility patch ensures kubelets older than v1.35 are not broken by Kamaji injecting 1.35-specific kubelet fields ([**@lexfrei**](https://github.com/lexfrei) in #2073).
|
||||
|
||||
* **[platform] Make cluster issuer name and ACME solver configurable**: Added `publishing.certificates.solver` (`http01` or `dns01`) and `publishing.certificates.issuerName` (default: `letsencrypt-prod`) parameters to the platform chart. This allows operators to point all ingress TLS annotations at any ClusterIssuer — custom ACME, self-signed, or internal CA — without modifying individual package templates. See the Breaking Changes section for the rename from the previous `issuerType` field ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2077).
|
||||
|
||||
* **[dashboard] VMInstance dropdowns for disks and instanceType**: The VM instance creation form now renders API-backed dropdowns for the `instanceType` field (populated from `VirtualMachineClusterInstancetype` cluster resources) and for disk `name` fields (populated from `VMDisk` resources in the same namespace). Default values are read from the ApplicationDefinition's OpenAPI schema. This eliminates manual lookups and reduces misconfiguration when attaching disks or selecting VM instance types ([**@sircthulhu**](https://github.com/sircthulhu) in #2071).
|
||||
|
||||
* **[installer] Remove CRDs from Helm chart, delegate lifecycle to operator**: The `cozy-installer` Helm chart no longer ships CRDs in its `crds/` directory. CRD lifecycle is now fully managed by the Cozystack operator via the `--install-crds` flag, which applies embedded CRD manifests on every startup using server-side apply. The platform PackageSource is also created by the operator instead of a Helm template. This ensures CRDs and the PackageSource are always up to date after each operator restart, eliminating stale CRDs from Helm's install-only behavior ([**@lexfrei**](https://github.com/lexfrei) in #2074).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[kubevirt] Update KubeVirt to v1.6.4 and CDI to v1.64.0, fix VM pod initialization**: Updated KubeVirt operator to v1.6.4 and CDI operator to v1.64.0, including live migration of existing VMs during the upgrade. Additionally, disabled serial console logging globally via the KubeVirt CR to prevent a known v1.6.x issue ([upstream #15989](https://github.com/kubevirt/kubevirt/issues/15989)) where the `guest-console-log` init container blocked virt-launcher pods from starting, causing all VMs to get stuck in `PodInitializing` state ([**@nbykov0**](https://github.com/nbykov0) in #1833; [**@kvaps**](https://github.com/kvaps) in 7dfb819).
|
||||
|
||||
* **[linstor] Fix DRBD+LUKS+STORAGE resource creation failure**: All newly created encrypted volumes were failing because the DRBD `.res` file was never written due to a missing `setExists(true)` call in the `LuksLayer`. Applied the upstream `skip-adjust-when-device-inaccessible` patch ([LINBIT/linstor-server#477](https://github.com/LINBIT/linstor-server/pull/477)) which fixes the root cause and also prevents unnecessary lsblk calls when devices are not yet physically present ([**@kvaps**](https://github.com/kvaps) in #2072).
|
||||
|
||||
* **[system] Fix monitoring-agents FQDN resolution for tenant workload clusters**: Monitoring agents (`vmagent`, `fluent-bit`) in tenant workload clusters were failing to deliver metrics and logs because service addresses used short DNS names without the cluster domain suffix. Fixed by appending the configured cluster domain from `_cluster.cluster-domain` (with fallback to `cluster.local`) to all vmagent remoteWrite URLs and fluent-bit output hosts ([**@IvanHunters**](https://github.com/IvanHunters) in #2075).
|
||||
|
||||
* **[cozystack-basics] Preserve existing HelmRelease values during reconciliations**: Fixed a data-loss bug where changes made to the `tenant-root` HelmRelease were silently dropped on the next forced or upgrade reconciliation of the `cozystack-basics` HelmRelease. The reconciler now merges new configuration with existing values instead of overwriting them ([**@sircthulhu**](https://github.com/sircthulhu) in #2068).
|
||||
|
||||
* **[cozystack-basics] Deny resourcequotas deletion for tenant admin**: Fixed the `cozy:tenant:admin:base` ClusterRole to explicitly deny deletion of `ResourceQuota` objects for tenant admins and superadmins, preventing accidental removal of tenant resource limits ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2076).
|
||||
|
||||
## Breaking Changes & Upgrade Notes
|
||||
|
||||
* **[platform] Certificate issuer configuration parameters renamed**: The `publishing.certificates.issuerType` field is renamed to `publishing.certificates.solver`, and the value `cloudflare` is renamed to `dns01` to align with standard ACME terminology. A new `publishing.certificates.issuerName` field (default: `letsencrypt-prod`) is introduced to allow pointing all ingresses at a custom ClusterIssuer. Migration 32 is included and automatically converts existing configurations during upgrade — no manual action is required ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2077).
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Migrate ConfigMap references to Platform Package in v1 documentation**: Updated the entire v1 documentation tree to replace legacy ConfigMap-based configuration references with the new Platform Package API, ensuring guides are consistent with the v1 configuration model ([**@sircthulhu**](https://github.com/sircthulhu) in cozystack/website#426).
|
||||
|
||||
* **[website] Add generic Kubernetes deployment guide for v1**: Added a new installation guide covering Cozystack deployment on any generic Kubernetes cluster, expanding the set of supported deployment targets beyond provider-specific guides ([**@lexfrei**](https://github.com/lexfrei) in cozystack/website#408).
|
||||
|
||||
* **[website] Refactor resource planning documentation**: Improved the resource planning guide with a clearer structure and more comprehensive coverage of planning considerations for Cozystack deployments ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#423).
|
||||
|
||||
* **[website] Add ServiceAccount API access documentation and update FAQ**: Added a new article documenting ServiceAccount API access token configuration and updated the FAQ to include related troubleshooting guidance ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#421).
|
||||
|
||||
* **[website] Update networking-mesh allowed-location-ips example**: Replaced provider-specific CLI usage with standard `kubectl` commands in the multi-location networking guide's `allowed-location-ips` example, making the documentation more universally applicable ([**@kvaps**](https://github.com/kvaps) in cozystack/website#425).
|
||||
|
||||
## Contributors
|
||||
|
||||
We'd like to thank all contributors who made this release possible:
|
||||
|
||||
* [**@IvanHunters**](https://github.com/IvanHunters)
|
||||
* [**@IvanStukov**](https://github.com/IvanStukov)
|
||||
* [**@kvaps**](https://github.com/kvaps)
|
||||
* [**@lexfrei**](https://github.com/lexfrei)
|
||||
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil)
|
||||
* [**@nbykov0**](https://github.com/nbykov0)
|
||||
* [**@sircthulhu**](https://github.com/sircthulhu)
|
||||
|
||||
### New Contributors
|
||||
|
||||
We're excited to welcome our first-time contributors:
|
||||
|
||||
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil) - First contribution!
|
||||
|
||||
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.6...v1.0.0-rc.1
|
||||
57
docs/changelogs/v1.0.0-rc.2.md
Normal file
57
docs/changelogs/v1.0.0-rc.2.md
Normal file
@@ -0,0 +1,57 @@
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-rc.2
|
||||
-->
|
||||
|
||||
> **⚠️ Release Candidate Warning**: This is a release candidate intended for final validation before the stable v1.0.0 release. Breaking changes are not expected at this stage, but please test thoroughly before deploying to production.
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[keycloak] Allow custom Ingress hostname via values**: Added an `ingress.host` field to the cozy-keycloak chart values, allowing operators to override the default `keycloak.<root-host>` Ingress hostname. The custom hostname is applied to both the Ingress resource and the `KC_HOSTNAME` environment variable in the StatefulSet. When left empty, the original behavior is preserved (fully backward compatible) ([**@sircthulhu**](https://github.com/sircthulhu) in #2101).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[platform] Fix upgrade issues in migrations, etcd timeout, and migration script**: Fixed multiple upgrade failures discovered during v0.41.1 → v1.0 upgrade testing. Migration 26 now uses the `cozystack.io/ui=true` label (always present on v0.41.1) instead of the new label that depends on migration 22 having run, and adds robust Helm secret deletion with fallback and verification. Migrations 28 and 29 wrap `grep` calls to prevent `pipefail` exits and fix the reconcile annotation to use RFC3339 format. Migration 27 now skips missing CRDs and adds a name-pattern fallback for Helm secret deletion. The etcd HelmRelease timeout is increased from 10m to 30m to accommodate TLS cert rotation hooks. The `migrate-to-version-1.0.sh` script gains the missing `bundle-disable`, `bundle-enable`, `expose-ingress`, and `expose-services` field mappings ([**@kvaps**](https://github.com/kvaps) in #2096).
|
||||
|
||||
* **[platform] Fix orphaned -rd HelmReleases after application renames**: After the `ferretdb→mongodb`, `mysql→mariadb`, and `virtual-machine→vm-disk+vm-instance` renames, the system-level `-rd` HelmReleases in `cozy-system` (`ferretdb-rd`, `mysql-rd`, `virtual-machine-rd`) were left orphaned, referencing ExternalArtifacts that no longer exist and causing persistent reconciliation failures. Migrations 28 and 29 are updated to remove these resources, and migration 33 is added as a safety net for clusters that already passed those migrations ([**@kvaps**](https://github.com/kvaps) in #2102).
|
||||
|
||||
* **[monitoring-agents] Fix FQDN resolution regression in tenant workload clusters**: The fix introduced in #2075 used `_cluster.cluster-domain` references in `values.yaml`, but `_cluster` values are not accessible from Helm subchart contexts — meaning fluent-bit received empty hostnames and failed to forward logs. This PR replaces the `_cluster` references with a new `global.clusterDomain` variable (empty by default for management clusters, set to the cluster domain for tenant clusters), which is correctly shared with all subcharts ([**@kvaps**](https://github.com/kvaps) in #2086).
|
||||
|
||||
* **[dashboard] Fix legacy templating and cluster identifier in sidebar links**: Standardized the cluster identifier used across dashboard menu links, administration links, and API request paths, resolving incorrect or broken link targets for the Backups and External IPs sidebar sections ([**@androndo**](https://github.com/androndo) in #2093).
|
||||
|
||||
* **[dashboard] Fix backupjobs creation form and sidebar backup category identifier**: Fixed the backup job creation form configuration, adding the required Name, Namespace, Plan Name, Application, and Backup Class fields. Fixed the sidebar backup category identifier that was causing incorrect navigation ([**@androndo**](https://github.com/androndo) in #2103).
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Add Helm chart development principles guide**: Added a new developer guide section documenting Cozystack's four core Helm chart principles: easy upstream updates, local-first artifacts, local dev/test workflow, and no external dependencies ([**@kvaps**](https://github.com/kvaps) in cozystack/website#418).
|
||||
|
||||
* **[website] Add network architecture overview**: Added comprehensive network architecture documentation covering the multi-layered networking stack — MetalLB (L2/BGP), Cilium eBPF (kube-proxy replacement), Kube-OVN (centralized IPAM), and tenant isolation with identity-based eBPF policies — with Mermaid diagrams for all major traffic flows ([**@IvanHunters**](https://github.com/IvanHunters) in cozystack/website#422).
|
||||
|
||||
* **[website] Update documentation to use jsonpatch for service exposure**: Improved `kubectl patch` commands throughout installation and configuration guides to use JSON Patch `add` operations for extending arrays instead of replacing them wholesale, making the documented commands safer and more precise ([**@sircthulhu**](https://github.com/sircthulhu) in cozystack/website#427).
|
||||
|
||||
* **[website] Update certificates section in Platform Package documentation**: Updated the certificate configuration documentation to reflect the new `solver` and `issuerName` fields introduced in v1.0.0-rc.1, replacing the legacy `issuerType` references ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in cozystack/website#429).
|
||||
|
||||
* **[website] Add tenant Kubernetes cluster log querying guide**: Added documentation for querying logs from tenant Kubernetes clusters in Grafana using VictoriaLogs labels (`tenant`, `kubernetes_namespace_name`, `kubernetes_pod_name`), including the `monitoringAgents` addon prerequisite and step-by-step filtering examples ([**@IvanHunters**](https://github.com/IvanHunters) in cozystack/website#430).
|
||||
|
||||
* **[website] Replace non-idempotent commands with idempotent alternatives**: Updated `helm install` to `helm upgrade --install`, `kubectl create -f` to `kubectl apply -f`, and `kubectl create ns` to the dry-run+apply pattern across all installation and deployment guides so commands can be safely re-run ([**@lexfrei**](https://github.com/lexfrei) in cozystack/website#431).
|
||||
|
||||
* **[website] Fix broken documentation links with `.md` suffix**: Fixed incorrect internal links with `.md` suffix across virtualization guides for both v0 and v1 documentation, standardizing link text to "Developer Guide" ([**@cheese**](https://github.com/cheese) in cozystack/website#432).
|
||||
|
||||
## Contributors
|
||||
|
||||
We'd like to thank all contributors who made this release possible:
|
||||
|
||||
* [**@androndo**](https://github.com/androndo)
|
||||
* [**@cheese**](https://github.com/cheese)
|
||||
* [**@IvanHunters**](https://github.com/IvanHunters)
|
||||
* [**@kvaps**](https://github.com/kvaps)
|
||||
* [**@lexfrei**](https://github.com/lexfrei)
|
||||
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil)
|
||||
* [**@sircthulhu**](https://github.com/sircthulhu)
|
||||
|
||||
### New Contributors
|
||||
|
||||
We're excited to welcome our first-time contributors:
|
||||
|
||||
* [**@cheese**](https://github.com/cheese) - First contribution!
|
||||
|
||||
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-rc.1...v1.0.0-rc.2
|
||||
@@ -32,6 +32,54 @@ if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 0: Annotate critical resources to prevent Helm from deleting them
|
||||
echo "Step 0: Protect critical resources from Helm deletion"
|
||||
echo ""
|
||||
echo "The following resources will be annotated with helm.sh/resource-policy=keep"
|
||||
echo "to prevent Helm from deleting them when the installer release is removed:"
|
||||
echo " - Namespace: $NAMESPACE"
|
||||
echo " - ConfigMap: $NAMESPACE/cozystack-version"
|
||||
echo ""
|
||||
read -p "Do you want to annotate these resources? (y/N) " -n 1 -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Annotating namespace $NAMESPACE..."
|
||||
kubectl annotate namespace "$NAMESPACE" helm.sh/resource-policy=keep --overwrite
|
||||
echo "Annotating ConfigMap cozystack-version..."
|
||||
kubectl annotate configmap -n "$NAMESPACE" cozystack-version helm.sh/resource-policy=keep --overwrite 2>/dev/null || echo " ConfigMap cozystack-version not found, skipping."
|
||||
echo ""
|
||||
echo "Resources annotated successfully."
|
||||
else
|
||||
echo "WARNING: Skipping annotation. If you remove the Helm installer release,"
|
||||
echo "the namespace and its contents may be deleted!"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 1: Check for cozy-proxy HelmRelease with conflicting releaseName
|
||||
# In v0.41.x, cozy-proxy was incorrectly configured with releaseName "cozystack",
|
||||
# which conflicts with the installer helm release name. If not suspended, cozy-proxy
|
||||
# HelmRelease will overwrite the installer release and delete cozystack-operator.
|
||||
COZY_PROXY_RELEASE_NAME=$(kubectl get hr -n "$NAMESPACE" cozy-proxy -o jsonpath='{.spec.releaseName}' 2>/dev/null || true)
|
||||
if [ "$COZY_PROXY_RELEASE_NAME" = "cozystack" ]; then
|
||||
echo "WARNING: HelmRelease cozy-proxy has releaseName 'cozystack', which conflicts"
|
||||
echo "with the installer release. It must be suspended before proceeding, otherwise"
|
||||
echo "it will overwrite the installer and delete cozystack-operator."
|
||||
echo ""
|
||||
read -p "Suspend HelmRelease cozy-proxy? (y/N) " -n 1 -r
|
||||
echo ""
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
kubectl -n "$NAMESPACE" patch hr cozy-proxy --type=merge --field-manager=flux-client-side-apply -p '{"spec":{"suspend":true}}'
|
||||
echo "HelmRelease cozy-proxy suspended."
|
||||
else
|
||||
echo "ERROR: Cannot proceed with conflicting cozy-proxy HelmRelease active."
|
||||
echo "Please suspend it manually:"
|
||||
echo " kubectl -n $NAMESPACE patch hr cozy-proxy --type=merge -p '{\"spec\":{\"suspend\":true}}'"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Read ConfigMap cozystack
|
||||
echo "Reading ConfigMap cozystack..."
|
||||
COZYSTACK_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack -o json 2>/dev/null || echo "{}")
|
||||
@@ -52,6 +100,10 @@ OIDC_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["oidc-enabled"] // "false"')
|
||||
KEYCLOAK_REDIRECTS=$(echo "$COZYSTACK_CM" | jq -r '.data["extra-keycloak-redirect-uri-for-dashboard"] // ""' )
|
||||
TELEMETRY_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["telemetry-enabled"] // "true"')
|
||||
BUNDLE_NAME=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-name"] // "paas-full"')
|
||||
BUNDLE_DISABLE=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-disable"] // ""')
|
||||
BUNDLE_ENABLE=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-enable"] // ""')
|
||||
EXPOSE_INGRESS=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-ingress"] // "tenant-root"')
|
||||
EXPOSE_SERVICES=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-services"] // ""')
|
||||
|
||||
# Certificate issuer configuration (old undocumented field: clusterissuer)
|
||||
OLD_CLUSTER_ISSUER=$(echo "$COZYSTACK_CM" | jq -r '.data["clusterissuer"] // ""')
|
||||
@@ -99,28 +151,31 @@ else
|
||||
EXTERNAL_IPS=$(echo "$EXTERNAL_IPS" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - "$0}')
|
||||
fi
|
||||
|
||||
# Determine bundle type
|
||||
case "$BUNDLE_NAME" in
|
||||
paas-full|distro-full)
|
||||
SYSTEM_ENABLED="true"
|
||||
SYSTEM_TYPE="full"
|
||||
;;
|
||||
paas-hosted|distro-hosted)
|
||||
SYSTEM_ENABLED="false"
|
||||
SYSTEM_TYPE="hosted"
|
||||
;;
|
||||
*)
|
||||
SYSTEM_ENABLED="false"
|
||||
SYSTEM_TYPE="hosted"
|
||||
;;
|
||||
esac
|
||||
# Convert comma-separated lists to YAML arrays
|
||||
if [ -z "$BUNDLE_DISABLE" ]; then
|
||||
DISABLED_PACKAGES="[]"
|
||||
else
|
||||
DISABLED_PACKAGES=$(echo "$BUNDLE_DISABLE" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - cozystack."$0}')
|
||||
fi
|
||||
|
||||
if [ -z "$BUNDLE_ENABLE" ]; then
|
||||
ENABLED_PACKAGES="[]"
|
||||
else
|
||||
ENABLED_PACKAGES=$(echo "$BUNDLE_ENABLE" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - cozystack."$0}')
|
||||
fi
|
||||
|
||||
if [ -z "$EXPOSE_SERVICES" ]; then
|
||||
EXPOSED_SERVICES_YAML="[]"
|
||||
else
|
||||
EXPOSED_SERVICES_YAML=$(echo "$EXPOSE_SERVICES" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - "$0}')
|
||||
fi
|
||||
|
||||
# Update bundle naming
|
||||
BUNDLE_NAME=$(echo "$BUNDLE_NAME" | sed 's/paas/isp/')
|
||||
|
||||
# Extract branding if available
|
||||
BRANDING=$(echo "$BRANDING_CM" | jq -r '.data // {} | to_entries[] | "\(.key): \"\(.value)\""')
|
||||
if [ -z "$BRANDING" ]; then
|
||||
if [ -z "$BRANDING" ]; then
|
||||
BRANDING="{}"
|
||||
else
|
||||
BRANDING=$(echo "$BRANDING" | awk 'BEGIN{print}{print " " $0}')
|
||||
@@ -141,8 +196,6 @@ echo " Root Host: $ROOT_HOST"
|
||||
echo " API Server Endpoint: $API_SERVER_ENDPOINT"
|
||||
echo " OIDC Enabled: $OIDC_ENABLED"
|
||||
echo " Bundle Name: $BUNDLE_NAME"
|
||||
echo " System Enabled: $SYSTEM_ENABLED"
|
||||
echo " System Type: $SYSTEM_TYPE"
|
||||
echo " Certificate Solver: ${SOLVER:-http01 (default)}"
|
||||
echo " Issuer Name: ${ISSUER_NAME:-letsencrypt-prod (default)}"
|
||||
echo ""
|
||||
@@ -160,15 +213,8 @@ spec:
|
||||
platform:
|
||||
values:
|
||||
bundles:
|
||||
system:
|
||||
enabled: $SYSTEM_ENABLED
|
||||
type: "$SYSTEM_TYPE"
|
||||
iaas:
|
||||
enabled: true
|
||||
paas:
|
||||
enabled: true
|
||||
naas:
|
||||
enabled: true
|
||||
disabledPackages: $DISABLED_PACKAGES
|
||||
enabledPackages: $ENABLED_PACKAGES
|
||||
networking:
|
||||
clusterDomain: "$CLUSTER_DOMAIN"
|
||||
podCIDR: "$POD_CIDR"
|
||||
@@ -177,6 +223,8 @@ spec:
|
||||
joinCIDR: "$JOIN_CIDR"
|
||||
publishing:
|
||||
host: "$ROOT_HOST"
|
||||
ingressName: "$EXPOSE_INGRESS"
|
||||
exposedServices: $EXPOSED_SERVICES_YAML
|
||||
apiServerEndpoint: "$API_SERVER_ENDPOINT"
|
||||
externalIPs: $EXTERNAL_IPS
|
||||
${CERTIFICATES_SECTION}
|
||||
|
||||
@@ -156,7 +156,7 @@ menuItems = append(menuItems, map[string]any{
|
||||
map[string]any{
|
||||
"key": "{plural}",
|
||||
"label": "{ResourceLabel}",
|
||||
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/{group}/{version}/{plural}",
|
||||
"link": "/openapi-ui/{cluster}/{namespace}/api-table/{group}/{version}/{plural}",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -174,7 +174,7 @@ menuItems = append(menuItems, map[string]any{
|
||||
|
||||
**Important Notes**:
|
||||
- The sidebar tag (`{lowercase-kind}-sidebar`) must match what the Factory uses
|
||||
- The link format: `/openapi-ui/{clusterName}/{namespace}/api-table/{group}/{version}/{plural}`
|
||||
- The link format: `/openapi-ui/{cluster}/{namespace}/api-table/{group}/{version}/{plural}`
|
||||
- All sidebars share the same `keysAndTags` and `menuItems`, so changes affect all sidebar instances
|
||||
|
||||
### Step 4: Verify Integration
|
||||
|
||||
@@ -195,6 +195,7 @@ func applyListInputOverrides(schema map[string]any, kind string, openAPIProps ma
|
||||
"valueUri": "/api/clusters/{cluster}/k8s/apis/instancetype.kubevirt.io/v1beta1/virtualmachineclusterinstancetypes",
|
||||
"keysToValue": []any{"metadata", "name"},
|
||||
"keysToLabel": []any{"metadata", "name"},
|
||||
"allowEmpty": true,
|
||||
},
|
||||
}
|
||||
if prop, _ := openAPIProps["instanceType"].(map[string]any); prop != nil {
|
||||
|
||||
@@ -202,6 +202,10 @@ func TestApplyListInputOverrides_VMInstance(t *testing.T) {
|
||||
t.Errorf("expected valueUri %s, got %v", expectedURI, customProps["valueUri"])
|
||||
}
|
||||
|
||||
if customProps["allowEmpty"] != true {
|
||||
t.Errorf("expected allowEmpty true, got %v", customProps["allowEmpty"])
|
||||
}
|
||||
|
||||
// Check disks[].name is a listInput
|
||||
disks, ok := specProps["disks"].(map[string]any)
|
||||
if !ok {
|
||||
|
||||
@@ -582,15 +582,14 @@ type factoryFlags struct {
|
||||
Secrets bool
|
||||
}
|
||||
|
||||
// factoryFeatureFlags tries several conventional locations so you can evolve the API
|
||||
// without breaking the controller. Defaults are false (hidden).
|
||||
// factoryFeatureFlags determines which tabs to show based on whether the
|
||||
// ApplicationDefinition has non-empty Include resource selectors.
|
||||
// Workloads tab is always shown.
|
||||
func factoryFeatureFlags(crd *cozyv1alpha1.ApplicationDefinition) factoryFlags {
|
||||
var f factoryFlags
|
||||
|
||||
f.Workloads = true
|
||||
f.Ingresses = true
|
||||
f.Services = true
|
||||
f.Secrets = true
|
||||
|
||||
return f
|
||||
return factoryFlags{
|
||||
Workloads: true,
|
||||
Ingresses: len(crd.Spec.Ingresses.Include) > 0,
|
||||
Services: len(crd.Spec.Services.Include) > 0,
|
||||
Secrets: len(crd.Spec.Secrets.Include) > 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +299,6 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.ApplicationDefini
|
||||
|
||||
// Add other stock sidebars that are created for each CRD
|
||||
stockSidebars := []string{
|
||||
"stock-instance-api-form",
|
||||
"stock-instance-api-table",
|
||||
"stock-instance-builtin-form",
|
||||
"stock-instance-builtin-table",
|
||||
"stock-project-factory-marketplace",
|
||||
"stock-project-factory-workloadmonitor-details",
|
||||
"stock-project-api-form",
|
||||
@@ -311,6 +307,10 @@ func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.ApplicationDefini
|
||||
"stock-project-builtin-table",
|
||||
"stock-project-crd-form",
|
||||
"stock-project-crd-table",
|
||||
"stock-instance-api-form",
|
||||
"stock-instance-api-table",
|
||||
"stock-instance-builtin-form",
|
||||
"stock-instance-builtin-table",
|
||||
}
|
||||
for _, sidebarID := range stockSidebars {
|
||||
expected["Sidebar"][sidebarID] = true
|
||||
|
||||
@@ -68,31 +68,46 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
|
||||
tags[i] = t
|
||||
}
|
||||
|
||||
specMap := map[string]any{
|
||||
"description": d.Description,
|
||||
"name": displayName,
|
||||
"type": "nonCrd",
|
||||
"apiGroup": "apps.cozystack.io",
|
||||
"apiVersion": "v1alpha1",
|
||||
"plural": app.Plural, // e.g., "buckets"
|
||||
"disabled": false,
|
||||
"hidden": false,
|
||||
"tags": tags,
|
||||
"icon": d.Icon,
|
||||
}
|
||||
|
||||
specBytes, err := json.Marshal(specMap)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, mp, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
m.addDashboardLabels(mp, crd, ResourceTypeDynamic)
|
||||
|
||||
// Preserve user-set disabled/hidden values from existing resource
|
||||
disabled := false
|
||||
hidden := false
|
||||
if mp.Spec.Raw != nil {
|
||||
var existing map[string]any
|
||||
if err := json.Unmarshal(mp.Spec.Raw, &existing); err == nil {
|
||||
if v, ok := existing["disabled"].(bool); ok {
|
||||
disabled = v
|
||||
}
|
||||
if v, ok := existing["hidden"].(bool); ok {
|
||||
hidden = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specMap := map[string]any{
|
||||
"description": d.Description,
|
||||
"name": displayName,
|
||||
"type": "nonCrd",
|
||||
"apiGroup": "apps.cozystack.io",
|
||||
"apiVersion": "v1alpha1",
|
||||
"plural": app.Plural, // e.g., "buckets"
|
||||
"disabled": disabled,
|
||||
"hidden": hidden,
|
||||
"tags": tags,
|
||||
"icon": d.Icon,
|
||||
}
|
||||
|
||||
specBytes, err := json.Marshal(specMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only update spec if it's different to avoid unnecessary updates
|
||||
newSpec := dashv1alpha1.ArbitrarySpec{
|
||||
JSON: apiextv1.JSON{Raw: specBytes},
|
||||
|
||||
@@ -17,8 +17,7 @@ import (
|
||||
|
||||
// ensureSidebar creates/updates multiple Sidebar resources that share the same menu:
|
||||
// - The "details" sidebar tied to the current kind (stock-project-factory-<kind>-details)
|
||||
// - The stock-instance sidebars: api-form, api-table, builtin-form, builtin-table
|
||||
// - The stock-project sidebars: api-form, api-table, builtin-form, builtin-table, crd-form, crd-table
|
||||
// - The stock-project sidebars: api-form, api-table, builtin-form, builtin-table, crd-form, crd-table
|
||||
//
|
||||
// Menu rules:
|
||||
// - The first section is "Marketplace" with two hardcoded entries:
|
||||
@@ -39,6 +38,23 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
}
|
||||
all = crdList.Items
|
||||
|
||||
// 1b) Fetch all MarketplacePanels to determine which resources are hidden
|
||||
hiddenResources := map[string]bool{}
|
||||
var mpList dashv1alpha1.MarketplacePanelList
|
||||
if err := m.List(ctx, &mpList, &client.ListOptions{}); err == nil {
|
||||
for i := range mpList.Items {
|
||||
mp := &mpList.Items[i]
|
||||
if mp.Spec.Raw != nil {
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(mp.Spec.Raw, &spec); err == nil {
|
||||
if hidden, ok := spec["hidden"].(bool); ok && hidden {
|
||||
hiddenResources[mp.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
|
||||
type item struct {
|
||||
Key string
|
||||
@@ -64,6 +80,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
plural := pickPlural(kind, def)
|
||||
lowerKind := strings.ToLower(kind)
|
||||
|
||||
// Skip resources hidden via MarketplacePanel
|
||||
if hiddenResources[def.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this resource is a module
|
||||
if def.Spec.Dashboard.Module {
|
||||
// Special case: info should have its own keysAndTags, not be in modules
|
||||
@@ -176,23 +197,23 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
|
||||
// Add hardcoded Backups section
|
||||
menuItems = append(menuItems, map[string]any{
|
||||
"key": "backups",
|
||||
"key": "backups-category",
|
||||
"label": "Backups",
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"key": "plans",
|
||||
"label": "Plans",
|
||||
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/plans",
|
||||
"link": "/openapi-ui/{cluster}/{namespace}/api-table/backups.cozystack.io/v1alpha1/plans",
|
||||
},
|
||||
map[string]any{
|
||||
"key": "backupjobs",
|
||||
"label": "BackupJobs",
|
||||
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backupjobs",
|
||||
"link": "/openapi-ui/{cluster}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backupjobs",
|
||||
},
|
||||
map[string]any{
|
||||
"key": "backups",
|
||||
"label": "Backups",
|
||||
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backups",
|
||||
"link": "/openapi-ui/{cluster}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backups",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -215,7 +236,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
map[string]any{
|
||||
"key": "loadbalancer-services",
|
||||
"label": "External IPs",
|
||||
"link": "/openapi-ui/{clusterName}/{namespace}/factory/external-ips",
|
||||
"link": "/openapi-ui/{cluster}/{namespace}/factory/external-ips",
|
||||
},
|
||||
map[string]any{
|
||||
"key": "tenants",
|
||||
@@ -228,13 +249,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
// 6) Prepare the list of Sidebar IDs to upsert with the SAME content
|
||||
// Create sidebars for ALL CRDs with dashboard config
|
||||
targetIDs := []string{
|
||||
// stock-instance sidebars
|
||||
"stock-instance-api-form",
|
||||
"stock-instance-api-table",
|
||||
"stock-instance-builtin-form",
|
||||
"stock-instance-builtin-table",
|
||||
|
||||
// stock-project sidebars
|
||||
// stock-project sidebars (namespace-level, full menu)
|
||||
"stock-project-factory-marketplace",
|
||||
"stock-project-factory-workloadmonitor-details",
|
||||
"stock-project-factory-kube-service-details",
|
||||
@@ -250,6 +265,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
||||
"stock-project-builtin-table",
|
||||
"stock-project-crd-form",
|
||||
"stock-project-crd-table",
|
||||
// stock-instance sidebars (namespace-level pages after namespace is selected)
|
||||
"stock-instance-api-form",
|
||||
"stock-instance-api-table",
|
||||
"stock-instance-builtin-form",
|
||||
"stock-instance-builtin-table",
|
||||
}
|
||||
|
||||
// Add details sidebars for all CRDs with dashboard config
|
||||
|
||||
@@ -505,7 +505,7 @@ func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride {
|
||||
createFormItem("spec.applicationRef.name", "Application Name", "text"),
|
||||
createFormItemWithAPI("spec.backupClassName", "Backup Class", "select", map[string]any{
|
||||
"api": map[string]any{
|
||||
"fetchUrl": "/api/clusters/{clusterName}/k8s/apis/backups.cozystack.io/v1alpha1/backupclasses",
|
||||
"fetchUrl": "/api/clusters/{cluster}/k8s/apis/backups.cozystack.io/v1alpha1/backupclasses",
|
||||
"pathToItems": []any{"items"},
|
||||
"pathToValue": []any{"metadata", "name"},
|
||||
"pathToLabel": []any{"metadata", "name"},
|
||||
@@ -516,6 +516,27 @@ func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride {
|
||||
createFormItem("spec.schedule.cron", "Schedule Cron", "text"),
|
||||
},
|
||||
}),
|
||||
|
||||
// BackupJobs form override - backups.cozystack.io/v1alpha1
|
||||
createCustomFormsOverride("default-/backups.cozystack.io/v1alpha1/backupjobs", map[string]any{
|
||||
"formItems": []any{
|
||||
createFormItem("metadata.name", "Name", "text"),
|
||||
createFormItem("metadata.namespace", "Namespace", "text"),
|
||||
createFormItem("spec.planRef.name", "Plan Name (optional)", "text"),
|
||||
createFormItem("spec.applicationRef.apiGroup", "Application API Group", "text"),
|
||||
createFormItem("spec.applicationRef.kind", "Application Kind", "text"),
|
||||
createFormItem("spec.applicationRef.name", "Application Name", "text"),
|
||||
createFormItemWithAPI("spec.backupClassName", "Backup Class", "select", map[string]any{
|
||||
"api": map[string]any{
|
||||
"fetchUrl": "/api/clusters/{cluster}/k8s/apis/backups.cozystack.io/v1alpha1/backupclasses",
|
||||
"pathToItems": []any{"items"},
|
||||
"pathToValue": []any{"metadata", "name"},
|
||||
"pathToLabel": []any{"metadata", "name"},
|
||||
"clusterNameVar": "clusterName",
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1915,12 +1936,12 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
map[string]any{
|
||||
"type": "EnrichedTable",
|
||||
"data": map[string]any{
|
||||
"id": "external-ips-table",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
|
||||
"clusterNamePartOfUrl": "{2}",
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1.services",
|
||||
"pathToItems": []any{"items"},
|
||||
"id": "external-ips-table",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
|
||||
"cluster": "{2}",
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1.services",
|
||||
"pathToItems": ".items",
|
||||
"fieldSelector": map[string]any{
|
||||
"spec.type": "LoadBalancer",
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:7deeee117e7eec599cb453836ca95eadd131dfc8c875dc457ef29dc1433395e0
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:3753b735b0315bee90de54cb25cfebc63bd2cc90ad11ca4fdc0e70439abd5096
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:604561e23df1b8eb25c24cf73fd93c7aaa6d1e7c56affbbda5c6f0f83424e4b1
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:1c8c842277f45f189a5c645fcf7b2023c8ed7189f44029ce8b988019000da14c
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:19ee4c76f0b3b7b40b97995ca78988ad8c82f6e9c75288d8b7b4b88a64f75d50
|
||||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:39f626c802dd84f95720ffb54fcd80dfb8a58ac280498870d0a1aa30d4252f94
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- $targetTenant := .Values._namespace.monitoring }}
|
||||
{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }}
|
||||
{{- if .Values.addons.monitoringAgents.enabled }}
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
@@ -49,7 +50,7 @@ spec:
|
||||
cluster: {{ .Release.Name }}
|
||||
tenant: {{ .Release.Namespace }}
|
||||
remoteWrite:
|
||||
url: http://vminsert-shortterm.{{ $targetTenant }}.svc:8480/insert/0/prometheus
|
||||
url: http://vminsert-shortterm.{{ $targetTenant }}.svc.{{ $clusterDomain }}:8480/insert/0/prometheus
|
||||
fluent-bit:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
@@ -72,7 +73,7 @@ spec:
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match kube.*
|
||||
Host vlogs-generic.{{ $targetTenant }}.svc
|
||||
Host vlogs-generic.{{ $targetTenant }}.svc.{{ $clusterDomain }}
|
||||
port 9428
|
||||
compress gzip
|
||||
uri /insert/jsonline?_stream_fields=stream,kubernetes_pod_name,kubernetes_container_name,kubernetes_namespace_name&_msg_field=log&_time_field=date
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
name: cozystack-etcd-application-default-etcd
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
timeout: 30m
|
||||
install:
|
||||
remediation:
|
||||
retries: -1
|
||||
|
||||
@@ -34,6 +34,12 @@ spec:
|
||||
metadata:
|
||||
annotations:
|
||||
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
|
||||
{{- $ovnIPName := printf "%s.%s" (include "virtual-machine.fullname" .) .Release.Namespace }}
|
||||
{{- $ovnIP := lookup "kubeovn.io/v1" "IP" "" $ovnIPName }}
|
||||
{{- if $ovnIP }}
|
||||
ovn.kubernetes.io/mac_address: {{ $ovnIP.spec.macAddress | quote }}
|
||||
ovn.kubernetes.io/ip_address: {{ $ovnIP.spec.ipAddress | quote }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "virtual-machine.labels" . | nindent 8 }}
|
||||
spec:
|
||||
|
||||
@@ -10,6 +10,8 @@ metadata:
|
||||
labels:
|
||||
cozystack.io/system: "true"
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
cozystackOperator:
|
||||
# Deployment variant: talos, generic, hosted
|
||||
variant: talos
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.6@sha256:c7490da9c1ccb51bff4dd5657ca6a33a29ac71ad9861dfa8c72fdfc8b5765b93
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.5@sha256:eed2b2aa2d1cb1819bfd5bafd00ccf0a9562fe7b787eb6e391d8941cf41fee00
|
||||
platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages'
|
||||
platformSourceRef: 'digest=sha256:b29b87d1a2b80452ffd4db7516a102c30c55121552dcdb237055d4124d12c55d'
|
||||
platformSourceRef: 'digest=sha256:f1df5097ea9ea59cf4aec3d54095dc587b0cd7f6355829e21f44576976416bcc'
|
||||
# Generic variant configuration (only used when cozystackOperator.variant=generic)
|
||||
cozystack:
|
||||
# Kubernetes API server host (IP only, no protocol/port)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Migration 26 --> 27
|
||||
# Migrate monitoring resources from extra/monitoring to system/monitoring
|
||||
# This migration re-labels resources so they become owned by monitoring-system HelmRelease
|
||||
# and deletes old helm release secrets so that helm does not diff old vs new chart manifests.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -35,10 +36,39 @@ relabel_resources() {
|
||||
done
|
||||
}
|
||||
|
||||
# Delete all helm release secrets for a given release name in a namespace.
|
||||
# Uses both label selector and name-pattern matching to ensure complete cleanup.
|
||||
delete_helm_secrets() {
|
||||
local ns="$1"
|
||||
local release="$2"
|
||||
|
||||
# Primary: delete by label selector
|
||||
kubectl delete secrets -n "$ns" -l "name=${release},owner=helm" --ignore-not-found
|
||||
|
||||
# Fallback: find and delete by name pattern (in case labels were modified)
|
||||
local remaining
|
||||
remaining=$(kubectl get secrets -n "$ns" -o name | { grep "^secret/sh\.helm\.release\.v1\.${release}\." || true; })
|
||||
if [ -n "$remaining" ]; then
|
||||
echo " Found secrets not matched by label selector, deleting by name..."
|
||||
echo "$remaining" | while IFS= read -r secret; do
|
||||
echo " Deleting $secret"
|
||||
kubectl delete -n "$ns" "$secret" --ignore-not-found
|
||||
done
|
||||
fi
|
||||
|
||||
# Verify all secrets are gone
|
||||
remaining=$(kubectl get secrets -n "$ns" -o name | { grep "^secret/sh\.helm\.release\.v1\.${release}\." || true; })
|
||||
if [ -n "$remaining" ]; then
|
||||
echo " ERROR: Failed to delete helm release secrets:"
|
||||
echo "$remaining"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Find all tenant namespaces with monitoring HelmRelease
|
||||
echo "Finding tenant namespaces with monitoring HelmRelease..."
|
||||
NAMESPACES=$(kubectl get hr --all-namespaces -l apps.cozystack.io/application.kind=Monitoring \
|
||||
-o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' 2>/dev/null | sort -u || true)
|
||||
NAMESPACES=$(kubectl get hr --all-namespaces -l cozystack.io/ui=true --field-selector=metadata.name=monitoring \
|
||||
-o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' | sort -u)
|
||||
|
||||
if [ -z "$NAMESPACES" ]; then
|
||||
echo "No monitoring HelmReleases found in tenant namespaces, skipping migration"
|
||||
@@ -66,7 +96,7 @@ for ns in $NAMESPACES; do
|
||||
# Step 1: Suspend the HelmRelease
|
||||
echo ""
|
||||
echo "Step 1: Suspending HelmRelease monitoring..."
|
||||
kubectl patch hr -n "$ns" monitoring --type=merge -p '{"spec":{"suspend":true}}' 2>/dev/null || true
|
||||
kubectl patch hr -n "$ns" monitoring --type=merge -p '{"spec":{"suspend":true}}'
|
||||
|
||||
# Wait a moment for reconciliation to stop
|
||||
sleep 2
|
||||
@@ -74,7 +104,7 @@ for ns in $NAMESPACES; do
|
||||
# Step 2: Delete helm secrets for the monitoring release
|
||||
echo ""
|
||||
echo "Step 2: Deleting helm secrets for monitoring release..."
|
||||
kubectl delete secrets -n "$ns" -l name=monitoring,owner=helm --ignore-not-found
|
||||
delete_helm_secrets "$ns" "monitoring"
|
||||
|
||||
# Step 3: Relabel resources to be owned by monitoring-system
|
||||
echo ""
|
||||
@@ -121,7 +151,9 @@ for ns in $NAMESPACES; do
|
||||
echo "Processing Cozystack resources..."
|
||||
relabel_resources "$ns" "workloadmonitors.cozystack.io"
|
||||
|
||||
# Step 4: Delete the suspended HelmRelease (Flux won't delete resources when HR is suspended)
|
||||
# Step 4: Delete the suspended HelmRelease
|
||||
# Helm secrets are already gone, so flux finalizer will find no release to uninstall
|
||||
# and will simply remove the finalizer without deleting any resources.
|
||||
echo ""
|
||||
echo "Step 4: Deleting suspended HelmRelease monitoring..."
|
||||
kubectl delete hr -n "$ns" monitoring --ignore-not-found
|
||||
|
||||
@@ -5,10 +5,24 @@ set -euo pipefail
|
||||
|
||||
# Migrate Piraeus CRDs to piraeus-operator-crds Helm release
|
||||
for crd in linstorclusters.piraeus.io linstornodeconnections.piraeus.io linstorsatelliteconfigurations.piraeus.io linstorsatellites.piraeus.io; do
|
||||
kubectl annotate crd "$crd" meta.helm.sh/release-namespace=cozy-linstor meta.helm.sh/release-name=piraeus-operator-crds --overwrite
|
||||
kubectl label crd "$crd" app.kubernetes.io/managed-by=Helm helm.toolkit.fluxcd.io/namespace=cozy-linstor helm.toolkit.fluxcd.io/name=piraeus-operator-crds --overwrite
|
||||
if kubectl get crd "$crd" >/dev/null 2>&1; then
|
||||
echo " Relabeling CRD $crd"
|
||||
kubectl annotate crd "$crd" meta.helm.sh/release-namespace=cozy-linstor meta.helm.sh/release-name=piraeus-operator-crds --overwrite
|
||||
kubectl label crd "$crd" app.kubernetes.io/managed-by=Helm helm.toolkit.fluxcd.io/namespace=cozy-linstor helm.toolkit.fluxcd.io/name=piraeus-operator-crds --overwrite
|
||||
else
|
||||
echo " CRD $crd not found, skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
# Delete old piraeus-operator helm secrets (by label and by name pattern)
|
||||
kubectl delete secret -n cozy-linstor -l name=piraeus-operator,owner=helm --ignore-not-found
|
||||
remaining=$(kubectl get secrets -n cozy-linstor -o name 2>/dev/null | { grep "^secret/sh\.helm\.release\.v1\.piraeus-operator\." || true; })
|
||||
if [ -n "$remaining" ]; then
|
||||
echo " Deleting remaining piraeus-operator helm secrets by name..."
|
||||
echo "$remaining" | while IFS= read -r secret; do
|
||||
kubectl delete -n cozy-linstor "$secret" --ignore-not-found
|
||||
done
|
||||
fi
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
|
||||
@@ -348,7 +348,7 @@ PVCEOF
|
||||
# --- 3g: Clone Secrets ---
|
||||
echo " --- Clone Secrets ---"
|
||||
for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release"); do
|
||||
| { grep "secret/${OLD_NAME}" || true; } | { grep -v "sh.helm.release" || true; }); do
|
||||
old_secret_name="${secret#secret/}"
|
||||
new_secret_name="${NEW_NAME}${old_secret_name#${OLD_NAME}}"
|
||||
clone_resource "$NAMESPACE" "secret" "$old_secret_name" "$new_secret_name" "$OLD_NAME" "$NEW_NAME"
|
||||
@@ -357,7 +357,7 @@ PVCEOF
|
||||
# --- 3h: Clone ConfigMaps ---
|
||||
echo " --- Clone ConfigMaps ---"
|
||||
for cm in $(kubectl -n "$NAMESPACE" get configmap -o name 2>/dev/null \
|
||||
| grep "configmap/${OLD_NAME}"); do
|
||||
| { grep "configmap/${OLD_NAME}" || true; }); do
|
||||
old_cm_name="${cm#configmap/}"
|
||||
new_cm_name="${NEW_NAME}${old_cm_name#${OLD_NAME}}"
|
||||
clone_resource "$NAMESPACE" "configmap" "$old_cm_name" "$new_cm_name" "$OLD_NAME" "$NEW_NAME"
|
||||
@@ -468,13 +468,13 @@ PVCEOF
|
||||
fi
|
||||
|
||||
for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release"); do
|
||||
| { grep "secret/${OLD_NAME}" || true; } | { grep -v "sh.helm.release" || true; }); do
|
||||
old_secret_name="${secret#secret/}"
|
||||
delete_resource "$NAMESPACE" "secret" "$old_secret_name"
|
||||
done
|
||||
|
||||
for cm in $(kubectl -n "$NAMESPACE" get configmap -o name 2>/dev/null \
|
||||
| grep "configmap/${OLD_NAME}"); do
|
||||
| { grep "configmap/${OLD_NAME}" || true; }); do
|
||||
old_cm_name="${cm#configmap/}"
|
||||
delete_resource "$NAMESPACE" "configmap" "$old_cm_name"
|
||||
done
|
||||
@@ -611,6 +611,19 @@ done
|
||||
echo ""
|
||||
echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ==="
|
||||
|
||||
# ============================================================
|
||||
# STEP 8: Clean up orphaned mysql-rd system HelmRelease
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 8: Clean up orphaned mysql-rd HelmRelease ---"
|
||||
if kubectl -n cozy-system get hr mysql-rd --no-headers 2>/dev/null | grep -q .; then
|
||||
echo " [DELETE] hr/mysql-rd"
|
||||
kubectl -n cozy-system delete hr mysql-rd --wait=false
|
||||
else
|
||||
echo " [SKIP] hr/mysql-rd already gone"
|
||||
fi
|
||||
kubectl -n cozy-system delete secret -l "owner=helm,name=mysql-rd" --ignore-not-found
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=29 --dry-run=client -o yaml | kubectl apply -f-
|
||||
|
||||
@@ -9,8 +9,6 @@ set -euo pipefail
|
||||
OLD_PREFIX="virtual-machine"
|
||||
NEW_DISK_PREFIX="vm-disk"
|
||||
NEW_INSTANCE_PREFIX="vm-instance"
|
||||
PROTECTION_WEBHOOK_NAME="protection-webhook"
|
||||
PROTECTION_WEBHOOK_NS="protection-webhook"
|
||||
CDI_APISERVER_NS="cozy-kubevirt-cdi"
|
||||
CDI_APISERVER_DEPLOY="cdi-apiserver"
|
||||
CDI_VALIDATING_WEBHOOKS="cdi-api-datavolume-validate cdi-api-dataimportcron-validate cdi-api-populator-validate cdi-api-validate"
|
||||
@@ -88,7 +86,6 @@ echo " Total: ${#INSTANCES[@]} instance(s)"
|
||||
# STEP 2: Migrate each instance
|
||||
# ============================================================
|
||||
ALL_PV_NAMES=()
|
||||
ALL_PROTECTED_RESOURCES=()
|
||||
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
NAMESPACE="${entry%%/*}"
|
||||
@@ -315,7 +312,7 @@ PVCEOF
|
||||
# --- 2i: Clone Secrets ---
|
||||
echo " --- Clone Secrets ---"
|
||||
kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \
|
||||
| { grep "secret/${OLD_NAME}" || true; } | { grep -v "sh.helm.release" || true; } | { grep -v "values" || true; } \
|
||||
| while IFS= read -r secret; do
|
||||
old_secret_name="${secret#secret/}"
|
||||
suffix="${old_secret_name#${OLD_NAME}}"
|
||||
@@ -542,7 +539,7 @@ SVCEOF
|
||||
# --- 2q: Delete old resources ---
|
||||
echo " --- Delete old resources ---"
|
||||
kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \
|
||||
| { grep "secret/${OLD_NAME}" || true; } | { grep -v "sh.helm.release" || true; } | { grep -v "values" || true; } \
|
||||
| while IFS= read -r secret; do
|
||||
old_secret_name="${secret#secret/}"
|
||||
delete_resource "$NAMESPACE" "secret" "$old_secret_name"
|
||||
@@ -564,71 +561,17 @@ SVCEOF
|
||||
delete_resource "$NAMESPACE" "secret" "$VALUES_SECRET"
|
||||
fi
|
||||
|
||||
# Collect protected resources for batch deletion
|
||||
# Delete old service (if exists)
|
||||
if resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then
|
||||
ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:svc/${OLD_NAME}")
|
||||
delete_resource "$NAMESPACE" "svc" "$OLD_NAME"
|
||||
fi
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 3: Delete protected resources (Services)
|
||||
# STEP 3: Restore PV reclaim policies
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 3: Delete protected resources ---"
|
||||
|
||||
if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then
|
||||
WEBHOOK_EXISTS=false
|
||||
if kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" --no-headers 2>/dev/null | grep -q .; then
|
||||
WEBHOOK_EXISTS=true
|
||||
fi
|
||||
|
||||
if [ "$WEBHOOK_EXISTS" = "true" ]; then
|
||||
echo " --- Temporarily disabling protection-webhook ---"
|
||||
|
||||
WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \
|
||||
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1")
|
||||
|
||||
echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})"
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0
|
||||
|
||||
echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}"
|
||||
kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \
|
||||
jq '.webhooks[].failurePolicy = "Ignore"' | \
|
||||
kubectl apply -f - 2>/dev/null || true
|
||||
|
||||
echo " Waiting for webhook pods to terminate..."
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \
|
||||
-l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do
|
||||
ns="${entry%%:*}"
|
||||
res="${entry#*:}"
|
||||
echo " [DELETE] ${ns}/${res}"
|
||||
kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true
|
||||
done
|
||||
|
||||
if [ "$WEBHOOK_EXISTS" = "true" ]; then
|
||||
echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}"
|
||||
kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \
|
||||
jq '.webhooks[].failurePolicy = "Fail"' | \
|
||||
kubectl apply -f - 2>/dev/null || true
|
||||
|
||||
echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}"
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \
|
||||
--replicas="$WEBHOOK_REPLICAS"
|
||||
echo " --- protection-webhook restored ---"
|
||||
fi
|
||||
else
|
||||
echo " [SKIP] No protected resources to delete"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# STEP 4: Restore PV reclaim policies
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 4: Restore PV reclaim policies ---"
|
||||
echo "--- Step 3: Restore PV reclaim policies ---"
|
||||
for pv_name in "${ALL_PV_NAMES[@]}"; do
|
||||
if [ -n "$pv_name" ]; then
|
||||
current_policy=$(kubectl get pv "$pv_name" \
|
||||
@@ -643,7 +586,7 @@ for pv_name in "${ALL_PV_NAMES[@]}"; do
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 5: Temporarily disable CDI datavolume webhooks
|
||||
# STEP 4: Temporarily disable CDI datavolume webhooks
|
||||
# ============================================================
|
||||
# CDI's datavolume-validate webhook rejects DataVolume creation when a PVC
|
||||
# with the same name already exists. We must disable it so that vm-disk
|
||||
@@ -652,7 +595,7 @@ done
|
||||
# cdi-apiserver (which serves the webhooks), then delete webhook configs.
|
||||
# Both are restored after vm-disk HRs reconcile.
|
||||
echo ""
|
||||
echo "--- Step 5: Temporarily disable CDI webhooks ---"
|
||||
echo "--- Step 4: Temporarily disable CDI webhooks ---"
|
||||
|
||||
CDI_OPERATOR_REPLICAS=$(kubectl -n "$CDI_APISERVER_NS" get deploy cdi-operator \
|
||||
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1")
|
||||
@@ -685,10 +628,10 @@ done
|
||||
sleep 2
|
||||
|
||||
# ============================================================
|
||||
# STEP 6: Unsuspend vm-disk HelmReleases first
|
||||
# STEP 5: Unsuspend vm-disk HelmReleases first
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 6: Unsuspend vm-disk HelmReleases ---"
|
||||
echo "--- Step 5: Unsuspend vm-disk HelmReleases ---"
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
ns="${entry%%/*}"
|
||||
instance="${entry#*/}"
|
||||
@@ -705,7 +648,7 @@ for entry in "${INSTANCES[@]}"; do
|
||||
# Force immediate reconciliation
|
||||
echo " [TRIGGER] Reconcile ${ns}/hr/${disk_name}"
|
||||
kubectl -n "$ns" annotate hr "$disk_name" --overwrite \
|
||||
"reconcile.fluxcd.io/requestedAt=$(date +%s)" 2>/dev/null || true
|
||||
"reconcile.fluxcd.io/requestedAt=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -729,12 +672,12 @@ for entry in "${INSTANCES[@]}"; do
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 7: Restore CDI webhooks
|
||||
# STEP 6: Restore CDI webhooks
|
||||
# ============================================================
|
||||
# Scale cdi-operator and cdi-apiserver back up.
|
||||
# cdi-apiserver will recreate webhook configurations automatically on start.
|
||||
echo ""
|
||||
echo "--- Step 7: Restore CDI webhooks ---"
|
||||
echo "--- Step 6: Restore CDI webhooks ---"
|
||||
|
||||
echo " [SCALE] cdi-operator -> ${CDI_OPERATOR_REPLICAS}"
|
||||
kubectl -n "$CDI_APISERVER_NS" scale deploy cdi-operator \
|
||||
@@ -749,10 +692,10 @@ kubectl -n "$CDI_APISERVER_NS" rollout status deploy "$CDI_APISERVER_DEPLOY" --t
|
||||
echo " --- CDI webhooks restored ---"
|
||||
|
||||
# ============================================================
|
||||
# STEP 8: Unsuspend vm-instance HelmReleases
|
||||
# STEP 7: Unsuspend vm-instance HelmReleases
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 8: Unsuspend vm-instance HelmReleases ---"
|
||||
echo "--- Step 7: Unsuspend vm-instance HelmReleases ---"
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
ns="${entry%%/*}"
|
||||
instance="${entry#*/}"
|
||||
@@ -772,6 +715,19 @@ done
|
||||
echo ""
|
||||
echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ==="
|
||||
|
||||
# ============================================================
|
||||
# STEP 8: Clean up orphaned virtual-machine-rd system HelmRelease
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 8: Clean up orphaned virtual-machine-rd HelmRelease ---"
|
||||
if kubectl -n cozy-system get hr virtual-machine-rd --no-headers 2>/dev/null | grep -q .; then
|
||||
echo " [DELETE] hr/virtual-machine-rd"
|
||||
kubectl -n cozy-system delete hr virtual-machine-rd --wait=false
|
||||
else
|
||||
echo " [SKIP] hr/virtual-machine-rd already gone"
|
||||
fi
|
||||
kubectl -n cozy-system delete secret -l "owner=helm,name=virtual-machine-rd" --ignore-not-found
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f-
|
||||
|
||||
30
packages/core/platform/images/migrations/migrations/33
Executable file
30
packages/core/platform/images/migrations/migrations/33
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
# Migration 33 --> 34
|
||||
# Clean up orphaned system -rd HelmReleases left after application renames.
|
||||
#
|
||||
# These HelmReleases reference ExternalArtifacts that no longer exist:
|
||||
# ferretdb-rd -> replaced by mongodb-rd
|
||||
# mysql-rd -> replaced by mariadb-rd (migration 28 handled user HRs only)
|
||||
# virtual-machine-rd -> replaced by vm-disk-rd + vm-instance-rd (migration 29 handled user HRs only)
|
||||
#
|
||||
# Idempotent: safe to re-run.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Cleaning up orphaned -rd HelmReleases ==="
|
||||
|
||||
for hr_name in ferretdb-rd mysql-rd virtual-machine-rd; do
|
||||
if kubectl -n cozy-system get hr "$hr_name" --no-headers 2>/dev/null | grep -q .; then
|
||||
echo " [DELETE] hr/${hr_name}"
|
||||
kubectl -n cozy-system delete hr "$hr_name" --wait=false
|
||||
else
|
||||
echo " [SKIP] hr/${hr_name} already gone"
|
||||
fi
|
||||
kubectl -n cozy-system delete secret -l "owner=helm,name=${hr_name}" --ignore-not-found
|
||||
done
|
||||
|
||||
echo "=== Cleanup complete ==="
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=34 --dry-run=client -o yaml | kubectl apply -f-
|
||||
@@ -24,7 +24,7 @@ if [ "$CURRENT_VERSION" -ge "$TARGET_VERSION" ]; then
|
||||
fi
|
||||
|
||||
# Run migrations sequentially from current version to target version
|
||||
for i in $(seq $((CURRENT_VERSION + 1)) $TARGET_VERSION); do
|
||||
for i in $(seq $CURRENT_VERSION $((TARGET_VERSION - 1))); do
|
||||
if [ -f "/migrations/$i" ]; then
|
||||
echo "Running migration $i"
|
||||
chmod +x /migrations/$i
|
||||
|
||||
@@ -6,6 +6,8 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: cozystack-version
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
data:
|
||||
version: {{ .Values.migrations.targetVersion | quote }}
|
||||
{{- end }}
|
||||
|
||||
@@ -5,8 +5,8 @@ sourceRef:
|
||||
path: /
|
||||
migrations:
|
||||
enabled: false
|
||||
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.6@sha256:37c78dafcedbdad94acd9912550db0b4875897150666b8a06edfa894de99064e
|
||||
targetVersion: 33
|
||||
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.5@sha256:d43c6f26c65edd448f586a29969ff76718338f1f1f78b24d3ad54c6c8977c748
|
||||
targetVersion: 34
|
||||
# Bundle deployment configuration
|
||||
bundles:
|
||||
system:
|
||||
@@ -46,7 +46,7 @@ publishing:
|
||||
apiServerEndpoint: "" # example: "https://api.example.org"
|
||||
externalIPs: []
|
||||
certificates:
|
||||
solver: http01 # "http01" or "dns01"
|
||||
solver: http01 # "http01" or "dns01"
|
||||
issuerName: letsencrypt-prod
|
||||
# Authentication configuration
|
||||
authentication:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
e2e:
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.6@sha256:09af5901abcbed2b612d2d93c163e8ad3948bc55a1d8beae714b4fb2b8f7d91d
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.5@sha256:0eae9f519669667d60b160ebb93c127843c470ad9ca3447fceaa54604503a7ba
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.6@sha256:212f624957447f5a932fd5d4564eb8c97694d336b7dc877a2833c1513c0d074d
|
||||
ghcr.io/cozystack/cozystack/matchbox:v1.0.5@sha256:81bd1cd76fd01a61b0bab082aa61325054f6ef80eb56b683ddd38ae072dd0351
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.6@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.5@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
backupController:
|
||||
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.6@sha256:365214a74ffc34a9314a62a7d4b491590051fc5486f6bae9913c0c1289983d43"
|
||||
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.5@sha256:6c61c68fb4110ddcc4e1f5ab5835c4bdcb0c74b5c672e34d73beafd52ec6056d"
|
||||
replicas: 2
|
||||
debug: false
|
||||
metrics:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
backupStrategyController:
|
||||
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.6@sha256:aa04ee61dce11950162606fc8db2d5cbc6f5b32ba700f790b3f1eee10d65efb1"
|
||||
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.5@sha256:96eed9980a63cc1093249dcfd5408be8f278cf6c11896d92885aa8a093395a9b"
|
||||
replicas: 2
|
||||
debug: false
|
||||
metrics:
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:291427de7db54a1d19dc9c2c807bdcc664a14caa9538786f31317e8c01a4a008
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:6ace7a9da7dea7eb0a8895d8ce72d3e3ac806488058aac6f386756fc0f143d78
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
cozystackAPI:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.6@sha256:d89cf68fb622d0dbef7db98db09d352efc93c2cce448d11f2d73dcd363e911b7
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.5@sha256:7a39918402ae50c51d22d4005e5cf9974a1b8ca0f85441944f685c420b45088f
|
||||
replicas: 2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cozystackController:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.6@sha256:d55a3c288934b1f69a00321bc8a94776915556b5f882fe6ac615e9de2701c61f
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.5@sha256:5b6dcf2d968343111e6681b2e76ff8725c7cde7826c24a771ae1666e5382b462
|
||||
debug: false
|
||||
disableTelemetry: false
|
||||
|
||||
@@ -6,7 +6,7 @@ FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder
|
||||
RUN apk add git
|
||||
WORKDIR /src
|
||||
# release/1.4.0
|
||||
ARG COMMIT=c67029cc7b7495c65ee0406033576e773a73bb01
|
||||
ARG COMMIT=d6b9e4ad0d1eb9d3730f7f0c664792c8dda3214d
|
||||
RUN wget -O- https://github.com/PRO-Robotech/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1
|
||||
|
||||
COPY openapi-k8s-toolkit/patches /patches
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
diff --git a/src/localTypes/formExtensions.ts b/src/localTypes/formExtensions.ts
|
||||
--- a/src/localTypes/formExtensions.ts
|
||||
+++ b/src/localTypes/formExtensions.ts
|
||||
@@ -59,2 +59,4 @@
|
||||
relatedValuePath?: string
|
||||
+ allowEmpty?: boolean
|
||||
+ persistType?: 'str' | 'number' | 'arr' | 'obj'
|
||||
}
|
||||
diff --git a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
--- a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
+++ b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
@@ -149,3 +149,10 @@
|
||||
}, [relatedPath, form, arrName, fixedName, relatedFieldValue, onValuesChangeCallBack, isTouchedPeristed])
|
||||
|
||||
+ // When allowEmpty is set, auto-persist the field so the BFF preserves empty values
|
||||
+ useEffect(() => {
|
||||
+ if (customProps.allowEmpty) {
|
||||
+ persistedControls.onPersistMark(persistName || name, customProps.persistType ?? 'str')
|
||||
+ }
|
||||
+ }, [customProps.allowEmpty, customProps.persistType, persistedControls, persistName, name])
|
||||
+
|
||||
const uri = prepareTemplate({
|
||||
@@ -267,5 +274,14 @@
|
||||
validateTrigger="onBlur"
|
||||
hasFeedback={designNewLayout ? { icons: feedbackIcons } : true}
|
||||
style={{ flex: 1 }}
|
||||
+ normalize={(value: unknown) => {
|
||||
+ if (customProps.allowEmpty && (value === undefined || value === null)) {
|
||||
+ if (customProps.persistType === 'number') return 0
|
||||
+ if (customProps.persistType === 'arr') return []
|
||||
+ if (customProps.persistType === 'obj') return {}
|
||||
+ return ''
|
||||
+ }
|
||||
+ return value
|
||||
+ }}
|
||||
>
|
||||
<Select
|
||||
@@ -1,49 +0,0 @@
|
||||
diff --git a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
index d5e5230..9038dbb 100644
|
||||
--- a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
+++ b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx
|
||||
@@ -259,14 +259,15 @@ export const FormListInput: FC<TFormListInputProps> = ({
|
||||
<PersistedCheckbox formName={persistName || name} persistedControls={persistedControls} type="arr" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
- <ResetedFormItem
|
||||
- key={arrKey !== undefined ? arrKey : Array.isArray(name) ? name.slice(-1)[0] : name}
|
||||
- name={arrName || fixedName}
|
||||
- rules={[getRequiredRule(forceNonRequired === false && !!required?.includes(getStringByName(name)), name)]}
|
||||
- validateTrigger="onBlur"
|
||||
- hasFeedback={designNewLayout ? { icons: feedbackIcons } : true}
|
||||
- >
|
||||
- <Flex gap={8} align="center">
|
||||
+ <Flex gap={8} align="center">
|
||||
+ <ResetedFormItem
|
||||
+ key={arrKey !== undefined ? arrKey : Array.isArray(name) ? name.slice(-1)[0] : name}
|
||||
+ name={arrName || fixedName}
|
||||
+ rules={[getRequiredRule(forceNonRequired === false && !!required?.includes(getStringByName(name)), name)]}
|
||||
+ validateTrigger="onBlur"
|
||||
+ hasFeedback={designNewLayout ? { icons: feedbackIcons } : true}
|
||||
+ style={{ flex: 1 }}
|
||||
+ >
|
||||
<Select
|
||||
mode={customProps.mode}
|
||||
placeholder="Select"
|
||||
@@ -277,13 +278,13 @@ export const FormListInput: FC<TFormListInputProps> = ({
|
||||
showSearch
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
- {relatedValueTooltip && (
|
||||
- <Tooltip title={relatedValueTooltip}>
|
||||
- <QuestionCircleOutlined />
|
||||
- </Tooltip>
|
||||
- )}
|
||||
- </Flex>
|
||||
- </ResetedFormItem>
|
||||
+ </ResetedFormItem>
|
||||
+ {relatedValueTooltip && (
|
||||
+ <Tooltip title={relatedValueTooltip}>
|
||||
+ <QuestionCircleOutlined />
|
||||
+ </Tooltip>
|
||||
+ )}
|
||||
+ </Flex>
|
||||
</HiddenContainer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
diff --git a/src/components/organisms/DynamicComponents/molecules/SecretBase64Plain/SecretBase64Plain.tsx b/src/components/organisms/DynamicComponents/molecules/SecretBase64Plain/SecretBase64Plain.tsx
|
||||
--- a/src/components/organisms/DynamicComponents/molecules/SecretBase64Plain/SecretBase64Plain.tsx
|
||||
+++ b/src/components/organisms/DynamicComponents/molecules/SecretBase64Plain/SecretBase64Plain.tsx
|
||||
@@ -145,6 +145,12 @@
|
||||
<Styled.DisabledInput
|
||||
$hidden={effectiveHidden}
|
||||
onClick={e => handleInputClick(e, effectiveHidden, value)}
|
||||
+ onCopy={e => {
|
||||
+ if (!effectiveHidden) {
|
||||
+ e.preventDefault()
|
||||
+ e.clipboardData?.setData('text/plain', value)
|
||||
+ }
|
||||
+ }}
|
||||
value={shownValue}
|
||||
readOnly
|
||||
/>
|
||||
@@ -161,6 +167,12 @@
|
||||
<Styled.DisabledInput
|
||||
$hidden={effectiveHidden}
|
||||
onClick={e => handleInputClick(e, effectiveHidden, value)}
|
||||
+ onCopy={e => {
|
||||
+ if (!effectiveHidden) {
|
||||
+ e.preventDefault()
|
||||
+ e.clipboardData?.setData('text/plain', value)
|
||||
+ }
|
||||
+ }}
|
||||
value={shownValue}
|
||||
readOnly
|
||||
/>
|
||||
@@ -1,6 +1,6 @@
|
||||
{{- $brandingConfig := .Values._cluster.branding | default dict }}
|
||||
|
||||
{{- $tenantText := "v1.0.0-beta.6" }}
|
||||
{{- $tenantText := "v1.0.5" }}
|
||||
{{- $footerText := "Cozystack" }}
|
||||
{{- $titleText := "Cozystack Dashboard" }}
|
||||
{{- $logoText := "" }}
|
||||
|
||||
20
packages/system/dashboard/templates/flowschema.yaml
Normal file
20
packages/system/dashboard/templates/flowschema.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: flowcontrol.apiserver.k8s.io/v1
|
||||
kind: FlowSchema
|
||||
metadata:
|
||||
name: cozy-dashboard-exempt
|
||||
spec:
|
||||
matchingPrecedence: 2
|
||||
priorityLevelConfiguration:
|
||||
name: exempt
|
||||
rules:
|
||||
- subjects:
|
||||
- kind: ServiceAccount
|
||||
serviceAccount:
|
||||
name: incloud-web-web
|
||||
namespace: {{ .Release.Namespace }}
|
||||
resourceRules:
|
||||
- verbs: ["*"]
|
||||
apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
namespaces: ["*"]
|
||||
clusterScope: true
|
||||
@@ -136,7 +136,7 @@ spec:
|
||||
- name: CUSTOMIZATION_NAVIGATION_RESOURCE_PLURAL
|
||||
value: navigations
|
||||
- name: CUSTOMIZATION_SIDEBAR_FALLBACK_ID
|
||||
value: stock-project-api-table
|
||||
value: ""
|
||||
- name: CUSTOMIZATION_BREADCRUMBS_FALLBACK_ID
|
||||
value: stock-project-api-table
|
||||
- name: INSTANCES_API_GROUP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapiUI:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.6@sha256:c333637673a9e878f6c4ed0fc96db55967bbcf94b2434d075b0f0c6fcfcf9eff
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.5@sha256:b95c6941991b2f5b5cb61e4b322431f89b3f064468ab3353cf68448143ced4a2
|
||||
openapiUIK8sBff:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.6@sha256:1b3ea6d4c7dbbe6a8def3b2807fffdfab2ac4afc39d7a846e57dd491fa168f92
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.5@sha256:a17f73592c4b37ac8dbb9697e2866bdea301ff4364e235bb58c665b5f41143c4
|
||||
tokenProxy:
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.6@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.5@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
| kubeRbacProxy.args[2] | string | `"--logtostderr=true"` | |
|
||||
| kubeRbacProxy.args[3] | string | `"--v=0"` | |
|
||||
| kubeRbacProxy.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||
| kubeRbacProxy.image.repository | string | `"gcr.io/kubebuilder/kube-rbac-proxy"` | Image repository |
|
||||
| kubeRbacProxy.image.tag | string | `"v0.16.0"` | Version of image |
|
||||
| kubeRbacProxy.image.repository | string | `"quay.io/brancz/kube-rbac-proxy"` | Image repository |
|
||||
| kubeRbacProxy.image.tag | string | `"v0.18.1"` | Version of image |
|
||||
| kubeRbacProxy.livenessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
|
||||
| kubeRbacProxy.readinessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
|
||||
| kubeRbacProxy.resources | object | `{"limits":{"cpu":"250m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}}` | ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ |
|
||||
|
||||
@@ -98,13 +98,13 @@ kubeRbacProxy:
|
||||
image:
|
||||
|
||||
# -- Image repository
|
||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||
repository: quay.io/brancz/kube-rbac-proxy
|
||||
|
||||
# -- Image pull policy
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# -- Version of image
|
||||
tag: v0.16.0
|
||||
tag: v0.18.1
|
||||
|
||||
args:
|
||||
- --secure-listen-address=0.0.0.0:8443
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.6@sha256:7a3c9af59f8d74d5a23750bbc845c7de64610dbd4d4f84011e10be037b3ce2a0
|
||||
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.5@sha256:7a3c9af59f8d74d5a23750bbc845c7de64610dbd4d4f84011e10be037b3ce2a0
|
||||
|
||||
@@ -3,7 +3,7 @@ kamaji:
|
||||
deploy: false
|
||||
image:
|
||||
pullPolicy: IfNotPresent
|
||||
tag: v1.0.0-beta.6@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918
|
||||
tag: v1.0.5@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
|
||||
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:v1.0.0-beta.6@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918
|
||||
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.5@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- $host := index .Values._cluster "root-host" }}
|
||||
{{- $ingressHost := .Values.ingress.host | default (printf "keycloak.%s" $host) }}
|
||||
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
|
||||
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
|
||||
{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }}
|
||||
@@ -19,10 +20,10 @@ spec:
|
||||
ingressClassName: {{ $exposeIngress }}
|
||||
tls:
|
||||
- hosts:
|
||||
- keycloak.{{ $host }}
|
||||
- {{ $ingressHost }}
|
||||
secretName: web-tls
|
||||
rules:
|
||||
- host: keycloak.{{ $host }}
|
||||
- host: {{ $ingressHost }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- $host := index .Values._cluster "root-host" }}
|
||||
{{- $ingressHost := .Values.ingress.host | default (printf "keycloak.%s" $host) }}
|
||||
{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }}
|
||||
|
||||
{{- $existingPassword := lookup "v1" "Secret" "cozy-keycloak" (printf "%s-credentials" .Release.Name) }}
|
||||
@@ -75,14 +76,18 @@ spec:
|
||||
{{- end }}
|
||||
- name: KC_METRICS_ENABLED
|
||||
value: "true"
|
||||
- name: KC_HEALTH_ENABLED
|
||||
value: "true"
|
||||
- name: KC_LOG_LEVEL
|
||||
value: "info"
|
||||
- name: KC_CACHE
|
||||
value: "ispn"
|
||||
- name: KC_CACHE_STACK
|
||||
value: "kubernetes"
|
||||
- name: KC_PROXY
|
||||
value: "edge"
|
||||
- name: KC_PROXY_HEADERS
|
||||
value: "xforwarded"
|
||||
- name: KC_HTTP_ENABLED
|
||||
value: "true"
|
||||
- name: KEYCLOAK_ADMIN
|
||||
value: admin
|
||||
- name: KEYCLOAK_ADMIN_PASSWORD
|
||||
@@ -120,23 +125,34 @@ spec:
|
||||
- name: KC_FEATURES
|
||||
value: "docker"
|
||||
- name: KC_HOSTNAME
|
||||
value: https://keycloak.{{ $host }}
|
||||
value: https://{{ $ingressHost }}
|
||||
- name: JAVA_OPTS_APPEND
|
||||
value: "-Djgroups.dns.query=keycloak-headless.cozy-keycloak.svc.{{ $clusterDomain }}"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: management
|
||||
containerPort: 9000
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: management
|
||||
failureThreshold: 30
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 120
|
||||
path: /health/live
|
||||
port: management
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /realms/master
|
||||
port: http
|
||||
initialDelaySeconds: 60
|
||||
timeoutSeconds: 1
|
||||
path: /health/ready
|
||||
port: management
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
terminationGracePeriodSeconds: 60
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
image: quay.io/keycloak/keycloak:26.0.4
|
||||
|
||||
ingress:
|
||||
# Custom hostname for the Keycloak Ingress.
|
||||
# If set, this value will be used as the Ingress hostname (e.g., "auth.example.com").
|
||||
# If left empty, defaults to "keycloak.<root-host>" based on the cluster root-host setting.
|
||||
host: ""
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/affinity: "cookie"
|
||||
nginx.ingress.kubernetes.io/session-cookie-expires: "86400"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.6@sha256:8e964605efe54e73a94c84abec7dbb5a011c02ccece282bef8ae7b70fce3d217
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.5@sha256:fbbd24c757a9210e44b4fcb7b1b9f0135683090bd508836c569b9fc669fe356a
|
||||
ovnCentralName: ovn-central
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.6@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.5@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
storageClass: replicated
|
||||
csiDriver:
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:604561e23df1b8eb25c24cf73fd93c7aaa6d1e7c56affbbda5c6f0f83424e4b1
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:1c8c842277f45f189a5c645fcf7b2023c8ed7189f44029ce8b988019000da14c
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
lineageControllerWebhook:
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.6@sha256:cf577e56ebc2b94205741d9c5b08f2983cec0811f0c2890edca8fdca22624de1
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.5@sha256:4cff89b0c2b23dd3f7ee5d751744057c29c95c299b6c6a0429e76fae882bf961
|
||||
debug: false
|
||||
localK8sAPIEndpoint:
|
||||
enabled: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
piraeusServer:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/piraeus-server
|
||||
tag: 1.32.3@sha256:66eadfc98cd809d2b3c4e6fd631bcd0c4b4cd72a7fb819ac4a0cab7904280546
|
||||
tag: 1.32.3@sha256:aa97f39d90c0726b587f0a376504f13d1f308adeb42db7d98cec9ac7de237361
|
||||
# Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian)
|
||||
talos:
|
||||
enabled: true
|
||||
@@ -13,4 +13,4 @@ linstor:
|
||||
linstorCSI:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/linstor-csi
|
||||
tag: v1.10.5@sha256:6e6cf48cb994f3918df946e02ec454ac64916678b3e60d78c136b431f1a26155
|
||||
tag: v1.10.5@sha256:7349a57e22224a641304594cb558e4f5f65bf01ffd28d9afc79d422607aac9b2
|
||||
|
||||
@@ -280,8 +280,8 @@ vmagent:
|
||||
cluster: cozystack
|
||||
remoteWrite:
|
||||
urls:
|
||||
- http://vminsert-shortterm.{{ .Values.global.target }}.svc.{{ (index .Values._cluster "cluster-domain") | default "cluster.local" }}:8480/insert/0/prometheus
|
||||
- http://vminsert-longterm.{{ .Values.global.target }}.svc.{{ (index .Values._cluster "cluster-domain") | default "cluster.local" }}:8480/insert/0/prometheus
|
||||
- http://vminsert-shortterm.{{ .Values.global.target }}.svc:8480/insert/0/prometheus
|
||||
- http://vminsert-longterm.{{ .Values.global.target }}.svc:8480/insert/0/prometheus
|
||||
extraArgs: {}
|
||||
|
||||
fluent-bit:
|
||||
@@ -344,7 +344,7 @@ fluent-bit:
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match kube.*
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc.{{ (index .Values._cluster "cluster-domain") | default "cluster.local" }}
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc
|
||||
port 9428
|
||||
compress gzip
|
||||
uri /insert/jsonline?_stream_fields=log_source,stream,kubernetes_pod_name,kubernetes_container_name,kubernetes_namespace_name&_msg_field=log&_time_field=date
|
||||
@@ -355,7 +355,7 @@ fluent-bit:
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match events.*
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc.{{ (index .Values._cluster "cluster-domain") | default "cluster.local" }}
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc
|
||||
port 9428
|
||||
compress gzip
|
||||
uri /insert/jsonline?_stream_fields=log_source,reason,meatdata_namespace,metadata_name&_msg_field=message&_time_field=date
|
||||
@@ -366,7 +366,7 @@ fluent-bit:
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match audit.*
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc.{{ (index .Values._cluster "cluster-domain") | default "cluster.local" }}
|
||||
Host vlogs-generic.{{ .Values.global.target }}.svc
|
||||
port 9428
|
||||
compress gzip
|
||||
uri /insert/jsonline?_stream_fields=log_source,stage,user_username,verb,requestUri&_msg_field=requestURI&_time_field=date
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
objectstorage:
|
||||
controller:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.6@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.5@sha256:e40e94f3014cfd04cce4230597315a1acfcca2daa8051b987614d0c05da6d928"
|
||||
|
||||
@@ -177,7 +177,7 @@ seaweedfs:
|
||||
bucketClassName: "seaweedfs"
|
||||
region: ""
|
||||
sidecar:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.6@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.5@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f"
|
||||
certificates:
|
||||
commonName: "SeaweedFS CA"
|
||||
ipAddresses: []
|
||||
|
||||
@@ -224,8 +224,8 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
|
||||
base, ok1 := doc.Components.Schemas[baseRef]
|
||||
list, ok2 := doc.Components.Schemas[baseListRef]
|
||||
stat, ok3 := doc.Components.Schemas[baseStatusRef]
|
||||
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
|
||||
return doc, fmt.Errorf("base Application* schemas not found")
|
||||
if !(ok1 && ok2 && ok3) {
|
||||
return doc, nil // not the apps GV — nothing to patch
|
||||
}
|
||||
|
||||
// Clone base schemas for each kind
|
||||
@@ -339,8 +339,8 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe
|
||||
base, ok1 := defs[baseRef]
|
||||
list, ok2 := defs[baseListRef]
|
||||
stat, ok3 := defs[baseStatusRef]
|
||||
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
|
||||
return sw, fmt.Errorf("base Application* schemas not found")
|
||||
if !(ok1 && ok2 && ok3) {
|
||||
return sw, nil // not the apps GV — nothing to patch
|
||||
}
|
||||
|
||||
for kind, raw := range kindSchemas {
|
||||
|
||||
Reference in New Issue
Block a user