Compare commits

...

103 Commits

Author SHA1 Message Date
Andrei Kvapil
cb44a01754 Release v0.37.10 (#1703)
This PR prepares the release `v0.37.10`.
2025-12-09 19:54:09 +01:00
cozystack-bot
1111cac7e9 Prepare release v0.37.10
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-12-09 16:22:46 +00:00
Andrei Kvapil
88deb4d87e [Backport release-0.37] [virtual-machine] Improve check for resizing job (#1702)
# Description
Backport of #1688 to `release-0.37`.
2025-12-09 17:10:23 +01:00
Andrei Kvapil
53d4a5e72c [virtual-machine] Improve check for resizing job
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 0bab895026)
2025-12-09 16:09:55 +00:00
Andrei Kvapil
3364f6a3ca [Backport release-0.37] [dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties (#1699)
# Description
Backport of #1692 to `release-0.37`.
2025-12-09 17:05:33 +01:00
Andrei Kvapil
eb5f5d4576 [dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 578a810413)
2025-12-09 16:04:57 +00:00
Andrei Kvapil
d6662856fd Release v0.37.9 (#1667)
This PR prepares the release `v0.37.9`.
2025-11-27 15:52:56 +01:00
cozystack-bot
c82f440ff0 Prepare release v0.37.9
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-27 13:47:48 +00:00
Andrei Kvapil
8f15a3e06c [Backport release-0.37] [seaweedfs] Extended CA certificate duration to reduce disruptive CA rotations. (#1666)
Backport of #1657 to `release-0.37`.

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-27 14:43:23 +01:00
Andrei Kvapil
470cb1cbe5 [tenant][kubernetes] Introduce better cleanup logic (#1661)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[tenant][kubernetes] Introduce better cleanup logic
```

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

* **New Features**
* Added an automated pre-delete cleanup job for tenant namespaces to
remove tenant-related releases during uninstall.

* **Improvements**
* Cleanup now runs early in the uninstall sequence with a clearer,
stepwise orchestration for resource removal.
* Expanded permissions and execution allowances to enable the cleanup
workflow.
* Deployment annotations now use a content checksum to better detect
config changes.

* **Removals**
* Previous teardown sequence for certain release types was removed and
replaced by the new workflow.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-27 14:40:22 +01:00
Andrei Kvapil
94e6d9383d [dashboard] Fix loading arrays in forms when editing existing objects
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-27 14:36:35 +01:00
Andrei Kvapil
d209a2e17d [Backport release-0.37] [dashboard] Add config hash annotations to restart pods on config changes (#1665)
Backport of #1662 to `release-0.38`.

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-27 12:31:20 +01:00
Andrei Kvapil
37d59e7ed9 Release v0.37.8 (#1655)
This PR prepares the release `v0.37.8`.
2025-11-25 18:04:06 +01:00
cozystack-bot
ac33585936 Prepare release v0.37.8
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-25 15:58:09 +00:00
Nikita
1676d34e98 Increase strimzi memory limit (#1651)
## What this PR does
Increase strimzi memory limit

### Release note
```release-note
Increased strimzi memory limit
```
2025-11-25 16:50:54 +01:00
Andrei Kvapil
c85e3cfc51 [cozy-lib] Fix malformed ResourceQuota rendering for LoadBalancer services (#1642)
This patch adds special handling for raw Kubernetes ResourceQuota
fields, such as `services.loadbalancers`, preventing them from being
wrapped as `limits.*` or `requests.*` keys by the flatten helper. This
ensures that LoadBalancer quotas render correctly in tenant
specifications.

```release-note
[cozy-lib] Correctly render services.loadbalancers in ResourceQuota without limits.* or requests.* prefixes.
```

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

## What this PR does


### Release note

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

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

* **Refactor**
* Resource flattening now handles compute and quota keys separately:
compute values are sanitized/flattened, quota-like inputs are emitted
directly as plain YAML.

* **Documentation**
* Added in-template comments and clarified examples for resource
processing behavior.

* **New Features**
* CI now runs unit tests; new test targets and test harnesses added
along with a test chart and test cases for quota handling.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-25 16:50:46 +01:00
Nikita
5271a5c966 [extra] ingress: rm spaces from external ip list (#1652)
## What this PR does
Remove spaces while processing exposed-external-ips list in cozystack
configmap as they 1) are user-specified and 2) lead to an incorrect
resource being created from it.

### Release note
```release-note
Remove spaces while processing exposed-external-ips list in cozystack configmap
```
2025-11-25 16:49:45 +01:00
Andrei Kvapil
0a1ceb6ec6 [Backport release-0.37] scripts: fix 20 migration (#1654)
# Description
Backport of #1653 to `release-0.37`.
2025-11-25 16:48:54 +01:00
nbykov0
b7a809701e scripts: fix 20 migration
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
(cherry picked from commit ec1a150d2c)
2025-11-25 15:34:13 +00:00
Timofei Larkin
2673ddc352 Release v0.37.7 (#1634)
This PR prepares the release `v0.37.7`.
2025-11-13 21:27:04 +04:00
cozystack-bot
b69b628a69 Prepare release v0.37.7
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-13 16:30:01 +00:00
Timofei Larkin
7a261a3094 [Backport release-0.37] [kubernetes] Cleanup loadbalancer services (#1632)
# Description
Backport of #1631 to `release-0.37`.
2025-11-13 18:21:45 +04:00
Timofei Larkin
309a49d349 [kubernetes] Cleanup loadbalancer services
## What this PR does

Similar to an earlier issue with DataVolumes remaining after deleting
the tenant k8s cluster using them, a similar problem is observed with
LoadBalancer services consuming external IPs. This patch adds another
step to the cleanup Helm hook to delete any such services.

### Release note

```release-note
[kubernetes] Add a cleanup hook to delete LoadBalancer services after
deleting the tenant Kubernetes cluster that they were servicing.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 1651d94291)
2025-11-13 14:17:08 +00:00
Nikita
5d6195f146 [system] kubeovn: increase limits (#1629)
## What this PR does
Increases kube-ovn-cni limits

### Release note
```release-note
Increased kube-ovn-cni limits so that it is not oomkilled during startup on busy nodes.
```

(cherry picked from commit a5c9bfabee)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-13 16:38:45 +03:00
Andrei Kvapil
2878fd1491 [Backport release-0.37] [rbac] Fix permissions for high-privilege users (#1624)
# Description
Backport of #1622 to `release-0.37`.
2025-11-12 09:57:06 -05:00
Timofei Larkin
ff89f4c390 [rbac] Fix permissions for high-privilege users
## What this PR does

This patch grants "admin" permissions to super-admins, "use" permissions
to admins and super-admins, "view" permissions to "use"-privileged
users, admins, and super-admins. Previously lower-privileged roles were
not assigned to higher-privileged users, so a viewer could excercise
their basic read-only permissions which were not available to
high-privilege users. This patch corrects the template function used to
generate subjects in rolebindings, fixing the issue.

### Release note

```release-note
[rbac] Fix issue of privileged users not having low-privilege read-only
permissions.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 7ddd9cf4a8)
2025-11-12 14:46:29 +00:00
Andrei Kvapil
ea043bc735 Release v0.37.6 (#1616)
This PR prepares the release `v0.37.6`.
2025-11-07 23:44:07 +01:00
cozystack-bot
b5984e764b Prepare release v0.37.6
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-07 22:00:21 +00:00
Andrei Kvapil
ad117ecf5a [cozystack-controller] improve API tests
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 22:54:45 +01:00
Timofei Larkin
cd3ea570da [api,lineage] Ensure node-local traffic
Since 0.37, many requests to the k8s API now go through a mutating
webhook (lineage-controller-webhook). Since the lineage webhook makes
multiple requests to the k8s API and, indirectly, to the Cozystack API
server, each request for, e.g., creating a secret now causes a lot of
chatter between the webhook, the k8s API, and the Cozystack API. When
this happens cross-node or, worse yet, cross-zone, this can blow up the
latency for simple requests.

This patch changes the Cozystack API to a DaemonSet targetting
controlplane nodes, configures its service for an `Local` internal
traffic policy and adds environment variables indicating that the k8s
API server is to be found at <hostIP>:6443, **not only for the Cozystack
API, but also for the lineage-controller-webhook.** This is a valid
configuration in most scenarios, including the default installation
method on top of Talos Linux in Cozystack, however, if this is not valid
in your environment, you must now set the values
`.lineageControllerWebhook.localK8sAPIEndpoint.enabled` and
`.cozystackAPI.localK8sAPIEndpoint.enabled` to `false` in the respective
system Helm releases.

```release-note
[api,lineage] Configure all chatter between the Lineage webhook, the
Cozystack API server and the Kubernetes API server to be confined to a
single controlplane node, improving k8s API latency.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-07 22:54:28 +01:00
Andrei Kvapil
7ad74f04c6 [dashboard-controller] Fix static resources reconciliation and showing
secrets

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 17:29:59 +01:00
Andrei Kvapil
4cbf562ae6 [virtual-machine] Revert per-vm network policies (#1611)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does

Revert per-vm network policies functionality introduced by
https://github.com/cozystack/cozystack/pull/1611
As it is not working as expected any way.

This is temporary solution before implementing full-fledged security
groups in Cozystack

fixes https://github.com/cozystack/cozystack/issues/1601
alternative solution: https://github.com/cozystack/cozystack/pull/1602

### Release note

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

```release-note
[virtual-machine] Revert per-vm network policies
```
2025-11-07 16:58:57 +01:00
Andrei Kvapil
d8c45db075 [cozystack-api][dashboard] Fix filtering for application services/ingresses/secrets (#1612)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does

- **[dashboard-controller] Fix labelSelectors**
- **[cozystack-api] Enhance TenantSecrets filtering**
- **[cozystack-api] Fix sorting for TenantSecrets**

### Release note

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

```release-note
[cozystack-api][dashboard] Fix filtering for application services/ingresses/secrets
```

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

## Summary by CodeRabbit

* **Refactor**
* Standardized internal configuration naming conventions across
dashboard components.
* Enhanced tenant secret validation and filtering logic with improved
label-based operations for consistency and correctness.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 16:58:52 +01:00
Andrei Kvapil
625f35d66f [dashboard] sync with upstream & enhancements (#1603)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

- Move patches to upstream: `namespaces` and `hide inside`
- Introduce flatMap logic
- Remove `tenantsecretstables` resource
- Extend dashboard-controller to specify `multilineString` for any
string without enum in spec (previusly it was for all strings)

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

```release-note
[dashboard] sync with upstream & enhancements
```

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

* **New Features**
* Enhanced OpenAPI form handling: string fields now better support
multiline input.

* **Improvements**
* Secrets UI and API alignment: secrets display and data keys updated
for consistency.
  * Form generation improved for nested objects and arrays.
* Deployment defaults adjusted (logger flags normalized; inside feature
hidden via env).

* **Removed**
* Removed the "Inside" header menu item and the legacy secrets-table
API/resource.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 16:58:40 +01:00
Timofei Larkin
4b395d758e [api] Use shared informer cache (#1539)
## What this PR does

This patch changes all clients in the Cozystack API server to typed ones
from the controller runtime. This should improve the performance of the
API server and simplifies the code by removing work with unstructured
objects and dynamic clients.

### Release note

```release-note
[api] Use typed and cache-backed k8s clients in the Cozystack API to
improve performance. Get rid of operations on unstructured objects and
use of dynamic clients.
```

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

* **Refactor**
* Backend migrated to a controller-runtime manager with typed clients
for Kubernetes resources, improving watch reliability and cache sync.
* Storage paths for applications, tenant modules, namespaces, and
secrets now use strongly-typed resource handling for more consistent
behavior.

* **Chores**
  * Cluster role expanded to include services in core API permissions.

* **Notes**
  * No user-facing API schema changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 16:52:52 +01:00
Timofei Larkin
c1ac876ff7 [dashboard] Revert reconciler removal (#1559)
## What this PR does

In a previous patch (#1555) the reconciliation loop for the OpenAPI UI
resources was accidentally removed. This patch reintroduces a separate
controller, which handles updates to CozystackResourceDefinitions and
creates, updates, or deletes the dashboard's custom resources.

### Release note

```release-note
[dashboard] Reintroduce the accidentally removed reconciler that
autoconfigures custom dashboard resources for the OpenAPI UI.
```
2025-11-07 16:43:51 +01:00
Timofei Larkin
8d91fcddd4 [controller] Remove crdmem, handle DaemonSet (#1555)
This patch drops the custom caching of the Cozystack resource
definitions in favor of the informer cache and adds a flag to the
Cozystack controller to select, whether it restarts the cozystack-api
deployment or the cozystack-api daemonset.

As with the new default behavior of using a local endpoint for the k8s
API by the lineage webhook and the Cozystack API, the Cozystack
controller now also defaults to restarting a Cozystack API DaemonSet
instead of a Deployment. To revert to the old behavior, disable the
local k8s API endpoint on the webhook and cozystack API and set the
`cozystackController.cozystackAPIKind` value in the Cozystack controller
system Helm chart to "Deployment".

```release-note
[controller] Use informer cache instead of the older bespoke
implementation and add support for running the Cozystack API as a
DaemonSet.
```
2025-11-07 16:20:04 +01:00
Andrei Kvapil
84f55fa728 [Backport release-0.37] [cozy-lib] Fix: handling resources=nil (#1610)
# Description
Backport of #1607 to `release-0.37`.
2025-11-07 11:03:55 +01:00
Andrei Kvapil
fbec0a1064 [Backport release-0.37] [kubernetes] Helm hooks for cleanup (#1609)
# Description
Backport of #1606 to `release-0.37`.
2025-11-07 11:03:45 +01:00
Andrei Kvapil
b7e7b0ac54 [cozy-lib] Fix: handling resources=nil
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Co-authored-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 00328c8a31)
2025-11-07 10:03:42 +00:00
Timofei Larkin
edbccafd49 [kubernetes] Helm hooks for cleanup
## What this PR does

When deleting a Kubernetes, some resources may linger post deletion
because of a race to remove HelmReleases deployed inside the tenant
cluster and the removal of the cluster and its controlplane itself. This
patch modifies the existing pre-delete hook to remove those helmreleases
instead of simply suspending them. Similarly, datavolumes may also
remain. These are now delete with a post-delete hook.

### Release note

```release-note
[kubernetes] Use Helm hooks to clean up HelmReleases deployed in tenant
clusters and DataVolumes backing the tenant clusters' PVCs when deleting
a tenant Kubernetes.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 63db8ca009)
2025-11-07 09:51:05 +00:00
Andrei Kvapil
e7d30df251 [Backport release-0.37] [nats] Fix NATS app chart to use existing secret credentials when present (#1605)
# Description
Backport of #1599 to `release-0.37`.
2025-11-07 09:25:46 +01:00
Isaiah Olson
3a1a69e10b Use dig function to check for existing secret in NATS app template and prevent nil indexing
Signed-off-by: Isaiah Olson <isaiah@olson-network.com>
(cherry picked from commit 627022972d)
2025-11-06 10:11:15 +00:00
Isaiah Olson
46c1116f07 Fix NATS app chart to use existing secret credentials when present
Signed-off-by: Isaiah Olson <isaiah@olson-network.com>
(cherry picked from commit 1e8a9ee980)
2025-11-06 10:11:15 +00:00
Andrei Kvapil
c1f056ff4e Release v0.37.5 (#1595)
This PR prepares the release `v0.37.5`.
2025-11-04 18:55:09 +01:00
cozystack-bot
181fc3cc40 Prepare release v0.37.5
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-04 17:19:31 +00:00
Andrei Kvapil
3d403205ca [Backport release-0.37] [dashboard-controller] Move bages generation logic to internal dashboard component (#1593)
# Description
Backport of #1567 to `release-0.37`.
2025-11-04 17:37:00 +01:00
Andrei Kvapil
3b027a7da9 [dashboard-controller] Move bages generation logic to internal dashboard
component

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit af460f1c41)
2025-11-04 16:36:43 +00:00
Andrei Kvapil
68718b9bf4 [Backport release-0.37] [nats] Fixes for NATS App Helm chart, fix template issues with config.merge (#1591)
# Description
Backport of #1583 to `release-0.37`.
2025-11-04 17:20:08 +01:00
Andrei Kvapil
32b12ceefa [Backport release-0.37] [flux] Close Flux Operator ports to external access (#1592)
# Description
Backport of #1581 to `release-0.37`.
2025-11-04 17:18:42 +01:00
IvanHunters
c1ec2a2fc3 add rule for success installing
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 48c6e23ca0)
2025-11-04 16:13:55 +00:00
IvanHunters
71c32d91a7 add patch
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit f60e2555c9)
2025-11-04 16:13:55 +00:00
IvanHunters
65ea7116ce close metrics port for external
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 52a23eacfc)
2025-11-04 16:13:55 +00:00
IvanHunters
d7b5636f23 [flux] Close Flux Operator ports to external access
This patch updates the Flux Operator Deployment to remove hostPort and hostNetwork,
ensuring that ports 8080 and 8081 are only accessible within the cluster.
This prevents external exposure and improves security.

```release-note
[flux] Close Flux Operator ports (8080/8081) to external access for improved security.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit eea685065a)
2025-11-04 16:13:55 +00:00
Timofei Larkin
b0beb6edf9 [nats] Terser checks using with
This patch makes the fixes from `b1ebc9cc` by @insignia96 terser by
making use of Helm's `with` blocks.

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 172774b6cd)
2025-11-04 16:00:28 +00:00
Isaiah Olson
7058e404cf Fixes for NATS App Helm chart, fix template issues with config.merge value
Signed-off-by: Isaiah Olson <isaiah@olson-network.com>
(cherry picked from commit b1ebc9cc85)
2025-11-04 16:00:28 +00:00
Nikita
25edc1f54b [core] rm talos lldp extension (#1586)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

Removes Talos lldp extension. Please build a custom talos image with factory.talos.dev if you need it.

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

```release-note
Talos lldp extension removed.
```

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

* **Chores**
* Removed LLDPD (Link Layer Discovery Protocol Daemon) system extension
from cluster configuration. This eliminates the LLDPD kernel module from
cluster setups, removes LLDPD references from build processes, and
updates installation profiles across all supported deployment methods
including bare metal, cloud environments, and ISO installations,
resulting in a reduced system footprint.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 13:48:22 +01:00
Andrei Kvapil
36a52277f6 [Backport release-0.37] [kubevirt] Fix: kubevirt metrics rule (#1588)
# Description
Backport of #1584 to `release-0.37`.
2025-11-04 13:43:46 +01:00
Andrei Kvapil
0e5aad8588 [kubevirt] Fix: kubevirt metrics rule
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 6443a1264e)
2025-11-04 12:35:02 +00:00
Andrei Kvapil
70cb6b586a [redis] Bump Redis image version for security fixes (#1580)
This patch updates the RedisFailover Helm template to use a newer,
secure Redis version (8.2.0). This addresses known security issues in
the previous Redis version and ensures safer deployments.

```release-note
[redis] Upgrade Redis to a secure version (8.2.0) to fix security vulnerabilities.
```

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

## What this PR does


### Release note

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


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

* **New Features**
* Redis deployments can now specify the container image via a new
configurable value (default: redis:8.2.0), allowing easy override of the
Redis image used.
* **Schema**
* Values schema and resource definition schemas updated to include and
validate the new image setting.
* **Documentation**
  * README updated to document the new image parameter.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 10:27:38 +01:00
Andrei Kvapil
90b29c7d4d [Backport release-0.37] [ingress] Enforce HTTPS-only for API (#1585)
# Description
Backport of #1582 to `release-0.37`.
2025-11-04 10:26:17 +01:00
IvanHunters
82e5a0d403 [ingress] Enforce HTTPS-only for API
This patch updates the default API Ingress to add the
nginx.ingress.kubernetes.io/force-ssl-redirect annotation,
ensuring all HTTP traffic (port 80) is redirected to HTTPS (port 443).
This prevents unencrypted external access and improves security.

```release-note
[ingress] Force HTTPS access for api.dev3.infra.aenix.org and block direct HTTP.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 667c778f27)
2025-11-04 09:18:47 +00:00
Andrei Kvapil
520cfd2526 Release v0.37.4 (#1578)
This PR prepares the release `v0.37.4`.
2025-11-03 13:41:08 +01:00
cozystack-bot
39db2c141a Prepare release v0.37.4
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-11-03 11:03:59 +00:00
Andrei Kvapil
42c94cfebd [Backport release-0.37] [tenant] Allow listing workloads (#1577)
# Description
Backport of #1576 to `release-0.37`.
2025-11-03 11:58:05 +01:00
Andrei Kvapil
9fae1b17b8 [tenant] Allow listing workloads
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 93a9241899)
2025-11-03 10:57:22 +00:00
Andrei Kvapil
5d232bef97 [Backport release-0.37] [seaweedfs] Fix migration to v3.99 (#1575)
# Description
Backport of #1572 to `release-0.37`.
2025-11-03 11:33:42 +01:00
Andrei Kvapil
19d2f43bf5 [Backport release-0.37] [nats] Merge container spec, not podTemplate (#1574)
# Description
Backport of #1571 to `release-0.37`.
2025-11-03 11:33:30 +01:00
Andrei Kvapil
7f646d84bb [seaweedfs] Fix migration to v3.99
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 42e6f0e3f2)
2025-11-03 10:33:28 +00:00
Timofei Larkin
83505f50c7 [nats] Merge container spec, not podTemplate
## What this PR does

The NATS chart incorrectly used podTemplate+merge instead of
container+merge to add resource requests and limits to the NATS
container in the statefulset, but as a result it just completely wiped
out the default container spec. By moving the overrides under the
container key, the upstream chart now correctly merges the resource
requests, instead of overwriting the container spec.

### Release note

```release-note
[nats] Fix incorrect path to container resources in the NATS chart.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit 8b95db06ee)
2025-11-03 10:33:14 +00:00
Andrei Kvapil
46de1f311b [Backport release-0.37] [e2e] Increase Kubernetes connection timeouts (#1573)
# Description
Backport of #1570 to `release-0.37`.
2025-11-03 10:48:38 +01:00
IvanHunters
682a933156 [e2e] Increase Kubernetes connection timeouts
This patch increases the connection and request timeouts used in the
E2E tests when communicating with the Kubernetes API. The change improves
test stability under high load and slow cluster response conditions.

```release-note
[e2e] Increase connection and request timeouts for Kubernetes API calls in E2E tests to improve stability.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit e2eb1e267b)
2025-11-03 09:48:21 +00:00
Andrei Kvapil
6d402ecff9 Release v0.37.3 (#1566)
This PR prepares the release `v0.37.3`.
2025-11-01 03:23:25 +05:00
cozystack-bot
2cb80079d1 Prepare release v0.37.3
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-31 20:39:20 +00:00
Andrei Kvapil
b8604b8ec5 [seaweedfs] Update SeaweedFS v3.99 and deploy S3 as stacked service (#1562)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[]
```

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

* **New Features**
* Helm values now control ingress paths; computed cluster endpoint env
vars are injected.
  * Optional container securityContext for volume init containers added.
  * Node architecture-specific targeting disabled by default.

* **Refactor**
* Image configuration reorganized with separate registry field;
container image build simplified.

* **Bug Fixes / Behavior**
* S3-related authorization and signature handling changed; S3 gateway
toggled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 21:24:13 +01:00
Andrei Kvapil
f645a5d8fc Update LINSTOR v1.32.3 (#1565)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
Update LINSTOR v1.32.3
```
2025-10-31 21:22:30 +01:00
Timofei Larkin
31d87671a1 [system] kube-ovn: turn off enableLb (#1548)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does
Turns off kubeovn enableLb, kube-proxy implementation of kube-ovn.

### Release note

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

```release-note
[system] kube-ovn: turn off kube-proxy implementation
```

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

## Summary by CodeRabbit

* **Chores**
* Added a new load balancing configuration option to system settings
(disabled by default).

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:27:31 +01:00
Timofei Larkin
13b294c9a7 [redis-operator] Build patched operator in-tree (#1547)
## What this PR does

This patch moves the build of the Redis operator into the Cozystack
organization and patches it to prevent overwriting third-party labels on
owned resources.

### Release note

```release-note
[redis-operator] Move operator into tree and patch it to retain
third-party labels on owned resources, reducing noisy traffic to the API
server.
```

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

## Summary by CodeRabbit

* **Chores**
* Implemented automated Docker image build pipeline with version
tracking and caching.
* Updated image configuration to include repository reference and digest
for reproducibility.

* **Bug Fixes**
* Improved label and annotation handling to preserve existing Kubernetes
resource metadata instead of overwriting it.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:26:31 +01:00
Andrei Kvapil
2ee2bf55ed [velero] Set defaultItemOperationTimeout=24h (#1542)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does

This PR changes default timeout for Velero to copy single item. Default
value 4h is not enough for copying large block volumes of virtual
machines.

### Release note

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

```release-note
[velero] Set defaultItemOperationTimeout=24h
```

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

## Summary by CodeRabbit

* **Chores**
* Extended default operation timeout to 24 hours to provide increased
time for operations to complete.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:25:31 +01:00
Andrei Kvapil
39f51f7fba [dashboard] Update openapi-ui v1.0.3 + fixes (#1564)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

- Update openapi-ui to v1.0.3
- Show YAML editor as readonly in YAML tab
- Remove inside link from user menu
- fix editing for tenantmodules, fixes
https://github.com/cozystack/cozystack/issues/1550
- fix editing valuesOverride, fixes
https://github.com/cozystack/cozystack/issues/1560

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

```release-note
[dashboard] Update openapi-ui v1.0.3 + fixes
```

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

* **Bug Fixes**
* Made YAML editor read-only to prevent accidental modifications in
details view
  * Fixed API request header handling to prevent stream abort issues
  * Updated resource API endpoint paths for correct data retrieval
  * Removed menu navigation item from user interface
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:21:09 +01:00
Timofei Larkin
0c50253e2c [kubernetes] Use controlPlane.replicas field (#1556)
## What this PR does

The managed Kubernetes app accepts a .controPlane.replicas field, but
this value was never used, instead being hardcoded in the
KamajiControlPlane template to 2. This patch fixes this.

### Release note

```release-note
[kubernetes] Pass the .controlPlane.replicas field into the
KamajiControlPlane template, making the replica count of the
controlplane pods user-configurable.
```

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

## Summary by CodeRabbit

* **New Features**
* Control plane replica count is now configurable via Helm values,
allowing flexible deployment scaling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:18:57 +01:00
Andrei Kvapil
cfab053c66 [mariadb-operator] Add post-delete job to remove PVCs (#1553)
<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does

[mariadb-operator] Add post-delete job to remove PVCs
This patch adds a Helm post-delete hook job that removes
PersistentVolumeClaims
left behind after Helm release deletion. The MariaDB Operator currently
does not
handle PVC cleanup, so this job ensures proper resource removal.

### Release note

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

```release-note
[mariadb-operator] Add a post-delete hook job to clean up PVCs left after Helm release deletion.
```

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

* **New Features**
* Persistent storage volumes are now automatically cleaned up when the
MySQL application is deleted, preventing orphaned storage resources from
accumulating in your cluster.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:14:01 +01:00
Andrei Kvapil
67a8fa91e8 [seaweedfs] Allow users to discover their buckets (#1528)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does

This PR enables building of `seaweedfs` image.
Also backports patch from upstream
https://github.com/seaweedfs/seaweedfs/pull/7335

### Release note

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

```release-note
[seaweedfs] Allow users to discover their buckets
```

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

* **Bug Fixes**
* S3 signature handling adjusted so signature verification focuses on
authentication; permission checks are evaluated afterward.

* **Chores**
* Build process now discovers and uses remote release versions
dynamically.
* Introduced an optimized multi-stage container build with improved
tagging and registry caching.
* Added configurable image settings (global image name and image tag)
for deployment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 18:13:31 +01:00
Timofei Larkin
5703e16a80 [monitoring] add settings alert for slack (#1545)
**What this PR does**
This PR adds configuration for sending alerts from Alerta to Slack.

**Key changes**
Added Slack integration configuration in Alerta settings.
2025-10-31 18:12:37 +01:00
Andrei Kvapil
297ddf853b [Backport release-0.37] [kamaji] Respect 3rd party labels (#1534)
# Description
Backport of #1531 to `release-0.37`.
2025-10-21 15:05:55 +02:00
Timofei Larkin
62f80f85b4 [kamaji] Respect 3rd party labels
The Kamaji controller overwrites labels on many of the resources it owns
(clastix/kamaji#991). This change applies PR clastix/kamaji#992 to
Cozystack's build of Kamaji, so the lineage webhook doesn't fight the
Kamaji controller, causing a non-stop reconciliation loop.

```release-note
[kamaji] Do not clobber third party labels on resources controlled by
Kamaji.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit b858745cdd)
2025-10-17 15:49:14 +00:00
Timofei Larkin
a5cdaddbb4 [lineage] Check for nil chart in HelmRelease (#1525)
## What this PR does

Some HelmReleases use `chartRef` instead of `chart`. If the lineage
webhook finds such a HelmRelease, a nil pointer dereference happens.
This patch adds a nil check to guard against this.

### Release note

```release-note
[lineage] Add a nil check to guard against HelmReleases with a nil
.spec.chart field when traversing the ownership tree.
```

(cherry picked from commit d57f9acc7a)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-16 17:19:19 +03:00
Timofei Larkin
0e4fe7a33e [dashboard] Show service LB IP (#1524)
Fix an incorrect JSON path that prevented Service LoadBalancer IPs from
rendering in the table view.

(cherry picked from commit 466f0fed52)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-16 17:19:13 +03:00
Timofei Larkin
ee95353342 [apps] Make VM service user facing (#1523)
(cherry picked from commit c1edc5d711)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-16 17:19:04 +03:00
Andrei Kvapil
9506c58926 Release v0.37.2 (#1520)
This PR prepares the release `v0.37.2`.
2025-10-15 14:08:07 +02:00
cozystack-bot
ffcc55c588 Prepare release v0.37.2
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-15 10:56:27 +00:00
Andrei Kvapil
cbf67cd30e [Backport release-0.37] [platform] Better migration for 0.36.2->0.37.2+ (#1522)
# Description
Backport of #1521 to `release-0.37`.
2025-10-15 12:50:36 +02:00
Timofei Larkin
1b46ff3f6b [platform] Better migration for 0.36.2->0.37.2+
For users upgrading from 0.36.2 directly to 0.37.2+, where the
lineage-controller-webhook is broken out of the Cozystack controller
into a separate daemonset, the existing migration script of 0.36->0.37.0
is insufficient. This patch ensures the presence of the new version of
the lineage webhook and fixes a bug in the migration script where the
readiness of the webhook was not appropriately verified.

```release-note
[platform] Improved migration script when skipping versions 0.37.0 and
0.37.1 during upgrades.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
(cherry picked from commit bf1ece5f7c)
2025-10-15 10:50:02 +00:00
Timofei Larkin
674b3963a7 [lineage] Separate webhook from cozy controller (#1515)
## What this PR does

The lineage-controller-webhook makes a lot of outgoing API calls for
every event it handles, contributing to a high API server latency,
increasing the number of in-flight requests and generally degrading
performance. This patch remedies this by separating the lineage
component from the cozystack-controller and deploying it as a separate
component on all control-plane nodes. Additionally, a new internal label
is introduced to track if a resource has already been handled by the
webhook. This label is used to exclude such resources from
consideration. Addresses #1513.

### Release note

```release-note
[lineage] Break webhook out into a separate daemonset. Reduce
unnecessary webhook calls by marking handled resources and excluding
them from consideration by the webhook's object selector.
```

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

- **New Features**
- Standalone Lineage Controller Webhook deployed as its own DaemonSet
with a dedicated Helm chart and image build targets.
  - Dedicated TLS provisioning for the webhook via chart-managed certs.

- **Changes**
  - Main controller no longer hosts webhook endpoints or certificates.
- Webhook now excludes already-managed resources to reduce unnecessary
invocations.
  - Platform bundles updated to include the new webhook release.

- **Documentation**
  - Changelog updated to reflect the separation and optimization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-15 12:49:02 +02:00
Andrei Kvapil
f71f914fe6 [Backport release-0.37] [api] Fix listing tenantnamespaces for non-oidc users (#1519)
# Description
Backport of #1517 to `release-0.37`.
2025-10-15 10:10:46 +02:00
Andrei Kvapil
a09ed799e9 [api] Fix listing tenantnamespaces for non-oidc users
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 671e13df70)
2025-10-15 08:10:29 +00:00
Andrei Kvapil
8e35d6ae4e Release v0.37.1 (#1512)
This PR prepares the release `v0.37.1`.
2025-10-13 16:04:33 +02:00
cozystack-bot
2f8c6b72fe Prepare release v0.37.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-13 13:29:01 +00:00
Andrei Kvapil
5a679e12ad [api] Fix RBAC for listing of TenantNamespaces and handle system:masters (#1511)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does

Fix regression introduced by
https://github.com/cozystack/cozystack/pull/1507

### Release note

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

```release-note
[api] Fix RBAC for listing of TenantNamespaces and handle system:masters
```

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

## Summary by CodeRabbit

- New Features
- System-wide administrators now see all tenant namespaces without
filtering.
- Expanded read access for role bindings to improve visibility of access
configurations.

- Bug Fixes
- Resolved cases where some authorized admins could not view all tenant
namespaces due to RBAC filtering.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 15:21:05 +02:00
Andrei Kvapil
0176ba5e95 [dashboard] Fix logout (#1510)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[dashboard] Fix logout
```

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

## Summary by CodeRabbit

* **New Features**
* Enhanced OIDC logout flow: backend logout is now supported, improving
reliability of signing out across services.
* Whitelisted the identity provider domain to enable seamless redirects
during authentication and logout journeys.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 15:02:47 +02:00
Andrei Kvapil
b474c07c80 Add addtional check to wait for lineage-webhook (#1506)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[]
```

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

## Summary by CodeRabbit

* **Chores**
* Added a timeout-based step that repeatedly attempts server-side
dry-run creation of a Kubernetes Service (headless) between controller
upgrade and subsequent waits.
* Inserts this validation step without altering existing flow or other
behaviors.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 15:02:35 +02:00
Andrei Kvapil
5dbdd0eafa [api] Efficient listing of TenantNamespaces (#1507)
## What this PR does

The Cozystack API server lists TenantNamespaces by running a
SubjectAccessReview against every single requested namespace to see if
the user can create a WorkloadMonitor there. Will this is robust in
terms of permissions, delegating the authorization decision to the k8s
API, this is incredibly inefficient and has caused high latency to the
API. This patch simplifies the logic by instead getting the user's
groups and checking if the namespace contains a rolebinding for that
group. That way listing TenantNamespaces is reduced to a list call to
the k8s API for namespaces and another list call for rolebindings across
all namespaces, while authorization is done on the Cozystack API server
instead of making further calls to the k8s API.

### Release note

```release-note
[api] Optimize listing of TenantNamespaces, fixes a bug causing very
high latency to the k8s API.
```

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

- Bug Fixes
- TenantNamespace visibility now consistently reflects RBAC role
bindings. Cluster administrators see all namespaces; users only see
namespaces they’re permitted to access.

- Refactor
- Access evaluation simplified to rely on role/rolebinding membership,
removing per-namespace authorization calls and improving listing
performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 15:02:29 +02:00
Andrei Kvapil
21715c02bc The Cozystack Kubernetes tests are now POSIX-compatible (#1509)
This patch replaces bash-specific [[ ... ]] expressions in the
run_kubernetes_test function with POSIX-compliant case and test
constructs. It ensures that the Kubernetes version on each worker node
is verified correctly and that required components (CoreDNS, Cilium,
ingress-nginx, vsnap-crd) are ready before proceeding. Now the tests
work reliably even when executed with /bin/sh, such as in Bats.

```release-note
[tests] Make Kubernetes tests POSIX-compliant and more reliable:
verify worker node versions and ensure required releases (CoreDNS,
Cilium, ingress-nginx, vsnap-crd) are installed and ready.
```

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

## What this PR does


### Release note

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

```release-note
[]
```

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved Kubernetes version detection to correctly handle 1.32
variants.
* Made node readiness checks more reliable to reduce false failures
during runs.

* **Refactor**
* Streamlined version matching logic for clearer, more predictable
behavior across releases.

* **Style**
  * Minor formatting cleanups with no functional impact.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 15:02:18 +02:00
151 changed files with 2867 additions and 2383 deletions

View File

@@ -15,6 +15,7 @@ build: build-deps
make -C packages/extra/monitoring image
make -C packages/system/cozystack-api image
make -C packages/system/cozystack-controller image
make -C packages/system/lineage-controller-webhook image
make -C packages/system/cilium image
make -C packages/system/kubeovn image
make -C packages/system/kubeovn-webhook image

View File

@@ -39,7 +39,6 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/dashboard"
lcw "github.com/cozystack/cozystack/internal/lineagecontrollerwebhook"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
@@ -70,6 +69,7 @@ func main() {
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var reconcileDeployment bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -89,6 +89,8 @@ func main() {
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.StringVar(&cozystackVersion, "cozystack-version", "unknown",
"Version of Cozystack")
flag.BoolVar(&reconcileDeployment, "reconcile-deployment", false,
"If set, the Cozystack API server is assumed to run as a Deployment, else as a DaemonSet.")
opts := zap.Options{
Development: false,
}
@@ -214,25 +216,25 @@ func main() {
os.Exit(1)
}
cozyAPIKind := "DaemonSet"
if reconcileDeployment {
cozyAPIKind = "Deployment"
}
if err = (&controller.CozystackResourceDefinitionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
CozystackAPIKind: cozyAPIKind,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CozystackResourceDefinitionReconciler")
os.Exit(1)
}
// special one that's both a webhook and a reconciler
lineageControllerWebhook := &lcw.LineageControllerWebhook{
dashboardManager := &dashboard.Manager{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err := lineageControllerWebhook.SetupWithManagerAsController(mgr); err != nil {
setupLog.Error(err, "unable to setup controller", "controller", "LineageController")
os.Exit(1)
}
if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil {
setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook")
if err = dashboardManager.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DashboardReconciler")
os.Exit(1)
}
@@ -261,7 +263,9 @@ func main() {
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
ctx := ctrl.SetupSignalHandler()
dashboardManager.InitializeStaticResources(ctx)
if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}

View File

@@ -0,0 +1,179 @@
/*
Copyright 2025.
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 (
"crypto/tls"
"flag"
"os"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
lcw "github.com/cozystack/cozystack/internal/lineagecontrollerwebhook"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(cozystackiov1alpha1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
opts := zap.Options{
Development: false,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
// - https://github.com/advisories/GHSA-4374-p667-p6c8
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
}
if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
}
webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
})
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
// More info:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server
// - https://book.kubebuilder.io/reference/metrics.html
metricsServerOptions := metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
}
if secureMetrics {
// FilterProvider is used to protect the metrics endpoint with authn/authz.
// These configurations ensure that only authorized users and service accounts
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
// TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically
// generate self-signed certificates for the metrics server. While convenient for development and testing,
// this setup is not recommended for production.
}
// Configure rate limiting for the Kubernetes client
config := ctrl.GetConfigOrDie()
config.QPS = 50.0 // Increased from default 5.0
config.Burst = 100 // Increased from default 10
mgr, err := ctrl.NewManager(config, ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "8796f12d.cozystack.io",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
lineageControllerWebhook := &lcw.LineageControllerWebhook{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err := lineageControllerWebhook.SetupWithManagerAsController(mgr); err != nil {
setupLog.Error(err, "unable to setup controller", "controller", "LineageController")
os.Exit(1)
}
if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil {
setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

View File

@@ -0,0 +1,3 @@
# Changes after v0.37.0
* [lineage] Break webhook out into a separate daemonset. Reduce unnecessary webhook calls by marking handled resources and excluding them from consideration by the webhook's object selector (@lllamnyp in #1515).

View File

@@ -64,19 +64,19 @@ spec:
EOF
# Wait for the tenant-test namespace to be active
kubectl wait namespace tenant-test --timeout=20s --for=jsonpath='{.status.phase}'=Active
# Wait for the Kamaji control plane to be created (retry for up to 10 seconds)
timeout 10 sh -ec 'until kubectl get kamajicontrolplane -n tenant-test kubernetes-'"${test_name}"'; do sleep 1; done'
# Wait for the tenant control plane to be fully created (timeout after 4 minutes)
kubectl wait --for=condition=TenantControlPlaneCreated kamajicontrolplane -n tenant-test kubernetes-${test_name} --timeout=4m
# Wait for Kubernetes resources to be ready (timeout after 2 minutes)
kubectl wait tcp -n tenant-test kubernetes-${test_name} --timeout=2m --for=jsonpath='{.status.kubernetesResources.version.status}'=Ready
# Wait for all required deployments to be available (timeout after 4 minutes)
kubectl wait deploy --timeout=4m --for=condition=available -n tenant-test kubernetes-${test_name} kubernetes-${test_name}-cluster-autoscaler kubernetes-${test_name}-kccm kubernetes-${test_name}-kcsi-controller
# Wait for the machine deployment to scale to 2 replicas (timeout after 1 minute)
kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=1m --for=jsonpath='{.status.replicas}'=2
# Get the admin kubeconfig and save it to a file
@@ -87,14 +87,14 @@ EOF
# Set up port forwarding to the Kubernetes API server for a 200 second timeout
bash -c 'timeout 200s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
bash -c 'timeout 300s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
# Verify the Kubernetes version matches what we expect (retry for up to 20 seconds)
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
# Wait for the nodes to be ready (timeout after 2 minutes)
timeout 2m bash -c '
timeout 3m bash -c '
until [ "$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
sleep 3
sleep 2
done
'
# Verify the nodes are ready
@@ -105,9 +105,11 @@ EOF
versions=$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
node_ok=true
if [[ "$k8s_version" == v1.32* ]]; then
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
fi
case "$k8s_version" in
v1.32*)
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
;;
esac
for v in $versions; do
case "$k8s_version" in
@@ -134,7 +136,7 @@ EOF
esac
done
if ! $node_ok; then
if [ "$node_ok" != true ]; then
echo "Kubelet versions did not match expected ${k8s_version}" >&2
exit 1
fi

View File

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

View File

@@ -132,7 +132,6 @@ machine:
- usermode_helper=disabled
- name: zfs
- name: spl
- name: lldpd
registries:
mirrors:
docker.io:

View File

@@ -9,6 +9,7 @@
@test "Test OpenAPI v3 endpoint" {
kubectl get -v7 --raw '/openapi/v3/apis/apps.cozystack.io/v1alpha1' > /dev/null
kubectl get -v7 --raw '/openapi/v3/apis/core.cozystack.io/v1alpha1' > /dev/null
}
@test "Test OpenAPI v2 endpoint (protobuf)" {

View File

@@ -5,28 +5,21 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"sort"
"slices"
"sync"
"time"
"github.com/cozystack/cozystack/internal/controller/dashboard"
"github.com/cozystack/cozystack/internal/shared/crdmem"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -40,128 +33,20 @@ type CozystackResourceDefinitionReconciler struct {
lastEvent time.Time
lastHandled time.Time
mem *crdmem.Memory
// Track static resources initialization
staticResourcesInitialized bool
staticResourcesMutex sync.Mutex
CozystackAPIKind string
}
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
crd := &cozyv1alpha1.CozystackResourceDefinition{}
err := r.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err == nil {
if r.mem != nil {
r.mem.Upsert(crd)
}
mgr := dashboard.NewManager(
r.Client,
r.Scheme,
dashboard.WithCRDListFunc(func(c context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if r.mem != nil {
return r.mem.ListFromCacheOrAPI(c, r.Client)
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := r.Client.List(c, &list); err != nil {
return nil, err
}
return list.Items, nil
}),
)
if res, derr := mgr.EnsureForCRD(ctx, crd); derr != nil || res.Requeue || res.RequeueAfter > 0 {
return res, derr
}
// After processing CRD, perform cleanup of orphaned resources
// This should be done after cache warming to ensure all current resources are known
if cleanupErr := mgr.CleanupOrphanedResources(ctx); cleanupErr != nil {
logger.Error(cleanupErr, "Failed to cleanup orphaned dashboard resources")
// Don't fail the reconciliation, just log the error
}
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return ctrl.Result{}, nil
}
// Handle error cases (err is guaranteed to be non-nil here)
if !apierrors.IsNotFound(err) {
return ctrl.Result{}, err
}
// If resource is not found, clean up from memory
if r.mem != nil {
r.mem.Delete(req.Name)
}
if req.Namespace == "cozy-system" && req.Name == "cozystack-api" {
return r.debouncedRestart(ctx, logger)
}
return ctrl.Result{}, nil
}
// initializeStaticResourcesOnce ensures static resources are created only once
func (r *CozystackResourceDefinitionReconciler) initializeStaticResourcesOnce(ctx context.Context) error {
r.staticResourcesMutex.Lock()
defer r.staticResourcesMutex.Unlock()
if r.staticResourcesInitialized {
return nil // Already initialized
}
// Create dashboard manager and initialize static resources
mgr := dashboard.NewManager(
r.Client,
r.Scheme,
dashboard.WithCRDListFunc(func(c context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if r.mem != nil {
return r.mem.ListFromCacheOrAPI(c, r.Client)
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := r.Client.List(c, &list); err != nil {
return nil, err
}
return list.Items, nil
}),
)
if err := mgr.InitializeStaticResources(ctx); err != nil {
return err
}
r.staticResourcesInitialized = true
log.FromContext(ctx).Info("Static dashboard resources initialized successfully")
return nil
return r.debouncedRestart(ctx)
}
func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
}
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
// Initialize static resources once during controller startup using manager.Runnable
if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
if err := r.initializeStaticResourcesOnce(ctx); err != nil {
log.FromContext(ctx).Error(err, "Failed to initialize static resources")
return err
}
return nil
})); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
Named("cozystackresource-controller").
For(&cozyv1alpha1.CozystackResourceDefinition{}, builder.WithPredicates()).
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
@@ -176,9 +61,6 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage
}}
}),
).
WithOptions(controller.Options{
MaxConcurrentReconciles: 5, // Allow more concurrent reconciles with proper rate limiting
}).
Complete(r)
}
@@ -188,22 +70,18 @@ type crdHashView struct {
}
func (r *CozystackResourceDefinitionReconciler) computeConfigHash(ctx context.Context) (string, error) {
var items []cozyv1alpha1.CozystackResourceDefinition
if r.mem != nil {
list, err := r.mem.ListFromCacheOrAPI(ctx, r.Client)
if err != nil {
return "", err
}
items = list
list := &cozyv1alpha1.CozystackResourceDefinitionList{}
if err := r.List(ctx, list); err != nil {
return "", err
}
sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name })
slices.SortFunc(list.Items, sortCozyRDs)
views := make([]crdHashView, 0, len(items))
for i := range items {
views := make([]crdHashView, 0, len(list.Items))
for i := range list.Items {
views = append(views, crdHashView{
Name: items[i].Name,
Spec: items[i].Spec,
Name: list.Items[i].Name,
Spec: list.Items[i].Spec,
})
}
b, err := json.Marshal(views)
@@ -214,7 +92,9 @@ func (r *CozystackResourceDefinitionReconciler) computeConfigHash(ctx context.Co
return hex.EncodeToString(sum[:]), nil
}
func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Context, logger logr.Logger) (ctrl.Result, error) {
func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Context) (ctrl.Result, error) {
logger := log.FromContext(ctx)
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
@@ -239,15 +119,12 @@ func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Con
return ctrl.Result{}, err
}
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"}, deploy); err != nil {
tpl, obj, patch, err := r.getWorkload(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"})
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if deploy.Spec.Template.Annotations == nil {
deploy.Spec.Template.Annotations = map[string]string{}
}
oldHash := deploy.Spec.Template.Annotations["cozystack.io/config-hash"]
oldHash := tpl.Annotations["cozystack.io/config-hash"]
if oldHash == newHash && oldHash != "" {
r.mu.Lock()
@@ -257,10 +134,9 @@ func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Con
return ctrl.Result{}, nil
}
patch := client.MergeFrom(deploy.DeepCopy())
deploy.Spec.Template.Annotations["cozystack.io/config-hash"] = newHash
tpl.Annotations["cozystack.io/config-hash"] = newHash
if err := r.Patch(ctx, deploy, patch); err != nil {
if err := r.Patch(ctx, obj, patch); err != nil {
return ctrl.Result{}, err
}
@@ -272,3 +148,40 @@ func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Con
"old", oldHash, "new", newHash)
return ctrl.Result{}, nil
}
func (r *CozystackResourceDefinitionReconciler) getWorkload(
ctx context.Context,
key types.NamespacedName,
) (tpl *corev1.PodTemplateSpec, obj client.Object, patch client.Patch, err error) {
if r.CozystackAPIKind == "Deployment" {
dep := &appsv1.Deployment{}
if err := r.Get(ctx, key, dep); err != nil {
return nil, nil, nil, err
}
obj = dep
tpl = &dep.Spec.Template
patch = client.MergeFrom(dep.DeepCopy())
} else {
ds := &appsv1.DaemonSet{}
if err := r.Get(ctx, key, ds); err != nil {
return nil, nil, nil, err
}
obj = ds
tpl = &ds.Spec.Template
patch = client.MergeFrom(ds.DeepCopy())
}
if tpl.Annotations == nil {
tpl.Annotations = make(map[string]string)
}
return tpl, obj, patch, nil
}
func sortCozyRDs(a, b cozyv1alpha1.CozystackResourceDefinition) int {
if a.Name == b.Name {
return 0
}
if a.Name < b.Name {
return -1
}
return 1
}

View File

@@ -58,8 +58,8 @@ func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.Cozyst
"breadcrumbItems": items,
}
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -30,10 +30,6 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
id := fmt.Sprintf("stock-namespace-/%s/%s/%s", g, v, plural)
// Badge content & color derived from kind
badgeText := initialsFromKind(kind) // e.g., "VirtualMachine" -> "VM", "Bucket" -> "B"
badgeColor := hexColorForKind(kind) // deterministic, dark enough for white text
obj := &dashv1alpha1.CustomColumnsOverride{}
obj.SetName(name)
@@ -62,25 +58,11 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
},
"children": []any{
map[string]any{
"type": "antdText",
"type": "ResourceBadge",
"data": map[string]any{
"id": "header-badge",
"text": badgeText,
"title": strings.ToLower(kind), // optional tooltip
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
"value": kind,
// abbreviation auto-generated by ResourceBadge from value
},
},
map[string]any{
@@ -145,8 +127,8 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
},
}
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

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

View File

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

View File

@@ -56,8 +56,8 @@ func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.client, cfp, func() error {
if err := controllerutil.SetOwnerReference(crd, cfp, m.scheme); err != nil {
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, cfp, func() error {
if err := controllerutil.SetOwnerReference(crd, cfp, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -53,7 +53,6 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
Kind: kind,
Plural: plural,
Title: strings.ToLower(plural),
Size: BadgeSizeLarge,
}
spec := createUnifiedFactory(config, tabs, []any{resourceFetch})
@@ -61,8 +60,8 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
obj := &dashv1alpha1.Factory{}
obj.SetName(factoryName)
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
@@ -115,7 +114,7 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
"gap": float64(6),
},
"children": []any{
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
antdLink("namespace-link",
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
@@ -222,7 +221,7 @@ func workloadsTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -247,7 +246,7 @@ func servicesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -273,7 +272,7 @@ func ingressesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-networking.k8s.io.v1.ingresses",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -294,12 +293,12 @@ func secretsTab(kind string) map[string]any {
"type": "EnrichedTable",
"data": map[string]any{
"id": "secrets-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -324,6 +323,7 @@ func yamlTab(plural string) map[string]any {
"type": "builtin",
"typeName": plural,
"prefillValuesRequestIndex": float64(0),
"readOnly": true,
"substractHeight": float64(400),
},
},

View File

@@ -1,12 +1,9 @@
package dashboard
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"regexp"
"sort"
"strings"
@@ -57,97 +54,6 @@ func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) stri
return k + "s"
}
// initialsFromKind splits CamelCase and returns the first letters in upper case.
// "VirtualMachine" -> "VM"; "Bucket" -> "B".
func initialsFromKind(kind string) string {
parts := splitCamel(kind)
if len(parts) == 0 {
return strings.ToUpper(kind)
}
var b strings.Builder
for _, p := range parts {
if p == "" {
continue
}
b.WriteString(strings.ToUpper(string(p[0])))
// Limit to 3 chars to keep the badge compact (VM, PVC, etc.)
if b.Len() >= 3 {
break
}
}
return b.String()
}
// hexColorForKind returns a dark, saturated color (hex) derived from a stable hash of the kind.
// We map the hash to an HSL hue; fix S/L for consistent readability with white text.
func hexColorForKind(kind string) string {
// Stable short hash (sha1 → bytes → hue)
sum := sha1.Sum([]byte(kind))
// Use first two bytes for hue [0..359]
hue := int(sum[0])<<8 | int(sum[1])
hue = hue % 360
// Fixed S/L chosen to contrast with white text:
// S = 80%, L = 35% (dark enough so #fff is readable)
r, g, b := hslToRGB(float64(hue), 0.80, 0.35)
return fmt.Sprintf("#%02x%02x%02x", r, g, b)
}
// hslToRGB converts HSL (0..360, 0..1, 0..1) to sRGB (0..255).
func hslToRGB(h float64, s float64, l float64) (uint8, uint8, uint8) {
c := (1 - absFloat(2*l-1)) * s
hp := h / 60.0
x := c * (1 - absFloat(modFloat(hp, 2)-1))
var r1, g1, b1 float64
switch {
case 0 <= hp && hp < 1:
r1, g1, b1 = c, x, 0
case 1 <= hp && hp < 2:
r1, g1, b1 = x, c, 0
case 2 <= hp && hp < 3:
r1, g1, b1 = 0, c, x
case 3 <= hp && hp < 4:
r1, g1, b1 = 0, x, c
case 4 <= hp && hp < 5:
r1, g1, b1 = x, 0, c
default:
r1, g1, b1 = c, 0, x
}
m := l - c/2
r := uint8(clamp01(r1+m) * 255.0)
g := uint8(clamp01(g1+m) * 255.0)
b := uint8(clamp01(b1+m) * 255.0)
return r, g, b
}
func absFloat(v float64) float64 {
if v < 0 {
return -v
}
return v
}
func modFloat(a, b float64) float64 {
return a - b*float64(int(a/b))
}
func clamp01(v float64) float64 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
// optional: tiny helper to expose the compact color hash (useful for debugging)
func shortHashHex(s string) string {
sum := sha1.Sum([]byte(s))
return hex.EncodeToString(sum[:4])
}
// ----------------------- Helpers (OpenAPI → values) -----------------------
// defaultOrZero returns the schema default if present; otherwise a reasonable zero value.
@@ -295,12 +201,6 @@ func normalizeJSON(v any) any {
}
}
var camelSplitter = regexp.MustCompile(`(?m)([A-Z]+[a-z0-9]*|[a-z0-9]+)`)
func splitCamel(s string) []string {
return camelSplitter.FindAllString(s, -1)
}
// --- helpers for schema inspection ---
func isScalarType(n map[string]any) bool {

View File

@@ -10,9 +10,12 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
managerpkg "sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -40,28 +43,51 @@ func AddToScheme(s *runtime.Scheme) error {
// Manager owns logic for creating/updating dashboard resources derived from CRDs.
// Its easy to extend: add new ensure* methods and wire them into EnsureForCRD.
type Manager struct {
client client.Client
scheme *runtime.Scheme
crdListFn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)
}
// Option pattern so callers can inject a custom lister.
type Option func(*Manager)
// WithCRDListFunc overrides how Manager lists all CozystackResourceDefinitions.
func WithCRDListFunc(fn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)) Option {
return func(m *Manager) { m.crdListFn = fn }
client.Client
Scheme *runtime.Scheme
}
// NewManager constructs a dashboard Manager.
func NewManager(c client.Client, scheme *runtime.Scheme, opts ...Option) *Manager {
m := &Manager{client: c, scheme: scheme}
for _, o := range opts {
o(m)
}
func NewManager(c client.Client, scheme *runtime.Scheme) *Manager {
m := &Manager{Client: c, Scheme: scheme}
return m
}
func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
if err := ctrl.NewControllerManagedBy(mgr).
Named("dashboard-reconciler").
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(m); err != nil {
return err
}
return mgr.Add(managerpkg.RunnableFunc(func(ctx context.Context) error {
if !mgr.GetCache().WaitForCacheSync(ctx) {
return fmt.Errorf("dashboard static resources cache sync failed")
}
return m.ensureStaticResources(ctx)
}))
}
func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crd := &cozyv1alpha1.CozystackResourceDefinition{}
err := m.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err != nil {
if apierrors.IsNotFound(err) {
if err := m.CleanupOrphanedResources(ctx); err != nil {
l.Error(err, "Failed to cleanup orphaned dashboard resources")
}
return ctrl.Result{}, nil // no point in requeuing here
}
return ctrl.Result{}, err
}
return m.EnsureForCRD(ctx, crd)
}
// EnsureForCRD is the single entry-point used by the controller.
// Add more ensure* calls here as you implement support for other resources:
//
@@ -171,21 +197,11 @@ func (m *Manager) getStaticResourceSelector() client.MatchingLabels {
// CleanupOrphanedResources removes dashboard resources that are no longer needed
// This should be called after cache warming to ensure all current resources are known
func (m *Manager) CleanupOrphanedResources(ctx context.Context) error {
// Get all current CRDs to determine which resources should exist
var allCRDs []cozyv1alpha1.CozystackResourceDefinition
if m.crdListFn != nil {
s, err := m.crdListFn(ctx)
if err != nil {
return err
}
allCRDs = s
} else {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
allCRDs = crdList.Items
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
allCRDs := crdList.Items
// Build a set of expected resource names for each type
expectedResources := m.buildExpectedResourceSet(allCRDs)
@@ -349,7 +365,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
}
// List with dashboard labels
if err := m.client.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
if err := m.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
return err
}
@@ -358,7 +374,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomColumnsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -369,7 +385,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomFormsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -380,7 +396,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomFormsPrefillList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -391,7 +407,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.MarketplacePanelList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -402,7 +418,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.SidebarList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -413,7 +429,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.TableUriMappingList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -426,7 +442,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Breadcrumb resource", "name", item.Name)
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -438,7 +454,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Factory resource", "name", item.Name)
if err := m.client.Delete(ctx, &item); err != nil {
if err := m.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}

View File

@@ -24,14 +24,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
// If dashboard is not set, delete the panel if it exists.
if crd.Spec.Dashboard == nil {
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name)
@@ -40,14 +40,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
// Skip module and tenant resources (they don't need MarketplacePanel)
if crd.Spec.Dashboard.Module || crd.Spec.Application.Kind == "Tenant" {
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because resource is a module", "name", mp.Name)
@@ -86,8 +86,8 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.client, mp, func() error {
if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil {
_, 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

View File

@@ -33,19 +33,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
// 1) Fetch all CRDs
var all []cozyv1alpha1.CozystackResourceDefinition
if m.crdListFn != nil {
s, err := m.crdListFn(ctx)
if err != nil {
return err
}
all = s
} else {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
all = crdList.Items
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
all = crdList.Items
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
type item struct {
@@ -251,7 +243,7 @@ func (m *Manager) upsertMultipleSidebars(
obj := &dashv1alpha1.Sidebar{}
obj.SetName(id)
if _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
// Only set owner reference for dynamic sidebars (stock-project-factory-{kind}-details)
// Static sidebars (stock-instance-*, stock-project-*) should not have owner references
if strings.HasPrefix(id, "stock-project-factory-") && strings.HasSuffix(id, "-details") {
@@ -260,7 +252,7 @@ func (m *Manager) upsertMultipleSidebars(
lowerKind := strings.ToLower(kind)
expectedID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
if id == expectedID {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -122,7 +122,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das
}
}
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" {
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecrets" {
data["additionalPrinterColumnsTrimLengths"] = []any{
map[string]any{
"key": "Name",
@@ -531,7 +531,6 @@ func createBreadcrumbItem(key, label string, link ...string) map[string]any {
// createCustomColumn creates a custom column with factory type and badge
func createCustomColumn(name, kind, plural, href string) map[string]any {
badge := createUnifiedBadgeFromKind("header-badge", kind, plural, BadgeSizeMedium)
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
return map[string]any{
@@ -541,8 +540,18 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{badge, link},
"type": "antdFlex",
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": map[string]any{
"id": "header-badge",
"value": kind,
// abbreviation auto-generated by ResourceBadge from value
},
},
link,
},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -554,16 +563,16 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
}
// createCustomColumnWithBadge creates a custom column with a specific badge
func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string) map[string]any {
config := BadgeConfig{
Text: badgeText,
Color: badgeColor,
Title: title,
Size: BadgeSizeMedium,
}
badge := createUnifiedBadge("header-badge", config)
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithBadge(name, badgeValue, href string) map[string]any {
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
}
return map[string]any{
"name": name,
"type": "factory",
@@ -571,8 +580,14 @@ func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{badge, link},
"type": "antdFlex",
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
},
link,
},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -583,17 +598,22 @@ func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string
}
}
// createCustomColumnWithSpecificColor creates a custom column with a specific color
func createCustomColumnWithSpecificColor(name, kind, title, color, href string) map[string]any {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: color,
Title: title,
Size: BadgeSizeMedium,
}
badge := createUnifiedBadge("header-badge", config)
// createCustomColumnWithSpecificColor creates a custom column with a specific kind and optional color
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
func createCustomColumnWithSpecificColor(name, kind, color, href string) map[string]any {
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
badgeData := map[string]any{
"id": "header-badge",
"value": kind,
}
// Add custom color if specified
if color != "" {
badgeData["style"] = map[string]any{
"backgroundColor": color,
}
}
return map[string]any{
"name": name,
"type": "factory",
@@ -602,8 +622,14 @@ func createCustomColumnWithSpecificColor(name, kind, title, color, href string)
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{badge, link},
"type": "antdFlex",
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
},
link,
},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -668,7 +694,7 @@ func createTimestampColumn(name, jsonPath string) map[string]any {
// createFactoryHeader creates a header for factory resources
func createFactoryHeader(kind, plural string) map[string]any {
lowerKind := strings.ToLower(kind)
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind, plural, BadgeSizeLarge)
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind)
nameText := parsedText(lowerKind+"-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": float64(20),
@@ -718,13 +744,26 @@ func createFactorySpec(key string, sidebarTags []any, urlsToFetch []any, header
}
// createCustomColumnWithJsonPath creates a column with a custom badge and link using jsonPath
func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
// badgeValue should be the kind in PascalCase (e.g., "Service", "VirtualMachine")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, linkHref string) map[string]any {
// Determine link ID based on jsonPath
linkId := "name-link"
if jsonPath == ".metadata.namespace" {
linkId = "namespace-link"
}
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
}
// Add custom color if specified
if badgeColor != "" {
badgeData["style"] = map[string]any{
"backgroundColor": badgeColor,
}
}
return map[string]any{
"name": name,
"type": "factory",
@@ -741,26 +780,8 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badge
},
"children": []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"text": badgeText,
"title": badgeTitle,
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
"type": "ResourceBadge",
"data": badgeData,
},
map[string]any{
"type": "antdLink",
@@ -778,7 +799,20 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badge
}
// createCustomColumnWithoutJsonPath creates a column with a custom badge and link without jsonPath
func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
// badgeValue should be the kind in PascalCase (e.g., "Node", "Pod")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithoutJsonPath(name, badgeValue, badgeColor, linkHref string) map[string]any {
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
}
// Add custom color if specified
if badgeColor != "" {
badgeData["style"] = map[string]any{
"backgroundColor": badgeColor,
}
}
return map[string]any{
"name": name,
"type": "factory",
@@ -794,26 +828,8 @@ func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor,
},
"children": []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"text": badgeText,
"title": badgeTitle,
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
"type": "ResourceBadge",
"data": badgeData,
},
map[string]any{
"type": "antdLink",
@@ -1030,6 +1046,15 @@ func createConverterBytesColumn(name, jsonPath string) map[string]any {
}
}
// createFlatMapColumn creates a flatMap column that expands a map into separate rows
func createFlatMapColumn(name, jsonPath string) map[string]any {
return map[string]any{
"name": name,
"type": "flatMap",
"jsonPath": jsonPath,
}
}
// ---------------- Factory UI helper functions ----------------
// labelsEditor creates a Labels editor component

View File

@@ -32,7 +32,7 @@ func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) e
// Add dashboard labels to static resources
m.addDashboardLabels(resource, nil, ResourceTypeStatic)
_, err := controllerutil.CreateOrUpdate(ctx, m.client, resource, func() error {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, resource, func() error {
// For static resources, we don't need to set owner references
// as they are meant to be persistent across CRD changes
// Copy Spec from the original object to the live object

View File

@@ -132,7 +132,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
return []*dashboardv1alpha1.CustomColumnsOverride{
// Factory details v1 services
createCustomColumnsOverride("factory-details-v1.services", []any{
createCustomColumnWithSpecificColor("Name", "Service", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithSpecificColor("Name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("ClusterIP", ".spec.clusterIP"),
createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
@@ -140,15 +140,15 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace v1 services
createCustomColumnsOverride("stock-namespace-/v1/services", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("ClusterIP", ".spec.clusterIP"),
createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"),
createStringColumn("LoadbalancerIP", ".status.loadBalancer.ingress[0].ip"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace core cozystack io v1alpha1 tenantmodules
createCustomColumnsOverride("stock-namespace-/core.cozystack.io/v1alpha1/tenantmodules", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "M", "module", getColorForType("module"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Module", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createReadyColumn(),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
createStringColumn("Version", ".status.version"),
@@ -164,7 +164,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory details v1alpha1 cozystack io workloadmonitors
createCustomColumnsOverride("factory-details-v1alpha1.cozystack.io.workloadmonitors", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "W", "workloadmonitor", getColorForType("workloadmonitor"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "WorkloadMonitor", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("TYPE", ".spec.type"),
createStringColumn("VERSION", ".spec.version"),
createStringColumn("REPLICAS", ".spec.replicas"),
@@ -173,18 +173,19 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createStringColumn("OBSERVED", ".status.observedReplicas"),
}),
// Factory details v1alpha1 core cozystack io tenantsecretstables
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", getColorForType("secret"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Key", ".data.key"),
createSecretBase64Column("Value", ".data.value"),
// Factory details v1alpha1 core cozystack io tenantsecrets
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createFlatMapColumn("Data", ".data"),
createStringColumn("Key", "_flatMapData_Key"),
createSecretBase64Column("Value", "._flatMapData_Value"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Factory ingress details rules
createCustomColumnsOverride("factory-kube-ingress-details-rules", []any{
createStringColumn("Host", ".host"),
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
createStringColumn("Port", ".http.paths[0].backend.service.port.number"),
createStringColumn("Path", ".http.paths[0].path"),
}),
@@ -250,7 +251,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory details networking k8s io v1 ingresses
createCustomColumnsOverride("factory-details-networking.k8s.io.v1.ingresses", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Hosts", ".spec.rules[*].host"),
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
@@ -259,7 +260,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace networking k8s io v1 ingresses
createCustomColumnsOverride("stock-namespace-/networking.k8s.io/v1/ingresses", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Hosts", ".spec.rules[*].host"),
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
@@ -268,34 +269,34 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 configmaps
createCustomColumnsOverride("stock-cluster-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace v1 configmaps
createCustomColumnsOverride("stock-namespace-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Cluster v1 configmaps
createCustomColumnsOverride("cluster-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock cluster v1 nodes
createCustomColumnsOverride("stock-cluster-/v1/nodes", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createSimpleStatusColumn("Status", "node-status"),
}),
// Factory node details v1 pods
createCustomColumnsOverride("factory-node-details-v1.pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -304,8 +305,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory v1 pods
createCustomColumnsOverride("factory-v1.pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -314,9 +315,9 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 pods
createCustomColumnsOverride("stock-cluster-/v1/pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -325,8 +326,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace v1 pods
createCustomColumnsOverride("stock-namespace-/v1/pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -335,15 +336,15 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 secrets
createCustomColumnsOverride("stock-cluster-/v1/secrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createStringColumn("Type", ".type"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace v1 secrets
createCustomColumnsOverride("stock-namespace-/v1/secrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Type", ".type"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
@@ -360,7 +361,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster core cozystack io v1alpha1 tenantnamespaces
createCustomColumnsOverride("stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "TN", "tenantnamespace", getColorForType("tenantnamespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "TenantNamespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
}
@@ -496,7 +497,6 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
Kind: "Namespace",
Plural: "namespaces",
Title: "namespace",
Size: BadgeSizeLarge,
}
namespaceSpec := createUnifiedFactory(namespaceConfig, nil, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{5}"})
@@ -796,6 +796,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"substractHeight": float64(400),
"type": "builtin",
"typeName": "secrets",
"readOnly": true,
},
},
},
@@ -1055,7 +1056,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-kube-service-details-endpointslice",
"fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices",
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}",
},
"pathToItems": ".items[*].endpoints",
@@ -1201,7 +1202,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"gap": 6,
},
"children": []any{
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
antdLink("namespace-link",
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
@@ -1396,7 +1397,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloads",
"fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads",
"labelsSelector": map[string]any{
"labelSelector": map[string]any{
"workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}",
},
"pathToItems": []any{"items"},

View File

@@ -1,7 +1,5 @@
package dashboard
import "strings"
// ---------------- UI helpers (use float64 for numeric fields) ----------------
func contentCard(id string, style map[string]any, children []any) map[string]any {
@@ -200,10 +198,10 @@ func createBadge(id, text, color, title string) map[string]any {
// createBadgeFromKind creates a badge using the existing badge generation functions
func createBadgeFromKind(id, kind, title string) map[string]any {
return createUnifiedBadgeFromKind(id, kind, title, BadgeSizeMedium)
return createUnifiedBadgeFromKind(id, kind)
}
// createHeaderBadge creates a badge specifically for headers with consistent styling
func createHeaderBadge(id, kind, plural string) map[string]any {
return createUnifiedBadgeFromKind(id, kind, strings.ToLower(plural), BadgeSizeLarge)
return createUnifiedBadgeFromKind(id, kind)
}

View File

@@ -81,86 +81,47 @@ func isAlphanumeric(c byte) bool {
// BadgeConfig holds configuration for badge generation
type BadgeConfig struct {
Text string
Color string
Title string
Size BadgeSize
Kind string // Resource kind in PascalCase (e.g., "VirtualMachine") - used for value and auto-generation
Text string // Optional abbreviation override (if empty, ResourceBadge auto-generates from Kind)
Color string // Optional custom backgroundColor override
}
// BadgeSize represents the size of the badge
type BadgeSize int
const (
BadgeSizeSmall BadgeSize = iota
BadgeSizeMedium
BadgeSizeLarge
)
// generateBadgeConfig creates a BadgeConfig from kind and optional custom values
func generateBadgeConfig(kind string, customText, customColor, customTitle string) BadgeConfig {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: hexColorForKind(kind),
Title: strings.ToLower(kind),
Size: BadgeSizeMedium,
}
// Override with custom values if provided
if customText != "" {
config.Text = customText
}
if customColor != "" {
config.Color = customColor
}
if customTitle != "" {
config.Title = customTitle
}
return config
}
// createUnifiedBadge creates a badge using the unified BadgeConfig
// createUnifiedBadge creates a badge using the unified BadgeConfig with ResourceBadge component
func createUnifiedBadge(id string, config BadgeConfig) map[string]any {
fontSize := "15px"
if config.Size == BadgeSizeLarge {
fontSize = "20px"
} else if config.Size == BadgeSizeSmall {
fontSize = "12px"
data := map[string]any{
"id": id,
"value": config.Kind,
}
// Add abbreviation override if specified (otherwise ResourceBadge auto-generates from Kind)
if config.Text != "" {
data["abbreviation"] = config.Text
}
// Add custom color if specified
if config.Color != "" {
data["style"] = map[string]any{
"backgroundColor": config.Color,
}
}
return map[string]any{
"type": "antdText",
"data": map[string]any{
"id": id,
"text": config.Text,
"title": config.Title,
"style": map[string]any{
"backgroundColor": config.Color,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": fontSize,
"fontWeight": float64(400),
"lineHeight": "24px",
"minWidth": float64(24),
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
"type": "ResourceBadge",
"data": data,
}
}
// createUnifiedBadgeFromKind creates a badge from kind with automatic color generation
func createUnifiedBadgeFromKind(id, kind, title string, size BadgeSize) map[string]any {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: hexColorForKind(kind),
Title: title,
Size: size,
// createUnifiedBadgeFromKind creates a badge from kind with ResourceBadge component
// Abbreviation is auto-generated by ResourceBadge from kind, but can be customized if needed
func createUnifiedBadgeFromKind(id, kind string) map[string]any {
return map[string]any{
"type": "ResourceBadge",
"data": map[string]any{
"id": id,
"value": kind,
// abbreviation is optional - ResourceBadge auto-generates from value
},
}
return createUnifiedBadge(id, config)
}
// ---------------- Resource creation helpers with unified approach ----------------
@@ -183,7 +144,9 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
metadataName := generateMetadataName(specID)
// Generate badge config
badgeConfig := generateBadgeConfig(kind, "", "", title)
badgeConfig := BadgeConfig{
Kind: kind,
}
return ResourceConfig{
SpecID: specID,
@@ -196,35 +159,6 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
// ---------------- Enhanced color generation ----------------
// getColorForKind returns a color for a specific kind with improved distribution
func getColorForKind(kind string) string {
// Use existing hexColorForKind function
return hexColorForKind(kind)
}
// getColorForType returns a color for a specific type (like "namespace", "service", etc.)
func getColorForType(typeName string) string {
// Map common types to specific colors for consistency
colorMap := map[string]string{
"namespace": "#a25792ff",
"service": "#6ca100",
"pod": "#009596",
"node": "#8476d1",
"secret": "#c46100",
"configmap": "#b48c78ff",
"ingress": "#2e7dff",
"workloadmonitor": "#c46100",
"module": "#8b5cf6",
}
if color, exists := colorMap[strings.ToLower(typeName)]; exists {
return color
}
// Fall back to hash-based color generation
return hexColorForKind(typeName)
}
// ---------------- Automatic ID generation for UI elements ----------------
// generateElementID creates an ID for UI elements based on context and type
@@ -282,7 +216,6 @@ type UnifiedResourceConfig struct {
Title string
Color string
BadgeText string
Size BadgeSize
}
// createUnifiedFactory creates a factory using unified approach
@@ -292,16 +225,9 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
// Create header with unified badge
badgeConfig := BadgeConfig{
Kind: config.Kind,
Text: config.BadgeText,
Color: config.Color,
Title: config.Title,
Size: config.Size,
}
if badgeConfig.Text == "" {
badgeConfig.Text = initialsFromKind(config.Kind)
}
if badgeConfig.Color == "" {
badgeConfig.Color = getColorForKind(config.Kind)
}
badge := createUnifiedBadge(generateBadgeID("header", config.Kind), badgeConfig)
@@ -348,7 +274,9 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
// createUnifiedCustomColumn creates a custom column using unified approach
func createUnifiedCustomColumn(name, jsonPath, kind, title, href string) map[string]any {
badgeConfig := generateBadgeConfig(kind, "", "", title)
badgeConfig := BadgeConfig{
Kind: kind,
}
badge := createUnifiedBadge(generateBadgeID("column", kind), badgeConfig)
linkID := generateLinkID("column", "name")

View File

@@ -38,6 +38,9 @@ func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string,
if !ok {
return "", "", "", fmt.Errorf("failed to load chart-app mapping from config")
}
if hr.Spec.Chart == nil {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
s := hr.Spec.Chart.Spec
val, ok := cfg.chartAppMap[chartRef{s.SourceRef.Name, s.Chart}]
if !ok {

View File

@@ -26,6 +26,13 @@ var (
AncestryAmbiguous = fmt.Errorf("object ancestry is ambiguous")
)
const (
ManagedObjectKey = "internal.cozystack.io/managed-by-cozystack"
ManagerGroupKey = "apps.cozystack.io/application.group"
ManagerKindKey = "apps.cozystack.io/application.kind"
ManagerNameKey = "apps.cozystack.io/application.name"
)
// getResourceSelectors returns the appropriate CozystackResourceDefinitionResources for a given GroupKind
func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.CozystackResourceDefinition) *cozyv1alpha1.CozystackResourceDefinitionResources {
switch {
@@ -91,7 +98,7 @@ func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Req
labels, err := h.computeLabels(ctx, obj)
for {
if err != nil && errors.Is(err, NoAncestors) {
return admission.Allowed("object not managed by app")
break // not a problem, mark object as unmanaged
}
if err != nil && errors.Is(err, AncestryAmbiguous) {
warn = append(warn, "object ancestry ambiguous, using first ancestor found")
@@ -119,7 +126,7 @@ func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Req
func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, error) {
owners := lineage.WalkOwnershipGraph(ctx, h.dynClient, h.mapper, h, o)
if len(owners) == 0 {
return nil, NoAncestors
return map[string]string{ManagedObjectKey: "false"}, NoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, h.dynClient, h.mapper)
if err != nil {
@@ -135,7 +142,8 @@ func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstruc
}
labels := map[string]string{
// truncate apigroup to first 63 chars
"apps.cozystack.io/application.group": func(s string) string {
ManagedObjectKey: "true",
ManagerGroupKey: func(s string) string {
if len(s) < 63 {
return s
}
@@ -145,8 +153,8 @@ func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstruc
}
return s
}(gv.Group),
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
ManagerKindKey: obj.GetKind(),
ManagerNameKey: obj.GetName(),
}
templateLabels := map[string]string{
"kind": strings.ToLower(obj.GetKind()),

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:e1c0eb54d0ddadc8c7d72cee4f4282d56c957bf4ff0f62ef37db6b38dd0e8a01

View File

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

View File

@@ -147,7 +147,7 @@ spec:
podAdditionalMetadata:
labels:
policy.cozystack.io/allow-to-etcd: "true"
replicas: 2
replicas: {{ .Values.controlPlane.replicas }}
version: {{ include "kubernetes.versionMap" $ }}
---
apiVersion: cozystack.io/v1alpha1

View File

@@ -0,0 +1,150 @@
---
apiVersion: batch/v1
kind: Job
metadata:
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
name: {{ .Release.Name }}-cleanup
spec:
template:
metadata:
labels:
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: {{ .Release.Name }}-cleanup
restartPolicy: Never
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: "NoSchedule"
containers:
- name: kubectl
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- |
set -e
echo "Step 1: Suspending all HelmReleases with label cozystack.io/target-cluster-name={{ .Release.Name }}"
for hr in $(kubectl -n {{ .Release.Namespace }} get helmreleases.helm.toolkit.fluxcd.io -l "cozystack.io/target-cluster-name={{ .Release.Name }}" -o name 2>/dev/null || true); do
if [ -n "$hr" ]; then
echo " Suspending $hr"
kubectl -n {{ .Release.Namespace }} patch "$hr" \
-p '{"spec": {"suspend": true}}' \
--type=merge --field-manager=flux-client-side-apply
fi
done
echo "Step 2: Deleting HelmReleases with label cozystack.io/target-cluster-name={{ .Release.Name }}"
kubectl -n {{ .Release.Namespace }} delete helmreleases.helm.toolkit.fluxcd.io \
-l "cozystack.io/target-cluster-name={{ .Release.Name }}" \
--ignore-not-found=true --wait=true
echo "Step 3: Deleting KamajiControlPlane {{ .Release.Name }}"
kubectl -n {{ .Release.Namespace }} delete kamajicontrolplanes.controlplane.cluster.x-k8s.io {{ .Release.Name }} \
--ignore-not-found=true
echo "Step 4: Deleting TenantControlPlane {{ .Release.Name }}"
kubectl -n {{ .Release.Namespace }} delete tenantcontrolplanes.kamaji.clastix.io {{ .Release.Name }} \
--ignore-not-found=true
echo "Step 5: Cleaning up DataVolumes"
kubectl -n {{ .Release.Namespace }} delete datavolumes \
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}" \
--ignore-not-found=true
echo "Step 6: Cleaning up LoadBalancer Services"
kubectl -n {{ .Release.Namespace }} delete services \
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}" \
--field-selector spec.type=LoadBalancer \
--ignore-not-found=true
echo "Cleanup completed successfully"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-cleanup
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
rules:
- apiGroups:
- "helm.toolkit.fluxcd.io"
resources:
- helmreleases
verbs:
- get
- list
- watch
- patch
- delete
- apiGroups:
- "controlplane.cluster.x-k8s.io"
resources:
- kamajicontrolplanes
verbs:
- get
- list
- watch
- delete
- apiGroups:
- "kamaji.clastix.io"
resources:
- tenantcontrolplanes
verbs:
- get
- list
- watch
- delete
- apiGroups:
- "cdi.kubevirt.io"
resources:
- datavolumes
verbs:
- get
- list
- watch
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup
namespace: {{ .Release.Namespace }}

View File

@@ -1,103 +0,0 @@
---
apiVersion: batch/v1
kind: Job
metadata:
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
name: {{ .Release.Name }}-flux-teardown
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-flux-teardown
restartPolicy: Never
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: "NoSchedule"
containers:
- name: kubectl
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- |
kubectl
--namespace={{ .Release.Namespace }}
patch
helmrelease
{{ .Release.Name }}-cilium
{{ .Release.Name }}-gateway-api-crds
{{ .Release.Name }}-csi
{{ .Release.Name }}-cert-manager
{{ .Release.Name }}-cert-manager-crds
{{ .Release.Name }}-vertical-pod-autoscaler
{{ .Release.Name }}-vertical-pod-autoscaler-crds
{{ .Release.Name }}-ingress-nginx
{{ .Release.Name }}-fluxcd-operator
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-flux-teardown
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
"helm.sh/hook": pre-install,post-install,pre-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-flux-teardown
rules:
- apiGroups:
- "helm.toolkit.fluxcd.io"
resources:
- helmreleases
verbs:
- get
- patch
resourceNames:
- {{ .Release.Name }}-cilium
- {{ .Release.Name }}-csi
- {{ .Release.Name }}-cert-manager
- {{ .Release.Name }}-cert-manager-crds
- {{ .Release.Name }}-vertical-pod-autoscaler
- {{ .Release.Name }}-vertical-pod-autoscaler-crds
- {{ .Release.Name }}-ingress-nginx
- {{ .Release.Name }}-fluxcd-operator
- {{ .Release.Name }}-fluxcd
- {{ .Release.Name }}-gpu-operator
- {{ .Release.Name }}-velero
- {{ .Release.Name }}-coredns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation,hook-failed
helm.sh/hook-weight: "5"
name: {{ .Release.Name }}-flux-teardown
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-flux-teardown
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-flux-teardown
namespace: {{ .Release.Namespace }}

View File

@@ -0,0 +1,74 @@
---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
metadata:
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: {{ .Release.Name }}-cleanup
restartPolicy: Never
containers:
- name: cleanup
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- |
echo "Deleting orphaned PVCs for {{ .Release.Name }}..."
kubectl delete pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} || true
echo "PVC cleanup complete."
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
helm.sh/hook-weight: "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
helm.sh/hook-weight: "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup

View File

@@ -1,6 +1,14 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
{{- $passwords := dict }}
{{- with (dig "data" (dict) $existingSecret) }}
{{- range $k, $v := . }}
{{- $_ := set $passwords $k (b64dec $v) }}
{{- end }}
{{- end }}
{{- range $user, $u := .Values.users }}
{{- if $u.password }}
{{- $_ := set $passwords $user $u.password }}
@@ -47,18 +55,14 @@ spec:
retries: -1
values:
nats:
podTemplate:
container:
merge:
spec:
containers:
- name: nats
image: nats:2.10.17-alpine
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 22 }}
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 12 }}
fullnameOverride: {{ .Release.Name }}
config:
{{- if or (gt (len $passwords) 0) (gt (len .Values.config.merge) 0) }}
{{- if or $passwords .Values.config.merge }}
merge:
{{- if gt (len $passwords) 0 }}
{{- if $passwords }}
accounts:
A:
users:
@@ -67,13 +71,13 @@ spec:
password: "{{ $password }}"
{{- end }}
{{- end }}
{{- if and .Values.config (hasKey .Values.config "merge") }}
{{ toYaml .Values.config.merge | nindent 12 }}
{{- with .Values.config.merge }}
{{- toYaml . | nindent 10 }}
{{- end }}
{{- end }}
{{- if and .Values.config (hasKey .Values.config "resolver") }}
{{- with .Values.config.resolver }}
resolver:
{{ toYaml .Values.config.resolver | nindent 12 }}
{{- toYaml . | nindent 10 }}
{{- end }}
cluster:
enabled: true

View File

@@ -27,6 +27,7 @@ spec:
replicas: 3
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 6 }}
redis:
image: "redis:8.2.0"
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 6 }}
replicas: {{ .Values.replicas }}
{{- with .Values.size }}

View File

@@ -0,0 +1,85 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "tenant.name" . }}-cleanup
namespace: {{ include "tenant.name" . }}
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
helm.sh/hook-weight: "-5"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "tenant.name" . }}-cleanup
namespace: {{ include "tenant.name" . }}
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
helm.sh/hook-weight: "-5"
rules:
- apiGroups: ["helm.toolkit.fluxcd.io"]
resources: ["helmreleases"]
verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "tenant.name" . }}-cleanup
namespace: {{ include "tenant.name" . }}
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
helm.sh/hook-weight: "-5"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "tenant.name" . }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ include "tenant.name" . }}-cleanup
namespace: {{ include "tenant.name" . }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "tenant.name" . }}-cleanup
namespace: {{ include "tenant.name" . }}
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
helm.sh/hook-weight: "0"
spec:
ttlSecondsAfterFinished: 300
template:
metadata:
name: {{ include "tenant.name" . }}-cleanup
labels:
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: {{ include "tenant.name" . }}-cleanup
restartPolicy: OnFailure
containers:
- name: cleanup
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
set -e
NAMESPACE="{{ include "tenant.name" . }}"
echo "Cleaning up HelmReleases in namespace: $NAMESPACE"
echo "Deleting Applications"
kubectl delete helmreleases.helm.toolkit.fluxcd.io -n "$NAMESPACE" \
-l 'cozystack.io/ui=true,internal.cozystack.io/tenantmodule!=true' \
--ignore-not-found=true --wait=true
echo "Deleting Tenant Modules"
kubectl delete helmreleases.helm.toolkit.fluxcd.io -n "$NAMESPACE" \
-l 'cozystack.io/ui=true,internal.cozystack.io/tenantmodule=true' \
--ignore-not-found=true --wait=true
echo "Cleanup completed successfully"

View File

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

View File

@@ -28,13 +28,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -113,6 +113,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -121,7 +122,7 @@ metadata:
name: {{ include "tenant.name" . }}-view
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenant" (list "view" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-view
@@ -184,13 +185,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -199,7 +200,7 @@ metadata:
name: {{ include "tenant.name" . }}-use
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenant" (list "use" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-use
@@ -282,13 +283,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -297,7 +298,7 @@ metadata:
name: {{ include "tenant.name" . }}-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenant" (list "admin" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "admin" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-admin
@@ -356,13 +357,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -371,7 +372,7 @@ metadata:
name: {{ include "tenant.name" . }}-super-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenant" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-super-admin

View File

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

View File

@@ -27,7 +27,11 @@
{{- if and $existingPVC $desiredStorage -}}
{{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}}
{{- if not (eq $currentStorage $desiredStorage) -}}
{{- $needResizePVC = true -}}
{{- $oldSize := (include "cozy-lib.resources.toFloat" $currentStorage) | float64 -}}
{{- $newSize := (include "cozy-lib.resources.toFloat" $desiredStorage) | float64 -}}
{{- if gt $newSize $oldSize -}}
{{- $needResizePVC = true -}}
{{- end -}}
{{- end -}}
{{- end -}}

View File

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

View File

@@ -1,5 +1,17 @@
{{- $existingPVC := lookup "v1" "PersistentVolumeClaim" .Release.Namespace .Release.Name }}
{{- if and $existingPVC (ne ($existingPVC.spec.resources.requests.storage | toString) .Values.storage) -}}
{{- $shouldResize := false -}}
{{- if and $existingPVC .Values.storage -}}
{{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}}
{{- if ne $currentStorage .Values.storage -}}
{{- $oldSize := (include "cozy-lib.resources.toFloat" $currentStorage) | float64 -}}
{{- $newSize := (include "cozy-lib.resources.toFloat" .Values.storage) | float64 -}}
{{- if gt $newSize $oldSize -}}
{{- $shouldResize = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $shouldResize -}}
apiVersion: batch/v1
kind: Job
metadata:
@@ -23,6 +35,7 @@ spec:
command: ["sh", "-xec"]
args:
- |
echo "Resizing PVC to {{ .Values.storage }}..."
kubectl patch pvc {{ .Release.Name }} -p '{"spec":{"resources":{"requests":{"storage":"{{ .Values.storage }}"}}}}'
---
apiVersion: v1

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ set -u
TMPDIR=$(mktemp -d)
PROFILES="initramfs kernel iso installer nocloud metal"
FIRMWARES="amd-ucode amdgpu bnx2-bnx2x i915 intel-ice-firmware intel-ucode qlogic-firmware"
EXTENSIONS="drbd zfs lldpd"
EXTENSIONS="drbd zfs"
mkdir -p images/talos/profiles
@@ -90,7 +90,6 @@ input:
- imageRef: ${QLOGIC_FIRMWARE_IMAGE}
- imageRef: ${DRBD_IMAGE}
- imageRef: ${ZFS_IMAGE}
- imageRef: ${LLDPD_IMAGE}
output:
kind: ${kind}
imageOptions: ${image_options}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: initramfs
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: installer
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: iso
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: kernel
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250708@sha256:97124ee3594ab1529c8153b633f85f2d2de1252ee8222a77f81904dcabd76815
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.10.6@sha256:ca7fba878c5acb8fdfe130a39472a6c0a5c9dd74d65ba7507c09780a873b29c7
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.10.6@sha256:4952ef7306cf014823b6a66cf6d29840f4c6b7b362e36f9d6e853846c7dd0025
- imageRef: ghcr.io/siderolabs/lldpd:1.0.19@sha256:73caa3c3a6c325970d0f527963f982698154d5f39c8c045b0fc2eb51d7da7b85
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

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

View File

@@ -68,6 +68,12 @@ releases:
disableTelemetry: true
{{- end }}
- name: lineage-controller-webhook
releaseName: lineage-controller-webhook
chart: cozy-lineage-controller-webhook
namespace: cozy-system
dependsOn: [cozystack-controller,cilium,cert-manager]
- name: cert-manager
releaseName: cert-manager
chart: cozy-cert-manager

View File

@@ -36,6 +36,12 @@ releases:
disableTelemetry: true
{{- end }}
- name: lineage-controller-webhook
releaseName: lineage-controller-webhook
chart: cozy-lineage-controller-webhook
namespace: cozy-system
dependsOn: [cozystack-controller,cert-manager]
- name: cert-manager
releaseName: cert-manager
chart: cozy-cert-manager

View File

@@ -105,6 +105,12 @@ releases:
disableTelemetry: true
{{- end }}
- name: lineage-controller-webhook
releaseName: lineage-controller-webhook
chart: cozy-lineage-controller-webhook
namespace: cozy-system
dependsOn: [cozystack-controller,cilium,kubeovn,cert-manager]
- name: cozystack-resource-definition-crd
releaseName: cozystack-resource-definition-crd
chart: cozystack-resource-definition-crd

View File

@@ -52,6 +52,12 @@ releases:
disableTelemetry: true
{{- end }}
- name: lineage-controller-webhook
releaseName: lineage-controller-webhook
chart: cozy-lineage-controller-webhook
namespace: cozy-system
dependsOn: [cozystack-controller,cert-manager]
- name: cozystack-resource-definition-crd
releaseName: cozystack-resource-definition-crd
chart: cozystack-resource-definition-crd

View File

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

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v0.37.0@sha256:5cca5f56b755285aefa11b1052fe55e1aa83b25bae34aef80cdb77ff63091044
ghcr.io/cozystack/cozystack/matchbox:v0.37.10@sha256:5ebd5dca8c4adac48362f0c11de9696ce675a7edf184029e0118351dd701c730

View File

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

View File

@@ -72,6 +72,8 @@
| `alerta.alerts.telegram.token` | Telegram token for your bot | `string` | `""` |
| `alerta.alerts.telegram.chatID` | Specify multiple ID's separated by comma. Get yours in https://t.me/chatid_echo_bot | `string` | `""` |
| `alerta.alerts.telegram.disabledSeverity` | List of severity without alerts, separated by comma like: "informational,warning" | `string` | `""` |
| `alerta.alerts.slack` | Configuration for Slack alerts | `*object` | `null` |
| `alerta.alerts.slack.url` | Configuration uri for Slack alerts | `*string` | `""` |
### Grafana configuration

View File

@@ -109,9 +109,20 @@ spec:
- name: AUTH_REQUIRED
value: "True"
{{- $plugins := list }}
{{- if and .Values.alerta.alerts.telegram.chatID .Values.alerta.alerts.telegram.token }}
{{- $plugins = append $plugins "telegram" }}
{{- end }}
{{- if .Values.alerta.alerts.slack.url }}
{{- $plugins = append $plugins "slack" }}
{{- end }}
{{- if gt (len $plugins) 0 }}
- name: "PLUGINS"
value: "telegram"
value: "{{ default "" (join "," $plugins) }}"
{{- end }}
{{- if and .Values.alerta.alerts.telegram.chatID .Values.alerta.alerts.telegram.token }}
- name: TELEGRAM_CHAT_ID
value: "{{ .Values.alerta.alerts.telegram.chatID }}"
- name: TELEGRAM_TOKEN
@@ -122,6 +133,11 @@ spec:
value: "{{ .Values.alerta.alerts.telegram.disabledSeverity }}"
{{- end }}
{{- if .Values.alerta.alerts.slack.url }}
- name: "SLACK_WEBHOOK_URL"
value: "{{ .Values.alerta.alerts.slack.url }}"
{{- end }}
ports:
- name: http
containerPort: 8080

View File

@@ -12,6 +12,17 @@
"type": "object",
"default": {},
"properties": {
"slack": {
"description": "Configuration for Slack alerts",
"type": "object",
"default": {},
"properties": {
"url": {
"description": "Configuration uri for Slack alerts",
"type": "string"
}
}
},
"telegram": {
"description": "Configuration for Telegram alerts",
"type": "object",

View File

@@ -90,6 +90,8 @@ logsStorages:
## @field telegramAlerts.token {string} Telegram token for your bot
## @field telegramAlerts.chatID {string} Specify multiple ID's separated by comma. Get yours in https://t.me/chatid_echo_bot
## @field telegramAlerts.disabledSeverity {string} List of severity without alerts, separated by comma like: "informational,warning"
## @field alerts.slack {*slackAlerts} Configuration for Slack alerts
## @field slackAlerts.url {*string} Configuration uri for Slack alerts
alerta:
storage: 10Gi
storageClassName: ""
@@ -112,6 +114,9 @@ alerta:
chatID: ""
disabledSeverity: ""
slack:
url: ""
## @section Grafana configuration
## @param grafana {grafana} Configuration for Grafana

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.37.0@sha256:f166f09cdc9cdbb758209883819ab8261a3793bc1d7a6b6685efd5a2b2930847
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.37.10@sha256:233a1177f309aa159a8c4a08212dab3f992f5a2b626f86a15c8ffd44f10bf2ad

View File

@@ -154,7 +154,7 @@
{{- $resources := index . 1 }}
{{- $global := index . 2 }}
{{- $presetMap := include "cozy-lib.resources.unsanitizedPreset" $preset | fromYaml }}
{{- $mergedMap := deepCopy $resources | mergeOverwrite $presetMap }}
{{- $mergedMap := deepCopy (default (dict) $resources) | mergeOverwrite $presetMap }}
{{- include "cozy-lib.resources.sanitize" (list $mergedMap $global) }}
{{- end }}
@@ -174,15 +174,68 @@
{{- end }}
{{- define "cozy-lib.resources.flatten" -}}
{{- /*
This helper either outputs raw ResourceQuota fields (e.g., services.loadbalancers)
as-is, or flattens sanitized resource maps into limits.* / requests.* keys.
If ALL keys in the input are recognized quota keys (pods, services.*, etc.),
the input is output directly as YAML. Otherwise, the input is treated as a
resource map and processed through sanitize + flatten.
Do not mix quota keys and resource keys in a single call.
*/ -}}
{{- $input := index . 0 -}}
{{- $ctx := index . 1 -}}
{{- $rawQuotaKeys := list
"pods"
"services"
"services.loadbalancers"
"services.nodeports"
"services.clusterip"
"configmaps"
"secrets"
"persistentvolumeclaims"
"replicationcontrollers"
"resourcequotas"
-}}
{{- $computeKeys := list
"cpu"
"memory"
"ephemeral-storage"
-}}
{{- $out := dict -}}
{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}}
{{- $computeResources := dict -}}
{{- $quotaResources := dict -}}
{{- range $k, $v := $input }}
{{- if or (has $k $computeKeys) (hasPrefix "limits." $k) (hasPrefix "requests." $k) }}
{{- $_ := set $computeResources $k $v }}
{{- else if has $k $rawQuotaKeys }}
{{- $_ := set $quotaResources $k $v }}
{{- else }}
{{- $_ := set $computeResources $k $v }}
{{- end }}
{{- end }}
{{- if $computeResources }}
{{- $res := include "cozy-lib.resources.sanitize" (list $computeResources (index . 1)) | fromYaml -}}
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- $key := printf "%s.%s" $section $k }}
{{- if ne $key "limits.storage" }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- end }}
{{- range $k, $v := $values }}
{{- $key := printf "%s.%s" $section $k }}
{{- if ne $key "limits.storage" }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- range $k, $v := $quotaResources }}
{{- $_ := set $out $k $v }}
{{- end }}
{{- $out | toYaml }}
{{- end }}

View File

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

View File

@@ -10,6 +10,7 @@ metadata:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: HTTPS
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
name: kubernetes
namespace: default
spec:

View File

@@ -1,9 +1,10 @@
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/cozystack-api"
name: v1alpha1.apps.cozystack.io
spec:
insecureSkipTLSVerify: true
group: apps.cozystack.io
groupPriorityMinimum: 1000
versionPriority: 15
@@ -15,9 +16,10 @@ spec:
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/cozystack-api"
name: v1alpha1.core.cozystack.io
spec:
insecureSkipTLSVerify: true
group: core.cozystack.io
groupPriorityMinimum: 1000
versionPriority: 15

View File

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

View File

@@ -1,12 +1,18 @@
apiVersion: apps/v1
{{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
kind: DaemonSet
{{- else }}
kind: Deployment
{{- end }}
metadata:
name: cozystack-api
namespace: cozy-system
labels:
app: cozystack-api
spec:
replicas: 2
{{- if not .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
replicas: {{ .Values.cozystackAPI.replicas }}
{{- end }}
selector:
matchLabels:
app: cozystack-api
@@ -16,6 +22,35 @@ spec:
app: cozystack-api
spec:
serviceAccountName: cozystack-api
{{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
nodeSelector:
node-role.kubernetes.io/control-plane: ""
{{- end }}
containers:
- name: cozystack-api
args:
- --tls-cert-file=/tmp/cozystack-api-certs/tls.crt
- --tls-private-key-file=/tmp/cozystack-api-certs/tls.key
{{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
env:
- name: KUBERNETES_SERVICE_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: KUBERNETES_SERVICE_PORT
value: "6443"
{{- end }}
image: "{{ .Values.cozystackAPI.image }}"
ports:
- containerPort: 443
name: https
volumeMounts:
- name: cozystack-api-certs
mountPath: /tmp/cozystack-api-certs
readOnly: true
volumes:
- name: cozystack-api-certs
secret:
secretName: cozystack-api-cert
defaultMode: 0400

View File

@@ -4,7 +4,10 @@ metadata:
name: cozystack-api
rules:
- apiGroups: [""]
resources: ["namespaces", "secrets"]
resources: ["namespaces", "secrets", "services"]
verbs: ["get", "watch", "list"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["secrets"]

View File

@@ -4,9 +4,12 @@ metadata:
name: cozystack-api
namespace: cozy-system
spec:
{{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
internalTrafficPolicy: Local
{{- end }}
ports:
- port: 443
protocol: TCP
targetPort: 443
targetPort: https
selector:
app: cozystack-api

View File

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

View File

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

View File

@@ -28,15 +28,6 @@ spec:
{{- if .Values.cozystackController.disableTelemetry }}
- --disable-telemetry
{{- end }}
ports:
- name: webhook
containerPort: 9443
volumeMounts:
- name: webhook-certs
mountPath: /tmp/k8s-webhook-server/serving-certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: cozystack-controller-webhook-cert
defaultMode: 0400
{{- if eq .Values.cozystackController.cozystackAPIKind "Deployment" }}
- --reconcile-deployment
{{- end }}

View File

@@ -5,7 +5,7 @@ metadata:
namespace: cozy-system
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
resources: ["deployments", "daemonsets"]
resourceNames: ["cozystack-api"]
verbs: ["patch", "update"]

View File

@@ -1,5 +1,6 @@
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.37.0@sha256:845b8e68cbc277c2303080bcd55597e4334610d396dad258ad56fd906530acc3
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.37.10@sha256:224871a6e8f7a80d2b6476b57b6176c472afe112b503a5c5bf151b945d78a407
debug: false
disableTelemetry: false
cozystackVersion: "v0.37.0"
cozystackVersion: "v0.37.10"
cozystackAPIKind: "DaemonSet"

File diff suppressed because one or more lines are too long

View File

@@ -32,3 +32,8 @@ spec:
secrets:
exclude: []
include: []
services:
exclude: []
include:
- resourceNames:
- virtual-machine-{{ .name }}

View File

@@ -1,15 +1,11 @@
# imported from https://github.com/cozystack/openapi-ui-k8s-bff
ARG NODE_VERSION=20.18.1
FROM node:${NODE_VERSION}-alpine AS builder
RUN apk add git
WORKDIR /src
ARG COMMIT_REF=22f9143f5109fb90332651c857d70b51bffccd9b
ARG COMMIT_REF=183dc9dcbb0f8a1833dad642c35faa385c71e58d
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui-k8s-bff/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
COPY patches /patches
RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH
RUN npm install
RUN npm run build

View File

@@ -1,7 +1,7 @@
diff --git a/src/endpoints/forms/formPrepare/formPrepare.ts b/src/endpoints/forms/formPrepare/formPrepare.ts
diff --git a/src/endpoints/forms/prepareFormProps/prepareFormProps.ts b/src/endpoints/forms/prepareFormProps/prepareFormProps.ts
index 7e437db..90c40f6 100644
--- a/src/endpoints/forms/formPrepare/formPrepare.ts
+++ b/src/endpoints/forms/formPrepare/formPrepare.ts
--- a/src/endpoints/forms/prepareFormProps/prepareFormProps.ts
+++ b/src/endpoints/forms/prepareFormProps/prepareFormProps.ts
@@ -15,6 +15,7 @@ export const prepareFormProps: RequestHandler = async (req: TPrepareFormReq, res
const filteredHeaders = { ...req.headers }

View File

@@ -3,9 +3,14 @@ ARG NODE_VERSION=20.18.1
# openapi-k8s-toolkit
# imported from https://github.com/cozystack/openapi-k8s-toolkit
FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder
RUN apk add git
WORKDIR /src
ARG COMMIT=4f57ab295b2a886eb294b0b987554194fbe67dcd
ARG COMMIT=7bd5380c6c4606640dd3bac68bf9dce469470518
RUN wget -O- https://github.com/cozystack/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1
COPY openapi-k8s-toolkit/patches /patches
RUN git apply /patches/*.diff
RUN npm install
RUN npm install --build-from-source @swc/core
RUN npm run build
@@ -14,14 +19,14 @@ RUN npm run build
# openapi-ui
# imported from https://github.com/cozystack/openapi-ui
FROM node:${NODE_VERSION}-alpine AS builder
RUN apk add git
#RUN apk add git
WORKDIR /src
ARG COMMIT_REF=65e7fa8b3dc530a36e94c8435622bb09961aef97
ARG COMMIT_REF=0c3629b2ce8545e81f7ece4d65372a188c802dfc
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
COPY patches /patches
RUN git apply /patches/*.diff
#COPY openapi-ui/patches /patches
#RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH

View File

@@ -0,0 +1,50 @@
diff --git a/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx b/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
index 8bcef4d..2551e92 100644
--- a/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
+++ b/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
@@ -22,6 +22,15 @@ import { TableFactory } from '../../molecules'
import { ShortenedTextWithTooltip, FilterDropdown, TrimmedTags, TextAlignContainer, TinyButton } from './atoms'
import { TInternalDataForControls } from './types'
+const getPluralForm = (singular: string): string => {
+ // If already ends with 's', add 'es'
+ if (singular.endsWith('s')) {
+ return `${singular}es`
+ }
+ // Otherwise just add 's'
+ return `${singular}s`
+}
+
export const getCellRender = ({
value,
record,
@@ -255,7 +264,7 @@ export const getEnrichedColumnsWithControls = ({
key: 'controls',
className: 'controls',
width: 60,
- render: (value: TInternalDataForControls) => {
+ render: (value: TInternalDataForControls, record: unknown) => {
return (
// <TextAlignContainer $align="right" className="hideable">
<TextAlignContainer $align="center">
@@ -279,10 +288,19 @@ export const getEnrichedColumnsWithControls = ({
domEvent.stopPropagation()
domEvent.preventDefault()
if (key === 'edit') {
+ // Special case: redirect tenantmodules from core.cozystack.io to apps.cozystack.io with plural form
+ let apiGroupAndVersion = value.apiGroupAndVersion
+ let typeName = value.typeName
+ if (apiGroupAndVersion?.startsWith('core.cozystack.io/') && typeName === 'tenantmodules') {
+ const appsApiVersion = apiGroupAndVersion.replace('core.cozystack.io/', 'apps.cozystack.io/')
+ const pluralTypeName = getPluralForm(value.entryName)
+ apiGroupAndVersion = appsApiVersion
+ typeName = pluralTypeName
+ }
navigate(
`${baseprefix}/${value.cluster}${value.namespace ? `/${value.namespace}` : ''}${
value.syntheticProject ? `/${value.syntheticProject}` : ''
- }/${value.pathPrefix}/${value.apiGroupAndVersion}/${value.typeName}/${value.entryName}?backlink=${
+ }/${value.pathPrefix}/${apiGroupAndVersion}/${typeName}/${value.entryName}?backlink=${
value.backlink
}`,
)

View File

@@ -1,89 +0,0 @@
diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
index 577ba0f..018df9c 100644
--- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
+++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
@@ -1,11 +1,16 @@
import React, { FC, useState } from 'react'
import { Button, Alert, Spin, Typography } from 'antd'
-import { filterSelectOptions, Spacer, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
+import { filterSelectOptions, Spacer, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'store/store'
import { setCluster } from 'store/cluster/cluster/cluster'
import { Styled } from './styled'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
export const ListInsideClusterAndNs: FC = () => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
@@ -17,9 +22,11 @@ export const ListInsideClusterAndNs: FC = () => {
const [selectedCluster, setSelectedCluster] = useState<string>()
const [selectedNamespace, setSelectedNamespace] = useState<string>()
- const namespacesData = useBuiltinResources({
+ const namespacesData = useApiResources({
clusterName: cluster,
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
})
diff --git a/src/hooks/useNavSelectorInside.ts b/src/hooks/useNavSelectorInside.ts
index d69405e..5adbd5d 100644
--- a/src/hooks/useNavSelectorInside.ts
+++ b/src/hooks/useNavSelectorInside.ts
@@ -1,6 +1,11 @@
-import { TClusterList, TSingleResource, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
+import { TClusterList, TSingleResource, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({
value: name,
@@ -15,9 +20,11 @@ const mappedNamespaceToOptionInSidebar = ({ metadata }: TSingleResource): { valu
export const useNavSelectorInside = (clusterName?: string) => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
- const { data: namespaces } = useBuiltinResources({
+ const { data: namespaces } = useApiResources({
clusterName: clusterName || '',
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
})
diff --git a/src/utils/getBacklink.ts b/src/utils/getBacklink.ts
index a862354..f24e2bc 100644
--- a/src/utils/getBacklink.ts
+++ b/src/utils/getBacklink.ts
@@ -28,7 +28,7 @@ export const getFormsBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {
@@ -64,7 +64,7 @@ export const getTablesBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {

File diff suppressed because one or more lines are too long

View File

@@ -55,6 +55,8 @@ spec:
- --http-address=0.0.0.0:8000
- --redirect-url=https://dashboard.{{ $host }}/oauth2/callback
- --oidc-issuer-url=https://keycloak.{{ $host }}/realms/cozy
- --backend-logout-url=https://keycloak.{{ $host }}/realms/cozy/protocol/openid-connect/logout?id_token_hint={id_token}
- --whitelist-domain=keycloak.{{ $host }}
- --email-domain=*
- --pass-access-token=true
- --pass-authorization-header=true

View File

@@ -16,7 +16,7 @@ spec:
template:
metadata:
annotations:
checksum/configmap-configurationnginxfile: 258c66b019c8c7f4a5d0a78dfd7bf297ce486b213346fbd2879c466abfc377e0
checksum/config: {{ include (print $.Template.BasePath "/nginx-config.yaml") . | sha256sum }}
labels:
app.kubernetes.io/instance: incloud-web
app.kubernetes.io/name: nginx

View File

@@ -15,7 +15,8 @@ spec:
type: RollingUpdate
template:
metadata:
annotations: null
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
app.kubernetes.io/instance: incloud-web
app.kubernetes.io/name: web
@@ -42,10 +43,12 @@ spec:
value: dashboard.cozystack.io
- name: BASE_API_VERSION
value: v1alpha1
- name: BASE_NAMESPACE_FULL_PATH
value: "/apis/core.cozystack.io/v1alpha1/tenantnamespaces"
- name: LOGGER
value: "TRUE"
value: "true"
- name: LOGGER_WITH_HEADERS
value: "TRUE"
value: "false"
- name: PORT
value: "64231"
image: {{ .Values.openapiUIK8sBff.image | quote }}
@@ -92,6 +95,8 @@ spec:
- env:
- name: BASEPREFIX
value: /openapi-ui
- name: HIDE_INSIDE
value: "true"
- name: CUSTOMIZATION_API_GROUP
value: dashboard.cozystack.io
- name: CUSTOMIZATION_API_VERSION
@@ -122,6 +127,12 @@ spec:
value: tenantnamespaces
- name: PROJECTS_VERSION
value: v1alpha1
- name: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
value: core.cozystack.io
- name: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
value: v1alpha1
- name: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
value: tenantnamespaces
- name: USE_NAMESPACE_NAV
value: "true"
- name: LOGIN_URL
@@ -140,21 +151,21 @@ spec:
configMapKeyRef:
name: incloud-web-dashboard-config
key: TITLE_TEXT
- name: TENANT_TEXT
- name: CUSTOM_TENANT_TEXT
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: TENANT_TEXT
key: CUSTOM_TENANT_TEXT
- name: LOGO_TEXT
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: LOGO_TEXT
- name: LOGO_SVG
- name: CUSTOM_LOGO_SVG
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: LOGO_SVG
key: CUSTOM_LOGO_SVG
- name: ICON_SVG
valueFrom:
configMapKeyRef:

View File

@@ -1,6 +1,6 @@
openapiUI:
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.37.0@sha256:13f38cf56830e899eb5e3d9dc8184965dd8dba9f8cd3c5ca10df0970355842d6
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.37.10@sha256:73e9a77e15c9e4e8bd72a292ee6093354048ac5d06df6db8e91ec0f3de6a37f0
openapiUIK8sBff:
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.37.0@sha256:2b626dbbf87241e8621ac5b0285f402edbc2c2069ba254ca2ace2dd5c9248ac8
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.37.10@sha256:2845688891e0cf9513768aa468ff4de5b6b40e58cc763bacbfe2b417ebad1bdf
tokenProxy:
image: ghcr.io/cozystack/cozystack/token-proxy:v0.37.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b
image: ghcr.io/cozystack/cozystack/token-proxy:v0.37.10@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b

View File

@@ -10,3 +10,4 @@ update:
rm -rf charts
helm pull oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator --untar --untardir charts
patch --no-backup-if-mismatch -p1 < patches/kubernetesEnvs.diff
patch --no-backup-if-mismatch -p1 < patches/networkPolicy.diff

View File

@@ -0,0 +1,21 @@
{{- if .Capabilities.APIVersions.Has "cilium.io/v2/CiliumClusterwideNetworkPolicy" }}
---
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: {{ include "flux-operator.fullname" . }}-restrict
spec:
nodeSelector: {}
ingressDeny:
- fromEntities:
- world
toPorts:
- ports:
- port: "8080"
protocol: TCP
- port: "8081"
protocol: TCP
ingress:
- fromEntities:
- cluster
{{- end }}

View File

@@ -0,0 +1,25 @@
diff --git a/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml b/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml
new file mode 100644
--- /dev/null (revision 52a23eacfc32430d8b008b765c64a81526521bae)
+++ b/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml (revision 52a23eacfc32430d8b008b765c64a81526521bae)
@@ -0,0 +1,18 @@
+{{- if .Capabilities.APIVersions.Has "cilium.io/v2/CiliumClusterwideNetworkPolicy" }}
+apiVersion: cilium.io/v2
+kind: CiliumClusterwideNetworkPolicy
+metadata:
+ name: {{ include "flux-operator.fullname" . }}-restrict
+spec:
+ nodeSelector: {}
+ ingressDeny:
+ - fromEntities:
+ - world
+ toPorts:
+ - ports:
+ - port: "8080"
+ protocol: TCP
+ - port: "8081"
+ protocol: TCP
+ ingress:
+ - fromEntities:
+ - cluster
+{{- end }}

View File

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

View File

@@ -0,0 +1,156 @@
diff --git a/internal/resources/api_server_certificate.go b/internal/resources/api_server_certificate.go
index 436cdf9..4702b6c 100644
--- a/internal/resources/api_server_certificate.go
+++ b/internal/resources/api_server_certificate.go
@@ -108,6 +108,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
}
r.resource.SetLabels(utilities.MergeMaps(
+ r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
diff --git a/internal/resources/api_server_kubelet_client_certificate.go b/internal/resources/api_server_kubelet_client_certificate.go
index 85b4d42..da18db4 100644
--- a/internal/resources/api_server_kubelet_client_certificate.go
+++ b/internal/resources/api_server_kubelet_client_certificate.go
@@ -95,6 +95,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
}
r.resource.SetLabels(utilities.MergeMaps(
+ r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
diff --git a/internal/resources/ca_certificate.go b/internal/resources/ca_certificate.go
index 5425b0b..625273f 100644
--- a/internal/resources/ca_certificate.go
+++ b/internal/resources/ca_certificate.go
@@ -137,7 +137,7 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
corev1.TLSPrivateKeyKey: ca.PrivateKey,
}
- r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
+ r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
utilities.SetObjectChecksum(r.resource, r.resource.Data)
diff --git a/internal/resources/datastore/datastore_certificate.go b/internal/resources/datastore/datastore_certificate.go
index dea45ae..8492a5e 100644
--- a/internal/resources/datastore/datastore_certificate.go
+++ b/internal/resources/datastore/datastore_certificate.go
@@ -94,6 +94,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
r.resource.Data["ca.crt"] = ca
r.resource.SetLabels(utilities.MergeMaps(
+ r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
diff --git a/internal/resources/datastore/datastore_storage_config.go b/internal/resources/datastore/datastore_storage_config.go
index 7d03420..4ea9e64 100644
--- a/internal/resources/datastore/datastore_storage_config.go
+++ b/internal/resources/datastore/datastore_storage_config.go
@@ -181,7 +181,7 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
utilities.SetObjectChecksum(r.resource, r.resource.Data)
- r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
+ r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}
diff --git a/internal/resources/front-proxy-client-certificate.go b/internal/resources/front-proxy-client-certificate.go
index f5ed67c..2dd4eda 100644
--- a/internal/resources/front-proxy-client-certificate.go
+++ b/internal/resources/front-proxy-client-certificate.go
@@ -95,6 +95,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
}
r.resource.SetLabels(utilities.MergeMaps(
+ r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
diff --git a/internal/resources/front_proxy_ca_certificate.go b/internal/resources/front_proxy_ca_certificate.go
index d410720..ccadc70 100644
--- a/internal/resources/front_proxy_ca_certificate.go
+++ b/internal/resources/front_proxy_ca_certificate.go
@@ -114,7 +114,7 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
kubeadmconstants.FrontProxyCAKeyName: ca.PrivateKey,
}
- r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
+ r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
utilities.SetObjectChecksum(r.resource, r.resource.Data)
diff --git a/internal/resources/k8s_ingress_resource.go b/internal/resources/k8s_ingress_resource.go
index f2e014f..e1aef59 100644
--- a/internal/resources/k8s_ingress_resource.go
+++ b/internal/resources/k8s_ingress_resource.go
@@ -147,7 +147,7 @@ func (r *KubernetesIngressResource) Define(_ context.Context, tenantControlPlane
func (r *KubernetesIngressResource) mutate(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
- labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
+ labels := utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
r.resource.SetLabels(labels)
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Annotations)
diff --git a/internal/resources/k8s_service_resource.go b/internal/resources/k8s_service_resource.go
index 7e7f11f..9c30145 100644
--- a/internal/resources/k8s_service_resource.go
+++ b/internal/resources/k8s_service_resource.go
@@ -76,7 +76,12 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
address, _ := tenantControlPlane.DeclaredControlPlaneAddress(ctx, r.Client)
return func() error {
- labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels)
+ labels := utilities.MergeMaps(
+ r.resource.GetLabels(),
+ utilities.KamajiLabels(
+ tenantControlPlane.GetName(), r.GetName()),
+ tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels,
+ )
r.resource.SetLabels(labels)
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Annotations)
diff --git a/internal/resources/kubeadm_config.go b/internal/resources/kubeadm_config.go
index ae4cfc0..98dc36d 100644
--- a/internal/resources/kubeadm_config.go
+++ b/internal/resources/kubeadm_config.go
@@ -89,7 +89,7 @@ func (r *KubeadmConfigResource) mutate(ctx context.Context, tenantControlPlane *
return err
}
- r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
+ r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
params := kubeadm.Parameters{
TenantControlPlaneAddress: address,
diff --git a/internal/resources/kubeconfig.go b/internal/resources/kubeconfig.go
index a87da7f..bd77676 100644
--- a/internal/resources/kubeconfig.go
+++ b/internal/resources/kubeconfig.go
@@ -163,6 +163,7 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
}
r.resource.SetLabels(utilities.MergeMaps(
+ r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "kubeconfig",
diff --git a/internal/resources/sa_certificate.go b/internal/resources/sa_certificate.go
index b53c7b0..4001eca 100644
--- a/internal/resources/sa_certificate.go
+++ b/internal/resources/sa_certificate.go
@@ -113,7 +113,7 @@ func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
kubeadmconstants.ServiceAccountPrivateKeyName: sa.PrivateKey,
}
- r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
+ r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
utilities.SetObjectChecksum(r.resource, r.resource.Data)

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ kube-ovn:
namespace: cozy-kubeovn
func:
ENABLE_NP: false
ENABLE_LB: false
ipv4:
POD_CIDR: "10.244.0.0/16"
POD_GATEWAY: "10.244.0.1"
@@ -43,7 +44,7 @@ kube-ovn:
memory: "50Mi"
limits:
cpu: "1000m"
memory: "1Gi"
memory: "2Gi"
kube-ovn-pinger:
requests:
cpu: "10m"
@@ -64,4 +65,4 @@ global:
images:
kubeovn:
repository: kubeovn
tag: v1.14.5@sha256:af10da442a0c6dc7df47a0ef752e2eb5c247bb0b43069fdfcb2aa51511185ea2
tag: v1.14.5@sha256:665ad3f64332dc8b70337953ddb3ac72559137b4110c2ec36ba609ceded6b972

View File

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

View File

@@ -10,7 +10,7 @@ spec:
expr: |
max_over_time(
kubevirt_vm_info{
status!="Running",
status!="running",
exported_namespace=~".+",
name=~".+"
}[10m]
@@ -27,7 +27,7 @@ spec:
expr: |
max_over_time(
kubevirt_vmi_info{
phase!="running",
phase!="Running",
exported_namespace=~".+",
name=~".+"
}[10m]

View File

@@ -0,0 +1,27 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/*
Dockerfile.cross
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Go workspace file
go.work
# Kubernetes Generated files - skip generated files, except for vendored files
!vendor/**/zz_generated.*
# editor and IDE paraphernalia
.idea
.vscode
*.swp
*.swo
*~

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