Compare commits

..

93 Commits

Author SHA1 Message Date
Andrei Kvapil
80ad9965e5 [cozystack-api] Fix List kinds
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-12 10:32:51 -05:00
Andrei Kvapil
523510469c [cozystack-controller] improve API tests (#1617)
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

Adds check also for core.cozystack.io group

### Release note

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

```release-note
[cozystack-controller] improve API tests
```

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

## Summary by CodeRabbit

# Release Notes

* **Tests**
* Enhanced validation during installation to verify multiple API
services
* Expanded OpenAPI endpoint verification to include additional services

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 23:44:27 +01:00
Andrei Kvapil
cf5b2f2bbb [cozystack-controller] improve API tests
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 22:57:28 +01:00
Andrei Kvapil
4e5343e36c [dashboard-controller] Fix static resources reconciliation and showing secrets (#1615)
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-controller] Fix static resources reconciliation and showing secrets
```
2025-11-07 17:31:09 +01:00
Andrei Kvapil
d8237b4321 [dashboard-controller] Fix static resources reconciliation and showing
secrets

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 17:30:26 +01:00
Andrei Kvapil
83c3b0ca12 [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 15:48:02 +01:00
Andrei Kvapil
e1590aad1b [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 15:36:24 +01:00
Andrei Kvapil
304338d697 Apply review suggestions
Co-authored-by: Timofei Larkin <lllamnyp@gmail.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 15:35:56 +01:00
Andrei Kvapil
b65d639ecb [cozystack-api] Fix sorting for TenantSecrets
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 14:56:23 +01:00
Andrei Kvapil
339e71331f [cozystack-api] Enhance TenantSecrets filtering
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 14:56:23 +01:00
Andrei Kvapil
08be385665 [dashboard-controller] Fix labelSelectors
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 14:56:23 +01:00
Andrei Kvapil
2f0657f8ba [virtual-machine] Revert per-vm network policies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-07 11:36:59 +01:00
Andrei Kvapil
a64ba184ce [cozy-lib] Fix: handling resources=nil (#1607)
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


Fixes issue:

```
error: template: tcp-balancer/templates/deployment.yaml:37:23: executing "tcp-balancer/templates/deployment.yaml" at <include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesP
reset .Values.resources $)>: error calling include: template: tcp-balancer/charts/cozy-lib/templates/_resources.tpl:157:20: executing "cozy-lib.resources.defaultingSanitize" at <deepCopy $re
sources>: error calling deepCopy: reflect: call of reflect.Value.Type on zero Value
```

### 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
[cozy-lib] Fix: handling resources=nil
```

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved resource handling to ensure proper behavior when resources
are not provided, enhancing system reliability and consistency in
resource merging operations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 11:03:32 +01:00
Andrei Kvapil
00328c8a31 [cozy-lib] Fix: handling resources=nil
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Co-authored-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-07 11:02:02 +01:00
Timofei Larkin
7009c8da37 [kubernetes] Helm hooks for cleanup (#1606)
## 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.
```

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

* **Chores**
* Added an automated post-delete cleanup job to remove persistent data
volumes scoped to the release namespace when a release is deleted.
* Updated Helm release teardown to actively delete lingering release
resources (rather than only suspending them) for cleaner uninstall
behavior.
* Broadened lifecycle hooks to run on successful completions and
expanded teardown permissions to list and delete related release
artifacts, including gateway CRDs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 13:50:55 +04:00
Timofei Larkin
63db8ca009 [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>
2025-11-07 12:01:00 +03:00
Andrei Kvapil
369384f5ec [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.
-->

## What this PR does

- 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)

### 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] sync with upstream & enhancements
```

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

* **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-06 16:23:39 +01:00
Timofei Larkin
4278692763 Revert "[kubernetes] Helm hooks for cleanup"
This reverts commit edc942b6c1.

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-06 15:06:42 +03:00
Timofei Larkin
edc942b6c1 [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>
2025-11-06 15:01:11 +03:00
Timofei Larkin
4c71e7fe57 [nats] Fix NATS app chart to use existing secret credentials when present (#1599)
<!-- 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 fixes an issue where NATS user credentials were being
regenerated on every helm release update, rather than reusing existing
secrets. The fix implements the same secret reuse pattern that is
already used in the postgres app.

### Changes:
- Added `lookup` call to fetch existing credentials secret before
generating passwords
- Pre-populate passwords from existing secret data (base64 decoded)
- Only generate new random passwords for users that don't have existing
credentials

### Behavior:
- **Before**: Every helm upgrade would regenerate credentials for users
without explicit passwords, breaking existing connections
- **After**: Existing credentials are preserved across helm upgrades,
matching postgres app behavior

### 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
[nats] Fix credential regeneration on helm release updates by implementing existing secret lookup pattern
```

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

* **New Features**
* NATS deployments can now read and reuse existing release credentials,
reducing unnecessary credential rotation and keeping logins consistent
across updates.
* When credentials are missing, the system still auto-generates
passwords; when users are defined it emits the computed credentials for
use by the deployment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-06 14:11:04 +04:00
Isaiah Olson
627022972d 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>
2025-11-05 18:12:23 -06:00
Isaiah Olson
1e8a9ee980 Fix NATS app chart to use existing secret credentials when present
Signed-off-by: Isaiah Olson <isaiah@olson-network.com>
2025-11-05 18:12:22 -06:00
Andrei Kvapil
b45f4a6545 [dashboard] sync with upstream & enhancements
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-05 18:22:22 +01:00
Timofei Larkin
5b96190be8 [vpc] Entry per subnet in the subnets configmap (#1600)
### Release note

```release-note
[vpc] Change the subnets configmap structure from
.data.subnets==[]Subnet to .data==map[SubnetName]Subnet for simpler
representation in the dashboard.
```

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

## Summary by CodeRabbit

* **Refactor**
* Restructured VPC subnet data organization in configuration from a
static list format to a dynamic map structure, where each subnet is now
stored with its own key containing subnet name, ID, and CIDR
information.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-05 14:38:23 +04:00
Timofei Larkin
8849570f74 [system] Tune kubevirt rollout and eviction settings (#1544)
<!-- 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
Adds kubevirt settings:
`vmRolloutStrategy`: how changes to a manifest are propagated to a vm:
changes will be applied on-the-fly if possible (such as guest memory)
`workloadUpdateStrategy`: how vms will react to an eviction, less
disruptive method will be used.

### 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
Kubevirt rollout and eviction settings tuned
```

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

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Enhanced VM rollout strategy with Live Update support
* Introduced configurable workload update strategy with Live Migration
and Eviction options
  * Added batch eviction controls for optimized resource management

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-05 14:30:25 +04:00
Nikita
b6958320b2 [apps] vpc: more docs (#1594)
## What this PR does
Adds VPC details about bundles and required components for it to work.

### Release note
```release-note
More docs for VPC
```
2025-11-05 13:01:49 +03:00
Timofei Larkin
0a210bf5d3 [vpc] Entry per subnet in the subnets configmap
### Release note

```release-note
[vpc] Change the subnets configmap structure from
.data.subnets==[]Subnet to .data==map[SubnetName]Subnet for simpler
representation in the dashboard.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-05 13:01:15 +03:00
nbykov0
90d50fef48 [apps] vpc: more docs
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-11-05 12:21:00 +03:00
Andrei Kvapil
19ed058897 [dashboard-controller] Move bages generation logic to internal dashboard component (#1567)
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-controller] Move bages generation logic to internal dashboard component
```

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

* **Refactors**
* Dashboard badges now render with a unified ResourceBadge component
across columns and headers.
* Explicit badge size options removed; badges use simplified default
sizing.
* Badge payload simplified to a single value-driven field; abbreviation
and rendering are handled by the badge component.
* Stable per-kind color hashing removed; badges use streamlined,
consistent styling with optional color overrides.
* Column and header labels expanded to full descriptive names for
clearer navigation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 17:36:33 +01:00
Andrei Kvapil
6438ce98b1 Add QOSI to ADOPTERS.md (#1589)
```release-note
[adopters] Add QOSI to ADOPTERS.md
```

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

* **Documentation**
* Added a new adopter entry for QOSI (dated 2025-10-04) with a
descriptive use-case.
  * Removed an erroneous stray dash that preceded the new entry.
* Cleaned up formatting (trailing newline added); existing adopter list
otherwise unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 17:33:22 +01:00
Timofei Larkin
523d8ea638 [vpc] Give predictable name to subnet configmap (#1590)
## What this PR does

The new VPC feature creates a configmap, containing the list of subnets
under the VPC. However, the configmap has the VPC ID in its name, rather
than the name of the VPC, making it harder to target inside the
dashboard, as the helper functions from Helm (e.g. sha256sum) may not be
available in the dashboard's custom resources. This patch renames the
configmap to use the VPC's name.

### Release note

```release-note
[vpc] Change the subnet configmap name to a human-friendly value
(matching the VPC name), instead of being derived via a sha256sum,
making it easier to reference in the dashboard.
```
2025-11-04 20:27:57 +04:00
Andrei Kvapil
e89896fdba [flux] Close Flux Operator ports to external access (#1581)
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.
```

<!-- 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

* **Security**
* Added a cluster-wide network policy for the Flux Operator to block
external access to internal service ports (notably TCP 8080 and 8081)
while preserving intra-cluster communication.
* **Chores**
* Update process now applies the new network policy as part of Flux
Operator deployments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 17:13:43 +01:00
Andrei Kvapil
ab5101a713 [dashboard] Migrate patches to upstream project (#1569)
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] Migrate patches to upstream project
[dashboard] Fix nested lists in addtiionalProperties
```

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

## Summary by CodeRabbit

* **New Features**
* Enhanced form generation with improved type inference and nested
property resolution for dynamic form fields.

* **Bug Fixes**
* Fixed stream data handling issues and improved form field value
normalization.
* Better support for array item initialization with sensible defaults
based on field types.

* **Chores**
  * Updated API endpoints for namespace resource management.
  * Updated container images and configurations.
  * Improved tenant branding configuration structure.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 17:11:57 +01:00
Andrei Kvapil
af460f1c41 [dashboard-controller] Move bages generation logic to internal dashboard
component

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-04 17:11:31 +01:00
Timofei Larkin
634649f9ec [nats] Fixes for NATS App Helm chart, fix template issues with config.merge (#1583)
[nats] Fixes for NATS App Helm chart, fix template issues with
config.merge value

## What this PR does

This PR fixes two critical bugs in the NATS application chart
(`packages/apps/nats`) that prevented successful deployment when using
user authentication with custom configuration:

**Bug #1: YAML Parse Error with Empty config.merge**
- When users were defined but `config.merge` was empty (`{}`), the Helm
template would fail with YAML parse errors
- Fixed by adding safe existence checks before accessing
`.Values.config.merge` and preventing rendering of empty merge blocks

**Bug #2: Incorrect Config Nesting**
- When both `users` and `config.merge` were defined, custom NATS config
values (e.g., `max_payload`, `max_connections`) were incorrectly nested
as children of the `accounts` object instead of being placed at the root
NATS configuration level
- This caused NATS pods to crash with "Expected map entries for
accounts" errors
- Fixed by correcting the indentation from `nindent 12` to `nindent 10`
in the template

The related issue is #1354 

### Changes Made

**File**: `packages/apps/nats/templates/nats.yaml`

1. Line 55: Added safe existence checks for `.Values.config.merge`
2. Line 66: Added length validation to prevent rendering empty merge
objects
3. Line 67: Fixed indentation to ensure config values merge at root
level

### Impact

- Enables NATS deployments with user authentication and custom
configuration
- Fixes Stalwart mail server clustering scenarios that require NATS with
authentication
- Resolves HelmRelease failures in multi-tenant environments

### Testing

Can be tested with:
```yaml
apiVersion: apps.cozystack.io/v1alpha1
kind: NATS
metadata:
  name: test-nats
spec:
  replicas: 2
  users:
    testuser: {}
  config:
    merge:
      max_payload: 2097152
      max_connections: 500
```

Expected result: HelmRelease succeeds, NATS pods start successfully, and
configuration is valid.

---

### Release note

```release-note
[nats] Fix NATS application chart template bugs that prevented deployments with user authentication and custom configuration.
```


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

## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced configuration validation with improved guard conditions to
ensure proper handling of merge configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 20:00:17 +04:00
Timofei Larkin
df782fec9c [vpc] Give predictable name to subnet configmap
## What this PR does

The new VPC feature creates a configmap, containing the list of subnets
under the VPC. However, the configmap has the VPC ID in its name, rather
than the name of the VPC, making it harder to target inside the
dashboard, as the helper functions from Helm (e.g. sha256sum) may not be
available in the dashboard's custom resources. This patch renames the
configmap to use the VPC's name.

### Release note

```release-note
[vpc] Change the subnet configmap name to a human-friendly value
(matching the VPC name), instead of being derived via a sha256sum,
making it easier to reference in the dashboard.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-04 18:54:48 +03:00
Timofei Larkin
172774b6cd [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>
2025-11-04 18:22:47 +03:00
Timofei Larkin
62119eb761 [vpc] Install Multus by default (#1587)
## What this PR does

The recent patch introducing VPCs in Cozystack did not include enabling
Multus, which is a dependency for this feature. This patch enables
Multus by default in the paas-full bundle.

### Release-note

```release-note
[vpc] Enable Multus by default as a necessary dependency for VPCs.
```
2025-11-04 19:14:34 +04:00
IvanHunters
48c6e23ca0 add rule for success installing
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 17:56:49 +03:00
Timofei Larkin
9064a72c92 [vpc] Install Multus by default
## What this PR does

The recent patch introducing VPCs in Cozystack did not include enabling
Multus, which is a dependency for this feature. This patch enables
Multus by default in the paas-full bundles.

### Release-note

```release-note
[vpc] Enable Multus by default as a necessary dependency for VPCs.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-04 17:02:55 +03:00
Tamerlan Abu
dc06b16d11 add qosikz as adopters
Signed-off-by: Tamerlan Abu <tamerlanabu@gmail.com>
2025-11-04 17:52:52 +05:00
Andrei Kvapil
739a74dc28 [kubevirt] Fix: kubevirt metrics rule (#1584)
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
[kubevirt] Fix: kubevirt metrics rule
```

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

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed Prometheus alert rule expressions for virtual machine
monitoring. Corrected status and phase condition comparisons to
accurately identify when virtual machines are not running, ensuring
alerts trigger reliably in such scenarios. These improvements enhance
the accuracy of monitoring notifications.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 13:34:51 +01:00
Andrei Kvapil
723eefea66 [dashboard] Migrate patches to upstream project
[dashboard] Fix nested lists in addtiionalProperties

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-04 13:33:21 +01:00
Nikita
1d10907168 [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.
-->

## What this PR does
Removes Talos lldp extension. Please build a custom talos image with factory.talos.dev if you need it.

### 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
Talos lldp extension removed.
```

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

## Summary by CodeRabbit

* **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:52:33 +03:00
nbykov0
c19cddf08e [core] rm talos lldp extension
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-11-04 12:21:23 +03:00
Andrei Kvapil
4c08caafe1 [ingress] Enforce HTTPS-only for API (#1582)
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.
```

<!-- 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**
* Enforced SSL/TLS redirect for API ingress connections to enhance
security.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-04 10:18:37 +01:00
Andrei Kvapil
be58047aba [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:17:51 +01:00
IvanHunters
f60e2555c9 add patch
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 12:14:43 +03:00
Andrei Kvapil
6443a1264e [kubevirt] Fix: kubevirt metrics rule
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-04 10:13:33 +01:00
IvanHunters
52a23eacfc close metrics port for external
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 12:00:30 +03:00
IvanHunters
2634b01465 revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:32:18 +03:00
IvanHunters
15a3636d5f revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:29:55 +03:00
IvanHunters
ef43ef6753 revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:26:56 +03:00
IvanHunters
ba804b7c52 revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:24:38 +03:00
IvanHunters
9c5abf49ca revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:23:16 +03:00
IvanHunters
10e79651ef revert redis values and static image in the chart
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:21:10 +03:00
IvanHunters
965818efd4 fix crd
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 11:06:49 +03:00
Isaiah Olson
b1ebc9cc85 Fixes for NATS App Helm chart, fix template issues with config.merge value
Signed-off-by: Isaiah Olson <isaiah@olson-network.com>
2025-11-03 23:59:12 -06:00
IvanHunters
667c778f27 [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>
2025-11-04 02:29:24 +03:00
IvanHunters
77d95e3b91 fix generator scheme for redis image
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 02:12:06 +03:00
IvanHunters
a8d3cbce82 Fix values.schema.json for values.yaml by security fix
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 02:04:50 +03:00
IvanHunters
eea685065a [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>
2025-11-04 01:59:16 +03:00
IvanHunters
480f8027d7 [redis] Bump Redis image version for security fixes
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.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-11-04 01:50:11 +03:00
Timofei Larkin
19b56414a6 [api] Delete previous instance when changing type (#1579)
## What this PR does

It was observed during upgrades to the `cozystack-api` Helm release that
when enabling the local endpoint for the traffic locality feature, hence
switching from a deployment to a daemonset, the deployment may remain
unpruned and the pods of the deployment will continue to run
indefinitely. This patch adds a post-upgrade hook that explicitly
deletes the deployment in case it exists and was not pruned.

### Release-note

```release-note
[api] Delete the cozystack-api deployment in a post-upgrade hook when
migrating to a daemonset and vice-versa.
```

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

* **New Features**
* Automatic post-upgrade cleanup that removes outdated cluster resources
when the local Kubernetes API endpoint is disabled.
* Cleanup runs in the release namespace during upgrades and includes
necessary permissions for the cleanup job to complete.

* **Configuration**
* New release value toggles the local Kubernetes API endpoint to enable
or skip the cleanup behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 19:27:01 +04:00
Timofei Larkin
0f9806e9b0 [api] Delete previous instance when changing type
## What this PR does

It was observed during upgrades to the `cozystack-api` Helm release that
when enabling the local endpoint for the traffic locality feature, hence
switching from a deployment to a daemonset, the deployment may remain
unpruned and the pods of the deployment will continue to run
indefinitely. This patch adds a post-upgrade hook that explicitly deletes
the deployment in case it exists and was not pruned.

### Release-note

```release-note
[api] Delete the cozystack-api deployment in a post-upgrade hook when
migrating to a daemonset and vice-versa.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-11-03 17:22:38 +03:00
Andrei Kvapil
177073596c [tenant] Allow listing workloads (#1576)
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] Allow listing workload
```
2025-11-03 11:57:12 +01:00
Andrei Kvapil
93a9241899 [tenant] Allow listing workloads
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-03 11:56:00 +01:00
Andrei Kvapil
5401ae9734 [seaweedfs] Fix migration to v3.99 (#1572)
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
[seaweedfs] Fix migration to v3.99
```

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

## Summary by CodeRabbit

* **Chores**
  * Upgraded seaweedfs configuration to version 3.
* Updated pre-upgrade hook execution conditions to ensure proper upgrade
procedures.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 11:33:16 +01:00
Andrei Kvapil
b78d97f374 [nats] Merge container spec, not podTemplate (#1571)
## 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.
```

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

* **Chores**
* Restructured NATS deployment configuration while maintaining existing
functionality and resource settings; templates were reorganized to
streamline how container and resource definitions are represented. No
functional or behavioral changes are expected for deployments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 11:33:03 +01:00
Timofei Larkin
8b95db06ee [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>
2025-11-03 12:50:24 +03:00
Andrei Kvapil
5a2d4d7e66 [e2e] Increase Kubernetes connection timeouts (#1570)
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.
```

<!-- 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

* **Chores**
* Optimized infrastructure timeout and polling configurations to improve
deployment reliability and test execution efficiency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 10:48:10 +01:00
Andrei Kvapil
42e6f0e3f2 [seaweedfs] Fix migration to v3.99
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-03 10:41:39 +01:00
IvanHunters
e2eb1e267b [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>
2025-11-03 10:53:47 +03:00
Andrei Kvapil
2ac533f2f6 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-11-01 01:21:49 +05:00
Andrei Kvapil
ae9f9c57b1 Update LINSTOR v1.32.3
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-31 21:19:47 +01:00
Andrei Kvapil
18f253f77a [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.
-->

## What this PR does

- 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

### 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] Update openapi-ui v1.0.3 + fixes
```

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

* **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 22:09:36 +05:00
Andrei Kvapil
bd9dcb52a3 [dashboard] Add new patches
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-31 18:06:44 +01:00
Andrei Kvapil
be473a12be [dashboard] Update openapi-ui v1.0.3
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-31 18:05:59 +01:00
Timofei Larkin
8f5adcccf5 [system] Add VPC (#1543)
<!-- 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
Add VPC support

### 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
Added VPC support
```

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

* **New Features**
- Added Virtual Private Cloud (VPC) support with configurable subnets,
per-subnet network attachments, and generated subnet resources.
- Enabled subnet support for Virtual Machine and VM Instance: additional
interfaces, Multus networks, and conditional cloud-init/network secret
wiring for supported images.

* **Documentation**
- Added/updated docs and examples for VPC, Virtual Machine, and VM
Instance showing subnet parameters.

* **Chores**
- Expanded admin role permissions to manage Virtual Private Cloud
resources.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-31 19:40:50 +04:00
Andrei Kvapil
08bd918a10 [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 20:13:09 +05:00
nbykov0
023276ebab [apps] tenant: add vpcs to tenant roles
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-31 17:48:36 +03:00
nbykov0
19c4674ebb [apps] vm-instance: add vpc support
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-31 17:48:36 +03:00
nbykov0
202da193c0 [apps] virtual-machine: add vpc support
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-31 17:48:36 +03:00
nbykov0
cc9687707c [apps] Add VPC app
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-31 17:48:17 +03:00
Andrei Kvapil
ac10e35272 [seaweedfs] Update SeaweedFS v3.99 and deploy S3 as stacked service
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-31 13:58:03 +01:00
Andrei Kvapil
fc7d5ee71f [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 15:13:37 +05:00
Timofei Larkin
9d90503fb7 [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-10-31 12:59:13 +04:00
Andrei Kvapil
4be1c257d6 [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-30 23:21:48 +05:00
Timofei Larkin
f3ba8eca8e [dashboard] Revert reconciler removal
## 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.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-30 19:37:20 +03:00
IvanHunters
0f286ee7ba [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
[mariadb-operator] Add a post-delete hook job to clean up PVCs left after Helm release deletion.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-29 22:57:11 +03:00
nbykov0
ea74d7d59a [system] kubevirt: restore evictionStrategy
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-21 19:29:50 +03:00
nbykov0
74262977f6 [system] tune kubevirt rollout and eviction
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-21 19:18:57 +03:00
Andrei Kvapil
1e36722ab8 [seaweedfs] Allow users to discover their buckets
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-16 19:31:51 +02:00
99 changed files with 1620 additions and 1847 deletions

View File

@@ -31,4 +31,5 @@ This list is sorted in chronological order, based on the submission date.
| [gohost](https://gohost.kz/) | @karabass_off | 2024-02-01 | Our company has been working in the market of Kazakhstan for more than 15 years, providing clients with a standard set of services: VPS/VDC, IaaS, shared hosting, etc. Now we are expanding the lineup by introducing Bare Metal Kubenetes cluster under Cozystack management. |
| [Urmanac](https://urmanac.com) | @kingdonb | 2024-12-04 | Urmanac is the future home of a hosting platform for the knowledge base of a community of personal server enthusiasts. We use Cozystack to provide support services for web sites hosted using both conventional deployments and on SpinKube, with WASM. |
| [Hidora](https://hikube.cloud) | @matthieu-robin | 2025-09-17 | Hidora is a Swiss cloud provider delivering managed services and infrastructure solutions through datacenters located in Switzerland, ensuring data sovereignty and reliability. Its sovereign cloud platform, Hikube, is designed to run workloads with high availability across multiple datacenters, providing enterprises with a secure and scalable foundation for their applications based on Cozystack. |
|
| [QOSI](https://qosi.kz) | @tabu-a | 2025-10-04 | QOSI is a non-profit organization driving open-source adoption and digital sovereignty across Kazakhstan and Central Asia. We use Cozystack as a platform for deploying sovereign, GPU-enabled clouds and educational environments under the National AI Program. Our goal is to accelerate the regions transition toward open, self-hosted cloud-native technologies |
|

View File

@@ -229,6 +229,15 @@ func main() {
os.Exit(1)
}
dashboardManager := &dashboard.Manager{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err = dashboardManager.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DashboardReconciler")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
@@ -254,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

@@ -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

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)" {
@@ -18,3 +19,26 @@
curl -sS --fail 'http://localhost:21234/openapi/v2?timeout=32s' -H 'Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf' > /dev/null
)
}
@test "Test kinds" {
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
if [ "$val" != "TenantList" ]; then
echo "Expected kind to be TenantList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind')
if [ "$val" != "Tenant" ]; then
echo "Expected kind to be Tenant, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind')
if [ "$val" != "IngressList" ]; then
echo "Expected kind to be IngressList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind')
if [ "$val" != "Ingress" ]; then
echo "Expected kind to be Ingress, got $val"
exit 1
fi
}

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,94 @@ 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{},
}
// Process spec properties recursively
processSpecProperties(props, schema["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,155 @@
package dashboard
import (
"encoding/json"
"testing"
)
func TestBuildMultilineStringSchema(t *testing.T) {
// Test OpenAPI schema with various field types
openAPISchema := `{
"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 simpleString
simpleString, ok := props["simpleString"].(map[string]any)
if !ok {
t.Fatal("simpleString not found in 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 := props["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 := props["numberField"].(map[string]any); ok {
if numberField["type"] != nil {
t.Error("numberField should not have any type override")
}
}
// Check nested object
nestedObject, ok := props["nestedObject"].(map[string]any)
if !ok {
t.Fatal("nestedObject not found in 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 := props["arrayOfObjects"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects not found in 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,7 +140,7 @@ 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", ".status.loadBalancer.ingress[0].ip"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
@@ -148,7 +148,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// 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

@@ -0,0 +1,76 @@
---
apiVersion: batch/v1
kind: Job
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
name: {{ .Release.Name }}-datavolume-cleanup
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-datavolume-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
- kubectl -n {{ .Release.Namespace }} delete datavolumes
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}"
--ignore-not-found=true
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-datavolume-cleanup
annotations:
helm.sh/hook: post-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": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-datavolume-cleanup
rules:
- apiGroups:
- "cdi.kubevirt.io"
resources:
- datavolumes
verbs:
- get
- list
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-datavolume-cleanup
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-datavolume-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-datavolume-cleanup
namespace: {{ .Release.Namespace }}

View File

@@ -24,26 +24,26 @@ spec:
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
- >-
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
@@ -51,7 +51,7 @@ 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-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -75,6 +75,7 @@ rules:
- {{ .Release.Name }}-csi
- {{ .Release.Name }}-cert-manager
- {{ .Release.Name }}-cert-manager-crds
- {{ .Release.Name }}-gateway-api-crds
- {{ .Release.Name }}-vertical-pod-autoscaler
- {{ .Release.Name }}-vertical-pod-autoscaler-crds
- {{ .Release.Name }}-ingress-nginx

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

@@ -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
@@ -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
@@ -283,13 +284,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
@@ -357,13 +358,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

View File

@@ -36,29 +36,29 @@ virtctl ssh <user>@<vm>
### Common parameters
| Name | Description | Type | Value |
| ------------------------- | ------------------------------------------------------------------ | ---------- | ------------ |
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
| `running` | Whether the virtual machine should be running. | `bool` | `true` |
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
| `systemDisk` | System disk configuration. | `object` | `{}` |
| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` |
| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` |
| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` |
| `subnets` | Additional subnets (management subnet will be attached by default) | `[]object` | `[]` |
| `subnets[i].name` | Name of the subnet to attach | `string` | `""` |
| `gpus` | List of GPUs to attach. | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
| `cloudInit` | Cloud-init user data. | `string` | `""` |
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
| Name | Description | Type | Value |
| ------------------------- | ------------------------------------------------------- | ---------- | ------------ |
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
| `running` | Whether the virtual machine should be running. | `bool` | `true` |
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
| `systemDisk` | System disk configuration. | `object` | `{}` |
| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` |
| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` |
| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` |
| `subnets` | Additional subnets | `[]object` | `[]` |
| `subnets[i].name` | Subnet name | `string` | `""` |
| `gpus` | List of GPUs to attach. | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
| `cloudInit` | Cloud-init user data. | `string` | `""` |
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
## U Series

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

@@ -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

@@ -168,17 +168,14 @@
}
},
"subnets": {
"description": "Additional subnets (management subnet will be attached by default)",
"description": "Additional subnets",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "Name of the subnet to attach",
"description": "Subnet name",
"type": "string"
}
}

View File

@@ -18,6 +18,9 @@
## @field {string} storage - The size of the disk allocated for the virtual machine.
## @field {string} [storageClass] - StorageClass used to store the data.
## @typedef {struct} Subnet - Additional subnets
## @field {string} [name] - Subnet name
## @typedef {struct} GPU - GPU device configuration.
## @field {string} name - The name of the GPU resource to attach.
@@ -26,9 +29,6 @@
## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology).
## @field {quantity} [memory] - Amount of memory allocated.
## @typedef {struct} Subnet - Additional subnet to attach to
## @field {string} name - Name of the subnet to attach
## @param {bool} external - Enable external access from outside the cluster.
external: false
@@ -53,7 +53,7 @@ systemDisk:
storage: 5Gi
storageClass: replicated
## @param {[]Subnet} subnets - Additional subnets (management subnet will be attached by default)
## @param {[]Subnet} subnets - Additional subnets
subnets: []
## Example:
## subnets:

View File

@@ -47,10 +47,10 @@ virtctl ssh <user>@<vm>
| `disks` | List of disks to attach. | `[]object` | `[]` |
| `disks[i].name` | Disk name. | `string` | `""` |
| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` |
| `subnets` | Additional subnets | `[]object` | `[]` |
| `subnets[i].name` | Subnet name | `string` | `""` |
| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `subnets` | Additional subnets (management subnet will be attached by default) | `[]object` | `[]` |
| `subnets[i].name` | Name of the subnet to attach | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |

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

@@ -189,17 +189,14 @@
}
},
"subnets": {
"description": "Additional subnets (management subnet will be attached by default)",
"description": "Additional subnets",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "Name of the subnet to attach",
"description": "Subnet name",
"type": "string"
}
}

View File

@@ -10,6 +10,9 @@
## @field {string} name - Disk name.
## @field {string} [bus] - Disk bus type (e.g. "sata").
## @typedef {struct} Subnet - Additional subnets
## @field {string} [name] - Subnet name
## @typedef {struct} GPU - GPU device configuration.
## @field {string} name - The name of the GPU resource to attach.
@@ -18,9 +21,6 @@
## @field {quantity} [memory] - Amount of memory allocated.
## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology).
## @typedef {struct} Subnet - Additional subnet to attach to
## @field {string} name - Name of the subnet to attach
## @param {bool} external - Enable external access from outside the cluster.
external: false
@@ -48,13 +48,7 @@ disks: []
## - name: example-data
## bus: sata
## @param {[]GPU} gpus - List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).
gpus: []
## Example:
## gpus:
## - name: nvidia.com/GA102GL_A10
## @param {[]Subnet} subnets - Additional subnets (management subnet will be attached by default)
## @param {[]Subnet} subnets - Additional subnets
subnets: []
## Example:
## subnets:
@@ -62,13 +56,14 @@ subnets: []
## - name: subnet-aa8896b5
## - name: subnet-e9b97196
## @param {[]GPU} gpus - List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).
gpus: []
## Example:
## gpus:
## - name: nvidia.com/GA102GL_A10
## @param {Resources} [resources] - Resource configuration for the virtual machine.
resources: {}
## Example:
## resources:
## cpu: "4"
## sockets: "1"
## memory: "8Gi"
## @param {[]string} sshKeys - List of SSH public keys for authentication.
sshKeys: []

View File

@@ -0,0 +1,50 @@
# VPC
VPC offers a subset of dedicated subnets with networking services related to it.
As the service evolves, it will provide more ways to isolate your workloads.
## Service details
To function, the service requires kube-ovn and multus CNI to be present, so by default it will only work on `paas-full` bundle.
Kube-ovn provides VPC and Subnet resources and performs isolation and networking maintenance such as DHCP. Under the hood it uses ovn virtual routers and virtual switches.
Multus enables a multi-nic capability, so a pod or a VM could have two or more network interfaces.
Currently every workload will have a connection to a default management network which will also have a default gateway, and the majority of traffic will go through it.
VPC subnets are for now an additional dedicated networking spaces.
## Deployment notes
VPC name must be unique within a tenant.
Subnet name and ip address range must be unique within a VPC.
Subnet ip address space must not overlap with the default management network ip address range, subsets of 172.16.0.0/12 are recommended.
Currently there are no fail-safe checks, however they are planned for the future.
Different VPCs may have subnets with ovelapping ip address ranges.
A VM or a pod may be connected to multiple secondary Subnets at once. Each secondary connection will be represented as an additional network interface.
## Parameters
### Common parameters
| Name | Description | Type | Value |
| -------------------- | -------------------------------- | ------------------- | ------- |
| `subnets` | Subnets of a VPC | `map[string]object` | `{...}` |
| `subnets[name].cidr` | Subnet CIDR, e.g. 192.168.0.0/24 | `cidr` | `{}` |
## Examples
```yaml
apiVersion: apps.cozystack.io/v1alpha1
kind: VirtualPrivateCloud
metadata:
name: vpc00
spec:
subnets:
sub00:
cidr: 172.16.0.0/24
sub01:
cidr: 172.16.1.0/24
sub02:
cidr: 172.16.2.0/24
```

View File

@@ -58,15 +58,15 @@ spec:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $vpcId }}-subnets
name: {{ $.Release.Name }}-subnets
labels:
cozystack.io/vpcName: {{ $.Release.Name }}
cozystack.io/vpcId: {{ $vpcId }}
cozystack.io/tenantName: {{ $.Release.Namespace }}
data:
subnets: |
{{- range $subnetName, $subnetConfig := .Values.subnets }}
- subnetName: {{ $subnetName }}
subnetId: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
subnetCIDR: {{ $subnetConfig.cidr }}
{{- end }}
{{- range $subnetName, $subnetConfig := .Values.subnets }}
{{ $subnetName }}: |-
subnetName: {{ $subnetName }}
subnetId: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
subnetCIDR: {{ $subnetConfig.cidr }}
{{- end }}

View File

@@ -8,12 +8,9 @@
"default": {},
"additionalProperties": {
"type": "object",
"required": [
"cidr"
],
"properties": {
"cidr": {
"description": "Subnet CIDR, e.g. 192.168.0.0/24",
"description": "IP address range",
"type": "string"
}
}

View File

@@ -1,14 +1,15 @@
##
## @section Common parameters
##
## @typedef {struct} Subnet - Subnet of a VPC
## @field {string} cidr - Subnet CIDR, e.g. 192.168.0.0/24
## @field {string} [cidr] - IP address range
## @param {map[string]Subnet} subnets - Subnets of a VPC
##
subnets: {}
## Example:
## subnets:
## mysubnet0:
## cidr: "172.16.0.0/16"
## cidr: "172.16.0.0/24"
## mysubnet1:
## cidr: "172.17.0.0/16"
subnets: {}
## cidr: "172.16.1.0/24"

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:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: initramfs
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: installer
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: iso
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: kernel
imageOptions: {}

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

@@ -21,7 +21,6 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

@@ -76,6 +76,13 @@ releases:
namespace: cozy-kubeovn
dependsOn: [cilium,kubeovn]
- name: multus
releaseName: multus
chart: cozy-multus
namespace: cozy-multus
privileged: true
dependsOn: [cilium,kubeovn]
- name: cozy-proxy
releaseName: cozystack
chart: cozy-cozy-proxy

View File

@@ -40,6 +40,10 @@ releases:
chart: cozy-cozystack-api
namespace: cozy-system
dependsOn: [cozystack-controller]
values:
cozystackAPI:
localK8sAPIEndpoint:
enabled: false
- name: cozystack-controller
releaseName: cozystack-controller

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 }}

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

@@ -0,0 +1,87 @@
{{- $shouldRunUpdateHook := false }}
{{- $previousKind := "Deployment" }}
{{- $previousKindPlural := "deployments" }}
{{- if not .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
{{- $previousKind = "DaemonSet" }}
{{- $previousKindPlural = "daemonsets" }}
{{- end }}
{{- $previous := lookup "apps/v1" $previousKind .Release.Namespace "cozystack-api" }}
{{- if $previous }}
{{- $shouldRunUpdateHook = true }}
{{- end }}
{{- if $shouldRunUpdateHook }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
spec:
template:
metadata:
labels:
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: "cozystack-api-hook"
containers:
- name: kubectl
image: docker.io/alpine/k8s:1.33.4
command:
- sh
args:
- -exc
- |-
kubectl --namespace={{ .Release.Namespace }} delete --ignore-not-found \
{{ $previousKindPlural }}.apps cozystack-api
restartPolicy: Never
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
name: "cozystack-api-hook"
rules:
- apiGroups:
- "apps"
resources:
- "{{ $previousKindPlural }}"
verbs:
- get
- delete
resourceNames:
- "cozystack-api"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: "cozystack-api-hook"
subjects:
- kind: ServiceAccount
name: "cozystack-api-hook"
namespace: "{{ .Release.Namespace }}"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
{{- end }}

View File

@@ -8,7 +8,7 @@ spec:
plural: virtualmachines
singular: virtualmachine
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Whether the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets (management subnet will be attached by default)","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of the subnet to attach","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}}
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Whether the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}}
release:
prefix: virtual-machine-
labels:

View File

@@ -8,7 +8,7 @@ spec:
plural: virtualprivateclouds
singular: virtualprivatecloud
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"object","default":{},"additionalProperties":{"type":"object","required":["cidr"],"properties":{"cidr":{"description":"Subnet CIDR, e.g. 192.168.0.0/24","type":"string"}}}}}}
{"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"cidr":{"description":"IP address range","type":"string"}}}}}}
release:
prefix: "virtualprivatecloud-"
labels:

View File

@@ -8,7 +8,7 @@ spec:
singular: vminstance
plural: vminstances
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets (management subnet will be attached by default)","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of the subnet to attach","type":"string"}}}}}}
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}}
release:
prefix: vm-instance-
labels:
@@ -28,7 +28,7 @@ spec:
tags:
- compute
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "gpus"], ["spec", "subnets"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
secrets:
exclude: []
include: []

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=ba56271739505284aee569f914fc90e6a9c670da
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,21 +0,0 @@
diff --git a/src/endpoints/forms/formPrepare/formPrepare.ts b/src/endpoints/forms/formPrepare/formPrepare.ts
index 7e437db..90c40f6 100644
--- a/src/endpoints/forms/formPrepare/formPrepare.ts
+++ b/src/endpoints/forms/formPrepare/formPrepare.ts
@@ -15,6 +15,7 @@ export const prepareFormProps: RequestHandler = async (req: TPrepareFormReq, res
const filteredHeaders = { ...req.headers }
delete filteredHeaders['host'] // Avoid passing internal host header
+ delete filteredHeaders['content-length'] // This header causes "stream has been aborted"
const { data: formsOverridesData } = await userKubeApi.get<TFormsOverridesData>(
`/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/customformsoverrides`,
@@ -40,7 +41,7 @@ export const prepareFormProps: RequestHandler = async (req: TPrepareFormReq, res
},
)
- const { data: namespacesData } = await userKubeApi.get<TBuiltinResources>(`/api/v1/namespaces`, {
+ const { data: namespacesData } = await userKubeApi.get<TBuiltinResources>(`/apis/core.cozystack.io/v1alpha1/tenantnamespaces`, {
headers: {
// Authorization: `Bearer ${bearerToken}`,
// Cookie: cookies,

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

@@ -42,10 +42,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 +94,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 +126,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 +150,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:latest@sha256:77991f2482c0026d082582b22a8ffb191f3ba6fc948b2f125ef9b1081538f865
openapiUIK8sBff:
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.37.0@sha256:2b626dbbf87241e8621ac5b0285f402edbc2c2069ba254ca2ace2dd5c9248ac8
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:latest@sha256:8386f0747266726afb2b30db662092d66b0af0370e3becd8bee9684125fa9cc9
tokenProxy:
image: ghcr.io/cozystack/cozystack/token-proxy:v0.37.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b
image: ghcr.io/cozystack/cozystack/token-proxy:latest@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

@@ -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

@@ -22,7 +22,13 @@ spec:
- GPU
- VMExport
evictionStrategy: LiveMigrate
vmRolloutStrategy: LiveUpdate
workloadUpdateStrategy:
workloadUpdateMethods:
- LiveMigrate
- Evict
batchEvictionInterval: 1m
batchEvictionSize: 10
customizeComponents: {}
imagePullPolicy: IfNotPresent
monitorNamespace: tenant-root
workloadUpdateStrategy: {}

View File

@@ -3,8 +3,8 @@ name: piraeus
description: |
The Piraeus Operator manages software defined storage clusters using LINSTOR in Kubernetes.
type: application
version: 2.9.0
appVersion: "v2.9.0"
version: 2.9.1
appVersion: "v2.9.1"
maintainers:
- name: Piraeus Datastore
url: https://piraeus.io

View File

@@ -1,4 +1,4 @@
# DO NOT EDIT; Automatically created by hack/copy-image-config-to-chart.sh
# DO NOT EDIT; Automatically created by tools/copy-image-config-to-chart.sh
apiVersion: v1
kind: ConfigMap
metadata:
@@ -17,16 +17,16 @@ data:
# quay.io/piraeusdatastore/piraeus-server:v1.24.2
components:
linstor-controller:
tag: v1.31.3
tag: v1.32.3
image: piraeus-server
linstor-satellite:
tag: v1.31.3
tag: v1.32.3
image: piraeus-server
linstor-csi:
tag: v1.8.0
tag: v1.9.0
image: piraeus-csi
drbd-reactor:
tag: v1.8.0
tag: v1.9.0
image: drbd-reactor
ha-controller:
tag: v1.3.0
@@ -35,45 +35,45 @@ data:
tag: v1.0.0
image: drbd-shutdown-guard
ktls-utils:
tag: v1.1.0
tag: v1.2.1
image: ktls-utils
drbd-module-loader:
tag: v9.2.14
tag: v9.2.15
# The special "match" attribute is used to select an image based on the node's reported OS.
# The operator will first check the k8s node's ".status.nodeInfo.osImage" field, and compare it against the list
# here. If one matches, that specific image name will be used instead of the fallback image.
image: drbd9-noble # Fallback image: chose a recent kernel, which can hopefully compile whatever config is actually in use
match:
- osImage: Red Hat Enterprise Linux Server 7\.
image: drbd9-centos7
- osImage: Red Hat Enterprise Linux 8\.
image: drbd9-almalinux8
- osImage: Red Hat Enterprise Linux 9\.
image: drbd9-almalinux9
- osImage: Red Hat Enterprise Linux 10\.
image: drbd9-almalinux10
- osImage: "Red Hat Enterprise Linux CoreOS 41[3-9]"
image: drbd9-almalinux9
- osImage: Red Hat Enterprise Linux CoreOS
image: drbd9-almalinux8
- osImage: CentOS Linux 7
image: drbd9-centos7
- osImage: CentOS Linux 8
image: drbd9-almalinux8
- osImage: AlmaLinux 8
image: drbd9-almalinux8
- osImage: AlmaLinux 9
image: drbd9-almalinux9
- osImage: AlmaLinux 10
image: drbd9-almalinux10
- osImage: Oracle Linux Server 8\.
image: drbd9-almalinux8
- osImage: Oracle Linux Server 9\.
image: drbd9-almalinux9
- osImage: Oracle Linux Server 10\.
image: drbd9-almalinux10
- osImage: Rocky Linux 8
image: drbd9-almalinux8
- osImage: Rocky Linux 9
image: drbd9-almalinux9
- osImage: Ubuntu 18\.04
image: drbd9-bionic
- osImage: Ubuntu 20\.04
image: drbd9-focal
- osImage: Rocky Linux 10
image: drbd9-almalinux10
- osImage: Ubuntu 22\.04
image: drbd9-jammy
- osImage: Ubuntu 24\.04
@@ -82,32 +82,30 @@ data:
image: drbd9-bookworm
- osImage: Debian GNU/Linux 11
image: drbd9-bullseye
- osImage: Debian GNU/Linux 10
image: drbd9-buster
0_sig_storage_images.yaml: |
---
base: registry.k8s.io/sig-storage
components:
csi-attacher:
tag: v4.9.0
tag: v4.10.0
image: csi-attacher
csi-livenessprobe:
tag: v2.16.0
tag: v2.17.0
image: livenessprobe
csi-provisioner:
tag: v5.3.0
image: csi-provisioner
csi-snapshotter:
tag: v8.2.1
tag: v8.3.0
image: csi-snapshotter
csi-resizer:
tag: v1.13.2
tag: v1.14.0
image: csi-resizer
csi-external-health-monitor-controller:
tag: v0.15.0
tag: v0.16.0
image: csi-external-health-monitor-controller
csi-node-driver-registrar:
tag: v2.14.0
tag: v2.15.0
image: csi-node-driver-registrar
{{- range $idx, $value := .Values.imageConfigOverride }}
{{ add $idx 1 }}_helm_override.yaml: |

View File

@@ -1,11 +1,5 @@
{{ if .Values.serviceAccount.create }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "piraeus-operator.serviceAccountName" . }}
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
# DO NOT EDIT; Automatically created by tools/copy-rbac-config-to-chart.sh
{{ if .Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
@@ -14,448 +8,288 @@ metadata:
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
- events
- persistentvolumes
- secrets
- serviceaccounts
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
- persistentvolumeclaims
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- persistentvolumeclaims/status
verbs:
- patch
- apiGroups:
- ""
resources:
- pods
verbs:
- delete
- list
- watch
- apiGroups:
- ""
resources:
- pods/eviction
verbs:
- create
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- daemonsets
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- get
- apiGroups:
- cert-manager.io
resources:
- certificates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- internal.linstor.linbit.com
resources:
- '*'
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstorclusters
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstorclusters/finalizers
verbs:
- update
- apiGroups:
- piraeus.io
resources:
- linstorclusters/status
verbs:
- get
- patch
- update
- apiGroups:
- piraeus.io
resources:
- linstornodeconnections
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstornodeconnections/finalizers
verbs:
- update
- apiGroups:
- piraeus.io
resources:
- linstornodeconnections/status
verbs:
- get
- patch
- update
- apiGroups:
- piraeus.io
resources:
- linstorsatelliteconfigurations
verbs:
- get
- list
- watch
- apiGroups:
- piraeus.io
resources:
- linstorsatelliteconfigurations/status
verbs:
- get
- patch
- update
- apiGroups:
- piraeus.io
resources:
- linstorsatellites
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstorsatellites/finalizers
verbs:
- update
- apiGroups:
- piraeus.io
resources:
- linstorsatellites/status
verbs:
- get
- patch
- update
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
- clusterroles
- rolebindings
- roles
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- security.openshift.io
resourceNames:
- privileged
resources:
- securitycontextconstraints
verbs:
- use
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotclasses
- volumesnapshots
verbs:
- get
- list
- watch
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotcontents
verbs:
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotcontents/status
verbs:
- patch
- update
- apiGroups:
- storage.k8s.io
resources:
- csidrivers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- storage.k8s.io
resources:
- csinodes
verbs:
- get
- list
- patch
- watch
- apiGroups:
- storage.k8s.io
resources:
- csistoragecapacities
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- storage.k8s.io
resources:
- storageclasses
verbs:
- get
- list
- watch
- apiGroups:
- storage.k8s.io
resources:
- volumeattachments
verbs:
- delete
- get
- list
- patch
- watch
- apiGroups:
- storage.k8s.io
resources:
- volumeattachments/status
verbs:
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "piraeus-operator.fullname" . }}-manager-rolebinding
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: '{{ include "piraeus-operator.fullname" . }}-controller-manager'
subjects:
- kind: ServiceAccount
name: '{{ include "piraeus-operator.serviceAccountName" . }}'
namespace: '{{ .Release.Namespace }}'
{{ end }}
{{ if.Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "piraeus-operator.fullname" . }}-proxy-role
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "piraeus-operator.fullname" . }}-proxy-rolebinding
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: '{{ include "piraeus-operator.fullname" . }}-proxy-role'
subjects:
- kind: ServiceAccount
name: {{ include "piraeus-operator.serviceAccountName" . }}
namespace: '{{ .Release.Namespace }}'
- apiGroups:
- ""
resources:
- configmaps
- events
- persistentvolumes
- pods
- secrets
- serviceaccounts
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- nodes
- persistentvolumeclaims
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- persistentvolumeclaims/status
verbs:
- patch
- apiGroups:
- ""
resources:
- pods/eviction
verbs:
- create
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- daemonsets
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- get
- apiGroups:
- cert-manager.io
resources:
- certificates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- internal.linstor.linbit.com
resources:
- '*'
verbs:
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstorclusters
- linstornodeconnections
- linstorsatellites
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- piraeus.io
resources:
- linstorclusters/finalizers
- linstornodeconnections/finalizers
- linstorsatellites/finalizers
verbs:
- update
- apiGroups:
- piraeus.io
resources:
- linstorclusters/status
- linstornodeconnections/status
- linstorsatelliteconfigurations/status
- linstorsatellites/status
verbs:
- get
- patch
- update
- apiGroups:
- piraeus.io
resources:
- linstorsatelliteconfigurations
verbs:
- get
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
- clusterroles
- rolebindings
- roles
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- security.openshift.io
resourceNames:
- privileged
resources:
- securitycontextconstraints
verbs:
- use
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotclasses
- volumesnapshots
verbs:
- get
- list
- watch
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotcontents
verbs:
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- snapshot.storage.k8s.io
resources:
- volumesnapshotcontents/status
verbs:
- patch
- update
- apiGroups:
- storage.k8s.io
resources:
- csidrivers
- csistoragecapacities
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- storage.k8s.io
resources:
- csinodes
verbs:
- get
- list
- patch
- watch
- apiGroups:
- storage.k8s.io
resources:
- storageclasses
verbs:
- get
- list
- watch
- apiGroups:
- storage.k8s.io
resources:
- volumeattachments
verbs:
- delete
- get
- list
- patch
- watch
- apiGroups:
- storage.k8s.io
resources:
- volumeattachments/status
verbs:
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "piraeus-operator.fullname" . }}-leader-election-role
name: {{ include "piraeus-operator.fullname" . }}-leader-election
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "piraeus-operator.fullname" . }}-leader-election-rolebinding
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: '{{ include "piraeus-operator.fullname" . }}-leader-election-role'
subjects:
- kind: ServiceAccount
name: {{ include "piraeus-operator.serviceAccountName" . }}
namespace: '{{ .Release.Namespace }}'
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
{{ end }}

View File

@@ -0,0 +1,33 @@
{{ if .Values.rbac.create }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "piraeus-operator.fullname" . }}-leader-election
namespace: {{ .Release.Namespace }}
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "piraeus-operator.fullname" . }}-leader-election
subjects:
- kind: ServiceAccount
name: {{ include "piraeus-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "piraeus-operator.fullname" . }}-controller-manager
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "piraeus-operator.fullname" . }}-controller-manager
subjects:
- kind: ServiceAccount
name: {{ include "piraeus-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{ end }}

View File

@@ -0,0 +1,9 @@
{{ if .Values.serviceAccount.create }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "piraeus-operator.serviceAccountName" . }}
labels:
{{- include "piraeus-operator.labels" . | nindent 4 }}
{{ end }}

View File

@@ -1,12 +1,29 @@
NAME=seaweedfs-system
export NAME=seaweedfs-system
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
update:
rm -rf charts
mkdir -p charts
curl -sSL https://github.com/seaweedfs/seaweedfs/archive/refs/heads/master.tar.gz | \
tar xzvf - --strip 3 -C charts seaweedfs-master/k8s/charts/seaweedfs
version=$$(git ls-remote --tags --sort="v:refname" https://github.com/seaweedfs/seaweedfs | grep -v '\^{}' | grep 'refs/tags/[0-9]' | awk -F'/' 'END{print $$3}') && \
curl -sSL https://github.com/seaweedfs/seaweedfs/archive/refs/tags/$${version}.tar.gz | \
tar xzvf - --strip 3 -C charts seaweedfs-$${version}/k8s/charts/seaweedfs && \
sed -i.bak "/ARG VERSION/ s|=.*|=$${version}|g" images/seaweedfs/Dockerfile && \
rm -f images/seaweedfs/Dockerfile.bak
patch --no-backup-if-mismatch -p4 < patches/resize-api-server-annotation.diff
patch --no-backup-if-mismatch -p4 < patches/fix-volume-servicemonitor.patch
#patch --no-backup-if-mismatch -p4 < patches/retention-policy-delete.yaml
image:
docker buildx build images/seaweedfs \
--tag $(REGISTRY)/seaweedfs:$(call settag,$(TAG)) \
--cache-from type=registry,ref=$(REGISTRY)/seaweedfs:latest \
--cache-to type=inline \
--metadata-file images/seaweedfs.json \
$(BUILDX_ARGS)
REGISTRY="$(REGISTRY)" \
yq -i '.seaweedfs.image.registry = strenv(REGISTRY)' values.yaml
TAG=$(TAG)@$$(yq e '."containerimage.digest"' images/seaweedfs.json -o json -r) \
yq -i '.seaweedfs.image.tag = strenv(TAG)' values.yaml
yq -i '.global.imageName = "seaweedfs"' values.yaml
rm -f images/seaweedfs.json

View File

@@ -1,6 +1,6 @@
apiVersion: v1
description: SeaweedFS
name: seaweedfs
appVersion: "3.97"
appVersion: "3.99"
# Dev note: Trigger a helm chart release by `git tag -a helm-<version>`
version: 4.0.397
version: 4.0.399

View File

@@ -79,6 +79,12 @@ spec:
image: {{ template "master.image" . }}
imagePullPolicy: {{ default "IfNotPresent" .Values.global.imagePullPolicy }}
env:
{{- /* Determine default cluster alias and the corresponding env var keys to avoid conflicts */}}
{{- $envMerged := merge (.Values.global.extraEnvironmentVars | default dict) (.Values.allInOne.extraEnvironmentVars | default dict) }}
{{- $clusterDefault := default "sw" (index $envMerged "WEED_CLUSTER_DEFAULT") }}
{{- $clusterUpper := upper $clusterDefault }}
{{- $clusterMasterKey := printf "WEED_CLUSTER_%s_MASTER" $clusterUpper }}
{{- $clusterFilerKey := printf "WEED_CLUSTER_%s_FILER" $clusterUpper }}
- name: POD_IP
valueFrom:
fieldRef:
@@ -95,6 +101,7 @@ spec:
value: "{{ template "seaweedfs.name" . }}"
{{- if .Values.allInOne.extraEnvironmentVars }}
{{- range $key, $value := .Values.allInOne.extraEnvironmentVars }}
{{- if and (ne $key $clusterMasterKey) (ne $key $clusterFilerKey) }}
- name: {{ $key }}
{{- if kindIs "string" $value }}
value: {{ $value | quote }}
@@ -104,8 +111,10 @@ spec:
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.global.extraEnvironmentVars }}
{{- range $key, $value := .Values.global.extraEnvironmentVars }}
{{- if and (ne $key $clusterMasterKey) (ne $key $clusterFilerKey) }}
- name: {{ $key }}
{{- if kindIs "string" $value }}
value: {{ $value | quote }}
@@ -115,6 +124,12 @@ spec:
{{- end }}
{{- end }}
{{- end }}
{{- end }}
# Inject computed cluster endpoints for the default cluster
- name: {{ $clusterMasterKey }}
value: {{ include "seaweedfs.cluster.masterAddress" . | quote }}
- name: {{ $clusterFilerKey }}
value: {{ include "seaweedfs.cluster.filerAddress" . | quote }}
command:
- "/bin/sh"
- "-ec"

View File

@@ -15,7 +15,6 @@ spec:
selector:
matchLabels:
app.kubernetes.io/name: {{ template "seaweedfs.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: objectstorage-provisioner
template:

View File

@@ -28,8 +28,8 @@ spec:
rules:
- http:
paths:
- path: /sw-filer/?(.*)
pathType: ImplementationSpecific
- path: {{ .Values.filer.ingress.path | quote }}
pathType: {{ .Values.filer.ingress.pathType | quote }}
backend:
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
service:

View File

@@ -28,8 +28,8 @@ spec:
rules:
- http:
paths:
- path: /sw-master/?(.*)
pathType: ImplementationSpecific
- path: {{ .Values.master.ingress.path | quote }}
pathType: {{ .Values.master.ingress.pathType | quote }}
backend:
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
service:

View File

@@ -27,8 +27,8 @@ spec:
rules:
- http:
paths:
- path: /
pathType: ImplementationSpecific
- path: {{ .Values.s3.ingress.path | quote }}
pathType: {{ .Values.s3.ingress.pathType | quote }}
backend:
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
service:

View File

@@ -96,13 +96,16 @@ Inject extra environment vars in the format key:value, if populated
{{/* Computes the container image name for all components (if they are not overridden) */}}
{{- define "common.image" -}}
{{- $registryName := default .Values.image.registry .Values.global.registry | toString -}}
{{- $repositoryName := .Values.image.repository | toString -}}
{{- $repositoryName := default .Values.image.repository .Values.global.repository | toString -}}
{{- $name := .Values.global.imageName | toString -}}
{{- $tag := default .Chart.AppVersion .Values.image.tag | toString -}}
{{- if $repositoryName -}}
{{- $name = printf "%s/%s" (trimSuffix "/" $repositoryName) (base $name) -}}
{{- end -}}
{{- if $registryName -}}
{{- printf "%s/%s%s:%s" $registryName $repositoryName $name $tag -}}
{{- printf "%s/%s:%s" $registryName $name $tag -}}
{{- else -}}
{{- printf "%s%s:%s" $repositoryName $name $tag -}}
{{- printf "%s:%s" $name $tag -}}
{{- end -}}
{{- end -}}
@@ -219,3 +222,27 @@ or generate a new random password if it doesn't exist.
{{- randAlphaNum $length -}}
{{- end -}}
{{- end -}}
{{/*
Compute the master service address to be used in cluster env vars.
If allInOne is enabled, point to the all-in-one service; otherwise, point to the master service.
*/}}
{{- define "seaweedfs.cluster.masterAddress" -}}
{{- $serviceNameSuffix := "-master" -}}
{{- if .Values.allInOne.enabled -}}
{{- $serviceNameSuffix = "-all-in-one" -}}
{{- end -}}
{{- printf "%s%s.%s:%d" (include "seaweedfs.name" .) $serviceNameSuffix .Release.Namespace (int .Values.master.port) -}}
{{- end -}}
{{/*
Compute the filer service address to be used in cluster env vars.
If allInOne is enabled, point to the all-in-one service; otherwise, point to the filer-client service.
*/}}
{{- define "seaweedfs.cluster.filerAddress" -}}
{{- $serviceNameSuffix := "-filer-client" -}}
{{- if .Values.allInOne.enabled -}}
{{- $serviceNameSuffix = "-all-in-one" -}}
{{- end -}}
{{- printf "%s%s.%s:%d" (include "seaweedfs.name" .) $serviceNameSuffix .Release.Namespace (int .Values.filer.port) -}}
{{- end -}}

View File

@@ -21,9 +21,9 @@ metadata:
{{- with $.Values.global.monitoring.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if $.Values.volume.annotations }}
{{- with $volume.annotations }}
annotations:
{{- toYaml $.Values.volume.annotations | nindent 4 }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
endpoints:

View File

@@ -88,6 +88,9 @@ spec:
- name: {{ $dir.name }}
mountPath: /{{ $dir.name }}
{{- end }}
{{- if $volume.containerSecurityContext.enabled }}
securityContext: {{- omit $volume.containerSecurityContext "enabled" | toYaml | nindent 12 }}
{{- end }}
{{- end }}
{{- if $volume.initContainers }}
{{ tpl (printf "{{ $volumeName := \"%s\" }}%s" $volumeName $volume.initContainers) $ | indent 8 | trim }}

View File

@@ -3,6 +3,7 @@
global:
createClusterRole: true
registry: ""
# if repository is set, it overrides the namespace part of imageName
repository: ""
imageName: chrislusf/seaweedfs
imagePullPolicy: IfNotPresent
@@ -201,8 +202,7 @@ master:
# nodeSelector labels for master pod assignment, formatted as a muli-line string.
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
# Example:
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
# nodeSelector: |
# sw-backend: "true"
@@ -238,6 +238,8 @@ master:
className: "nginx"
# host: false for "*" hostname
host: "master.seaweedfs.local"
path: "/sw-master/?(.*)"
pathType: ImplementationSpecific
annotations:
nginx.ingress.kubernetes.io/auth-type: "basic"
nginx.ingress.kubernetes.io/auth-secret: "default/ingress-basic-auth-secret"
@@ -478,8 +480,7 @@ volume:
# nodeSelector labels for server pod assignment, formatted as a muli-line string.
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
# Example:
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
# nodeSelector: |
# sw-volume: "true"
@@ -735,8 +736,7 @@ filer:
# nodeSelector labels for server pod assignment, formatted as a muli-line string.
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
# Example:
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
# nodeSelector: |
# sw-backend: "true"
@@ -772,6 +772,8 @@ filer:
className: "nginx"
# host: false for "*" hostname
host: "seaweedfs.cluster.local"
path: "/sw-filer/?(.*)"
pathType: ImplementationSpecific
annotations:
nginx.ingress.kubernetes.io/backend-protocol: GRPC
nginx.ingress.kubernetes.io/auth-type: "basic"
@@ -871,7 +873,7 @@ filer:
# anonymousRead: false
s3:
enabled: false
enabled: true
imageOverride: null
restartPolicy: null
replicas: 1
@@ -932,8 +934,7 @@ s3:
# nodeSelector labels for server pod assignment, formatted as a muli-line string.
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
# Example:
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
# nodeSelector: |
# sw-backend: "true"
@@ -975,6 +976,11 @@ s3:
extraEnvironmentVars:
# Custom command line arguments to add to the s3 command
# Example to fix connection idle seconds:
extraArgs: ["-idleTimeout=30"]
# extraArgs: []
# used to configure livenessProbe on s3 containers
#
livenessProbe:
@@ -1006,6 +1012,8 @@ s3:
className: "nginx"
# host: false for "*" hostname
host: "seaweedfs.cluster.local"
path: "/"
pathType: Prefix
# additional ingress annotations for the s3 endpoint
annotations: {}
tls: []
@@ -1051,8 +1059,7 @@ sftp:
annotations: {}
resources: {}
tolerations: ""
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
priorityClassName: ""
serviceAccountName: ""
podSecurityContext: {}
@@ -1179,8 +1186,7 @@ allInOne:
# nodeSelector labels for master pod assignment, formatted as a muli-line string.
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: |
kubernetes.io/arch: amd64
nodeSelector: ""
# Used to assign priority to master pods
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/

View File

@@ -0,0 +1,2 @@
ARG VERSION=3.99
FROM chrislusf/seaweedfs:${VERSION}

View File

@@ -1,15 +0,0 @@
diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-servicemonitor.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-servicemonitor.yaml
--- a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-servicemonitor.yaml (revision 8951bc13d7d02b5e6982a239570ed58ed7cb025a)
+++ b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-servicemonitor.yaml (revision fa4fff2292c4b79a92db5cd654a3c6bf590252a6)
@@ -21,9 +21,9 @@
{{- with $.Values.global.monitoring.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
-{{- if .Values.volume.annotations }}
+{{- if $.Values.volume.annotations }}
annotations:
- {{- toYaml .Values.volume.annotations | nindent 4 }}
+ {{- toYaml $.Values.volume.annotations | nindent 4 }}
{{- end }}
spec:
endpoints:

View File

@@ -2,7 +2,7 @@
{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace "seaweedfs-deployed-version" }}
{{- if $configMap }}
{{- $deployedVersion := dig "data" "version" "0" $configMap }}
{{- if ge $deployedVersion "2" }}
{{- if ge $deployedVersion "3" }}
{{- $shouldRunUpdateHook = false }}
{{- end }}
{{- end }}

View File

@@ -3,4 +3,4 @@ kind: ConfigMap
metadata:
name: seaweedfs-deployed-version
data:
version: "2"
version: "3"

View File

@@ -1,12 +1,16 @@
global:
enableSecurity: true
serviceAccountName: "tenant-foo-seaweedfs"
imageName: "seaweedfs"
extraEnvironmentVars:
WEED_CLUSTER_SW_MASTER: "seaweedfs-master:9333"
WEED_CLUSTER_SW_FILER: "seaweedfs-filer-client:8888"
monitoring:
enabled: true
seaweedfs:
image:
tag: "latest@sha256:944e9bff98b088773847270238b63ce57dc5291054814d08e0226a139b3affb2"
registry: ghcr.io/cozystack/cozystack
master:
volumeSizeLimitMB: 30000
replicas: 3
@@ -83,7 +87,7 @@ seaweedfs:
existingConfigSecret: null
auditLogConfig: {}
s3:
enabled: true
enabled: false
extraArgs:
- -idleTimeout=60
enableAuth: false

View File

@@ -59,11 +59,9 @@ func RegisterStaticTypes(scheme *runtime.Scheme) {
&TenantNamespaceList{},
&TenantSecret{},
&TenantSecretList{},
&TenantSecretsTable{},
&TenantSecretsTableList{},
&TenantModule{},
&TenantModuleList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
klog.V(1).Info("Registered static kinds: TenantNamespace, TenantSecret, TenantSecretsTable, TenantModule")
klog.V(1).Info("Registered static kinds: TenantNamespace, TenantSecret, TenantModule")
}

View File

@@ -1,34 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// TenantSecretEntry represents a single key from a Secret's data.
type TenantSecretEntry struct {
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// TenantSecretsTable is a virtual, namespaced resource that exposes every key
// of Secrets labelled cozystack.io/ui=true as a separate object.
type TenantSecretsTable struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Data TenantSecretEntry `json:"data,omitempty"`
}
// DeepCopy methods are generated by deepcopy-gen
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TenantSecretsTableList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []TenantSecretsTable `json:"items"`
}
// DeepCopy methods are generated by deepcopy-gen

View File

@@ -216,22 +216,6 @@ func (in *TenantSecret) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantSecretEntry) DeepCopyInto(out *TenantSecretEntry) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretEntry.
func (in *TenantSecretEntry) DeepCopy() *TenantSecretEntry {
if in == nil {
return nil
}
out := new(TenantSecretEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantSecretList) DeepCopyInto(out *TenantSecretList) {
*out = *in
@@ -264,63 +248,3 @@ func (in *TenantSecretList) DeepCopyObject() runtime.Object {
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantSecretsTable) DeepCopyInto(out *TenantSecretsTable) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Data = in.Data
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretsTable.
func (in *TenantSecretsTable) DeepCopy() *TenantSecretsTable {
if in == nil {
return nil
}
out := new(TenantSecretsTable)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TenantSecretsTable) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantSecretsTableList) DeepCopyInto(out *TenantSecretsTableList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TenantSecretsTable, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretsTableList.
func (in *TenantSecretsTableList) DeepCopy() *TenantSecretsTableList {
if in == nil {
return nil
}
out := new(TenantSecretsTableList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TenantSecretsTableList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@@ -44,7 +44,6 @@ import (
tenantmodulestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantmodule"
tenantnamespacestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantnamespace"
tenantsecretstorage "github.com/cozystack/cozystack/pkg/registry/core/tenantsecret"
tenantsecretstablestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantsecretstable"
)
var (
@@ -177,9 +176,6 @@ func (c completedConfig) New() (*CozyServer, error) {
coreV1alpha1Storage["tenantsecrets"] = cozyregistry.RESTInPeace(
tenantsecretstorage.NewREST(cli, watchCli),
)
coreV1alpha1Storage["tenantsecretstables"] = cozyregistry.RESTInPeace(
tenantsecretstablestorage.NewREST(cli, watchCli),
)
coreV1alpha1Storage["tenantmodules"] = cozyregistry.RESTInPeace(
tenantmodulestorage.NewREST(cli, watchCli),
)

View File

@@ -39,10 +39,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantNamespace": schema_pkg_apis_core_v1alpha1_TenantNamespace(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantNamespaceList": schema_pkg_apis_core_v1alpha1_TenantNamespaceList(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecret": schema_pkg_apis_core_v1alpha1_TenantSecret(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry": schema_pkg_apis_core_v1alpha1_TenantSecretEntry(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretList": schema_pkg_apis_core_v1alpha1_TenantSecretList(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable": schema_pkg_apis_core_v1alpha1_TenantSecretsTable(ref),
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTableList": schema_pkg_apis_core_v1alpha1_TenantSecretsTableList(ref),
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest": schema_pkg_apis_apiextensions_v1_ConversionRequest(ref),
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse": schema_pkg_apis_apiextensions_v1_ConversionResponse(ref),
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionReview": schema_pkg_apis_apiextensions_v1_ConversionReview(ref),
@@ -557,37 +554,6 @@ func schema_pkg_apis_core_v1alpha1_TenantSecret(ref common.ReferenceCallback) co
}
}
func schema_pkg_apis_core_v1alpha1_TenantSecretEntry(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "TenantSecretEntry represents a single key from a Secret's data.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"key": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"value": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_core_v1alpha1_TenantSecretList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -636,95 +602,6 @@ func schema_pkg_apis_core_v1alpha1_TenantSecretList(ref common.ReferenceCallback
}
}
func schema_pkg_apis_core_v1alpha1_TenantSecretsTable(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "TenantSecretsTable is a virtual, namespaced resource that exposes every key of Secrets labelled cozystack.io/ui=true as a separate object.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"data": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry"),
},
},
},
},
},
Dependencies: []string{
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_pkg_apis_core_v1alpha1_TenantSecretsTableList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable"),
},
},
},
},
},
},
Required: []string{"items"},
},
},
Dependencies: []string{
"github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_pkg_apis_apiextensions_v1_ConversionRequest(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -1196,12 +1196,18 @@ func (r *REST) Destroy() {
// New creates a new instance of Application
func (r *REST) New() runtime.Object {
return &appsv1alpha1.Application{}
app := &unstructured.UnstructuredList{}
app.SetAPIVersion("apps.cozystack.io/v1alpha1")
app.SetKind(r.kindName)
return app
}
// NewList returns an empty list of Application objects
func (r *REST) NewList() runtime.Object {
return &appsv1alpha1.ApplicationList{}
appList := &unstructured.UnstructuredList{}
appList.SetAPIVersion("apps.cozystack.io/v1alpha1")
appList.SetKind(r.kindName + "List")
return appList
}
// Kind returns the resource kind used for API discovery

View File

@@ -9,7 +9,7 @@ import (
"encoding/base64"
"fmt"
"net/http"
"sort"
"slices"
"time"
corev1 "k8s.io/api/core/v1"
@@ -226,6 +226,9 @@ func (r *REST) Get(
if err != nil {
return nil, err
}
if sec.Labels == nil || sec.Labels[tsLabelKey] != tsLabelValue {
return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
return secretToTenant(sec), nil
}
@@ -253,11 +256,13 @@ func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtim
list := &corev1.SecretList{}
err = r.c.List(ctx, list,
&client.ListOptions{
Namespace: ns,
Namespace: ns,
LabelSelector: ls,
Raw: &metav1.ListOptions{
LabelSelector: ls.String(),
FieldSelector: fieldSel,
}})
},
})
if err != nil {
return nil, err
}
@@ -273,7 +278,17 @@ func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtim
for i := range list.Items {
out.Items = append(out.Items, *secretToTenant(&list.Items[i]))
}
sort.Slice(out.Items, func(i, j int) bool { return out.Items[i].Name < out.Items[j].Name })
slices.SortFunc(out.Items, func(a, b corev1alpha1.TenantSecret) int {
aKey := fmt.Sprintf("%s/%s", a.Namespace, a.Name)
bKey := fmt.Sprintf("%s/%s", b.Namespace, b.Name)
switch {
case aKey < bKey:
return -1
case aKey > bKey:
return 1
}
return 0
})
return out, nil
}
@@ -291,10 +306,17 @@ func (r *REST) Update(
return nil, false, err
}
cur := &corev1.Secret{}
err = r.c.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, cur, &client.GetOptions{Raw: &metav1.GetOptions{}})
if err != nil && !apierrors.IsNotFound(err) {
return nil, false, err
var cur *corev1.Secret
previous := &corev1.Secret{}
if err := r.c.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, previous, &client.GetOptions{Raw: &metav1.GetOptions{}}); err != nil {
if !apierrors.IsNotFound(err) {
return nil, false, err
}
} else {
if previous.Labels == nil || previous.Labels[tsLabelKey] != tsLabelValue {
return nil, false, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
cur = previous
}
newObj, err := objInfo.UpdatedObject(ctx, nil)
@@ -306,7 +328,7 @@ func (r *REST) Update(
newSec := tenantToSecret(in, cur)
newSec.Namespace = ns
if cur == nil {
if !forceCreate && err == nil {
if !forceCreate {
return nil, false, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
err := r.c.Create(ctx, newSec, &client.CreateOptions{Raw: &metav1.CreateOptions{}})
@@ -328,6 +350,13 @@ func (r *REST) Delete(
if err != nil {
return nil, false, err
}
current := &corev1.Secret{}
if err := r.c.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, current, &client.GetOptions{Raw: &metav1.GetOptions{}}); err != nil {
return nil, false, err
}
if current.Labels == nil || current.Labels[tsLabelKey] != tsLabelValue {
return nil, false, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
err = r.c.Delete(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}}, &client.DeleteOptions{Raw: opts})
return nil, err == nil, err
}
@@ -347,6 +376,13 @@ func (r *REST) Patch(
if err != nil {
return nil, err
}
current := &corev1.Secret{}
if err := r.c.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, current, &client.GetOptions{Raw: &metav1.GetOptions{}}); err != nil {
return nil, err
}
if current.Labels == nil || current.Labels[tsLabelKey] != tsLabelValue {
return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
out := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
@@ -383,12 +419,16 @@ func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch
}
secList := &corev1.SecretList{}
ls := labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String()
base, err := r.w.Watch(ctx, secList, &client.ListOptions{Namespace: ns, Raw: &metav1.ListOptions{
Watch: true,
LabelSelector: ls,
ResourceVersion: opts.ResourceVersion,
}})
ls := labels.Set{tsLabelKey: tsLabelValue}.AsSelector()
base, err := r.w.Watch(ctx, secList, &client.ListOptions{
Namespace: ns,
LabelSelector: ls,
Raw: &metav1.ListOptions{
Watch: true,
LabelSelector: ls.String(),
ResourceVersion: opts.ResourceVersion,
},
})
if err != nil {
return nil, err
}

View File

@@ -1,335 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// TenantSecretsTable registry namespaced, read-only flattened view over
// Secrets labelled "internal.cozystack.io/tenantresource=true". Each data key is a separate object.
package tenantsecretstable
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"sort"
"time"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1"
)
const (
tsLabelKey = corev1alpha1.TenantResourceLabelKey
tsLabelValue = corev1alpha1.TenantResourceLabelValue
kindObj = "TenantSecretsTable"
kindObjList = "TenantSecretsTableList"
singularName = "tenantsecretstable"
resourcePlural = "tenantsecretstables"
)
type REST struct {
c client.Client
w client.WithWatch
gvr schema.GroupVersionResource
}
func NewREST(c client.Client, w client.WithWatch) *REST {
return &REST{
c: c,
w: w,
gvr: schema.GroupVersionResource{
Group: corev1alpha1.GroupName,
Version: "v1alpha1",
Resource: resourcePlural,
},
}
}
var (
_ rest.Getter = &REST{}
_ rest.Lister = &REST{}
_ rest.Watcher = &REST{}
_ rest.TableConvertor = &REST{}
_ rest.Scoper = &REST{}
_ rest.SingularNameProvider = &REST{}
_ rest.Storage = &REST{}
)
func (*REST) NamespaceScoped() bool { return true }
func (*REST) New() runtime.Object { return &corev1alpha1.TenantSecretsTable{} }
func (*REST) NewList() runtime.Object {
return &corev1alpha1.TenantSecretsTableList{}
}
func (*REST) Kind() string { return kindObj }
func (r *REST) GroupVersionKind(_ schema.GroupVersion) schema.GroupVersionKind {
return r.gvr.GroupVersion().WithKind(kindObj)
}
func (*REST) GetSingularName() string { return singularName }
func (*REST) Destroy() {}
func nsFrom(ctx context.Context) (string, error) {
ns, ok := request.NamespaceFrom(ctx)
if !ok {
return "", fmt.Errorf("namespace required")
}
return ns, nil
}
// -----------------------
// Get/List
// -----------------------
func (r *REST) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) {
ns, err := nsFrom(ctx)
if err != nil {
return nil, err
}
// We need to identify secret name and key. Iterate secrets in namespace with tenant secret label
// and return the matching composed object.
list := &corev1.SecretList{}
err = r.c.List(ctx, list,
&client.ListOptions{
Namespace: ns,
Raw: &metav1.ListOptions{
LabelSelector: labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String(),
},
})
if err != nil {
return nil, err
}
for i := range list.Items {
sec := &list.Items[i]
for k, v := range sec.Data {
composed := composedName(sec.Name, k)
if composed == name {
return secretKeyToObj(sec, k, v), nil
}
}
}
return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name)
}
func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtime.Object, error) {
ns, err := nsFrom(ctx)
if err != nil {
return nil, err
}
sel := labels.NewSelector()
req, _ := labels.NewRequirement(tsLabelKey, selection.Equals, []string{tsLabelValue})
sel = sel.Add(*req)
if opts.LabelSelector != nil {
if reqs, _ := opts.LabelSelector.Requirements(); len(reqs) > 0 {
sel = sel.Add(reqs...)
}
}
fieldSel := ""
if opts.FieldSelector != nil {
fieldSel = opts.FieldSelector.String()
}
list := &corev1.SecretList{}
err = r.c.List(ctx, list,
&client.ListOptions{
Namespace: ns,
Raw: &metav1.ListOptions{
LabelSelector: labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String(),
FieldSelector: fieldSel,
},
})
if err != nil {
return nil, err
}
out := &corev1alpha1.TenantSecretsTableList{
TypeMeta: metav1.TypeMeta{APIVersion: corev1alpha1.SchemeGroupVersion.String(), Kind: kindObjList},
ListMeta: list.ListMeta,
}
for i := range list.Items {
sec := &list.Items[i]
// Ensure stable ordering of keys
keys := make([]string, 0, len(sec.Data))
for k := range sec.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := sec.Data[k]
o := secretKeyToObj(sec, k, v)
out.Items = append(out.Items, *o)
}
}
sort.Slice(out.Items, func(i, j int) bool { return out.Items[i].Name < out.Items[j].Name })
return out, nil
}
// -----------------------
// Watch
// -----------------------
func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch.Interface, error) {
ns, err := nsFrom(ctx)
if err != nil {
return nil, err
}
secList := &corev1.SecretList{}
ls := labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String()
base, err := r.w.Watch(ctx, secList, &client.ListOptions{Namespace: ns, Raw: &metav1.ListOptions{
Watch: true,
LabelSelector: ls,
ResourceVersion: opts.ResourceVersion,
}})
if err != nil {
return nil, err
}
ch := make(chan watch.Event)
proxy := watch.NewProxyWatcher(ch)
go func() {
defer proxy.Stop()
for ev := range base.ResultChan() {
sec, ok := ev.Object.(*corev1.Secret)
if !ok || sec == nil {
continue
}
// Emit an event per key
for k, v := range sec.Data {
obj := secretKeyToObj(sec, k, v)
ch <- watch.Event{Type: ev.Type, Object: obj}
}
}
}()
return proxy, nil
}
// -----------------------
// TableConvertor
// -----------------------
func (r *REST) ConvertToTable(_ context.Context, obj runtime.Object, _ runtime.Object) (*metav1.Table, error) {
now := time.Now()
row := func(o *corev1alpha1.TenantSecretsTable) metav1.TableRow {
return metav1.TableRow{
Cells: []interface{}{o.Name, o.Data.Name, o.Data.Key, humanAge(o.CreationTimestamp.Time, now)},
Object: runtime.RawExtension{Object: o},
}
}
tbl := &metav1.Table{
TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1", Kind: "Table"},
ColumnDefinitions: []metav1.TableColumnDefinition{
{Name: "NAME", Type: "string"},
{Name: "SECRET", Type: "string"},
{Name: "KEY", Type: "string"},
{Name: "AGE", Type: "string"},
},
}
switch v := obj.(type) {
case *corev1alpha1.TenantSecretsTableList:
for i := range v.Items {
tbl.Rows = append(tbl.Rows, row(&v.Items[i]))
}
tbl.ListMeta.ResourceVersion = v.ListMeta.ResourceVersion
case *corev1alpha1.TenantSecretsTable:
tbl.Rows = append(tbl.Rows, row(v))
tbl.ListMeta.ResourceVersion = v.ResourceVersion
default:
return nil, notAcceptable{r.gvr.GroupResource(), fmt.Sprintf("unexpected %T", obj)}
}
return tbl, nil
}
// -----------------------
// Helpers
// -----------------------
func composedName(secretName, key string) string {
return secretName + "-" + key
}
func humanAge(t time.Time, now time.Time) string {
d := now.Sub(t)
// simple human duration
if d.Hours() >= 24 {
return fmt.Sprintf("%dd", int(d.Hours()/24))
}
if d.Hours() >= 1 {
return fmt.Sprintf("%dh", int(d.Hours()))
}
if d.Minutes() >= 1 {
return fmt.Sprintf("%dm", int(d.Minutes()))
}
return fmt.Sprintf("%ds", int(d.Seconds()))
}
func secretKeyToObj(sec *corev1.Secret, key string, val []byte) *corev1alpha1.TenantSecretsTable {
return &corev1alpha1.TenantSecretsTable{
TypeMeta: metav1.TypeMeta{APIVersion: corev1alpha1.SchemeGroupVersion.String(), Kind: kindObj},
ObjectMeta: metav1.ObjectMeta{
Name: sec.Name,
Namespace: sec.Namespace,
UID: sec.UID,
ResourceVersion: sec.ResourceVersion,
CreationTimestamp: sec.CreationTimestamp,
Labels: filterUserLabels(sec.Labels),
Annotations: sec.Annotations,
},
Data: corev1alpha1.TenantSecretEntry{
Name: sec.Name,
Key: key,
Value: toBase64String(val),
},
}
}
func filterUserLabels(m map[string]string) map[string]string {
if m == nil {
return nil
}
out := make(map[string]string, len(m))
for k, v := range m {
if k == tsLabelKey {
continue
}
out[k] = v
}
return out
}
func toBase64String(b []byte) string {
const enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// Minimal base64 encoder to avoid extra deps; for readability we could use stdlib encoding/base64
// but keeping inline is fine; however using stdlib is clearer.
// Using stdlib:
return base64.StdEncoding.EncodeToString(b)
}
type notAcceptable struct {
resource schema.GroupResource
message string
}
func (e notAcceptable) Error() string { return e.message }
func (e notAcceptable) Status() metav1.Status {
return metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusNotAcceptable,
Reason: metav1.StatusReason("NotAcceptable"),
Message: e.message,
}
}