Compare commits

...

64 Commits

Author SHA1 Message Date
Andrei Kvapil
156c1e8524 PoC: Move workloadmonitors to controller
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-04 19:31:31 +01: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
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
118 changed files with 2363 additions and 1287 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

@@ -59,6 +59,10 @@ type CozystackResourceDefinitionSpec struct {
// Dashboard configuration for this resource
Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"`
// WorkloadMonitors configuration for this resource
// List of WorkloadMonitor templates to be created for each application instance
WorkloadMonitors []WorkloadMonitorTemplate `json:"workloadMonitors,omitempty"`
}
type CozystackResourceDefinitionChart struct {
@@ -110,17 +114,18 @@ type CozystackResourceDefinitionRelease struct {
// - {{ .namespace }}: The namespace of the resource being processed
//
// Example YAML:
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
//
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
type CozystackResourceDefinitionResourceSelector struct {
metav1.LabelSelector `json:",inline"`
// ResourceNames is a list of resource names to match
@@ -191,3 +196,47 @@ type CozystackResourceDefinitionDashboard struct {
// +optional
Module bool `json:"module,omitempty"`
}
// ---- WorkloadMonitor types ----
// WorkloadMonitorTemplate defines a template for creating WorkloadMonitor resources
// for application instances. Fields support Go template syntax with the following variables:
// - {{ .Release.Name }}: The name of the Helm release
// - {{ .Release.Namespace }}: The namespace of the Helm release
// - {{ .Chart.Version }}: The version of the Helm chart
// - {{ .Values.<path> }}: Any value from the Helm values
type WorkloadMonitorTemplate struct {
// Name is the name of the WorkloadMonitor.
// Supports Go template syntax (e.g., "{{ .Release.Name }}-keeper")
// +required
Name string `json:"name"`
// Kind specifies the kind of the workload (e.g., "postgres", "kafka")
// +required
Kind string `json:"kind"`
// Type specifies the type of the workload (e.g., "postgres", "zookeeper")
// +required
Type string `json:"type"`
// Selector is a map of label key-value pairs for matching workloads.
// Supports Go template syntax in values (e.g., "app.kubernetes.io/instance: {{ .Release.Name }}")
// +required
Selector map[string]string `json:"selector"`
// Replicas is a Go template expression that evaluates to the desired number of replicas.
// Example: "{{ .Values.replicas }}" or "{{ .Values.clickhouseKeeper.replicas }}"
// +optional
Replicas string `json:"replicas,omitempty"`
// MinReplicas is a Go template expression that evaluates to the minimum number of replicas.
// Example: "1" or "{{ div .Values.replicas 2 | add1 }}"
// +optional
MinReplicas string `json:"minReplicas,omitempty"`
// Condition is a Go template expression that must evaluate to "true" for the monitor to be created.
// Example: "{{ .Values.clickhouseKeeper.enabled }}"
// If empty, the monitor is always created.
// +optional
Condition string `json:"condition,omitempty"`
}

View File

@@ -244,6 +244,13 @@ func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDe
*out = new(CozystackResourceDefinitionDashboard)
(*in).DeepCopyInto(*out)
}
if in.WorkloadMonitors != nil {
in, out := &in.WorkloadMonitors, &out.WorkloadMonitors
*out = make([]WorkloadMonitorTemplate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec.
@@ -461,6 +468,28 @@ func (in *WorkloadMonitorStatus) DeepCopy() *WorkloadMonitorStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadMonitorTemplate) DeepCopyInto(out *WorkloadMonitorTemplate) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitorTemplate.
func (in *WorkloadMonitorTemplate) DeepCopy() *WorkloadMonitorTemplate {
if in == nil {
return nil
}
out := new(WorkloadMonitorTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkloadStatus) DeepCopyInto(out *WorkloadStatus) {
*out = *in

View File

@@ -192,6 +192,14 @@ func main() {
os.Exit(1)
}
if err = (&controller.WorkloadMonitorFromCRDReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "WorkloadMonitorFromCRD")
os.Exit(1)
}
if err = (&controller.WorkloadReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
@@ -229,6 +237,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 +271,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

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

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

@@ -53,8 +53,8 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
"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

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",
@@ -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,7 +10,9 @@ 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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -40,28 +42,42 @@ 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 {
return ctrl.NewControllerManagedBy(mgr).
Named("dashboard-reconciler").
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(m)
}
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 +187,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 +355,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 +364,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 +375,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 +386,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 +397,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 +408,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 +419,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 +432,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 +444,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

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

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"),
@@ -175,7 +175,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// 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']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Key", ".data.key"),
createSecretBase64Column("Value", ".data.value"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
@@ -184,7 +184,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// 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 +250,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 +259,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 +268,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 +304,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 +314,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 +325,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 +335,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 +360,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 +496,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 +795,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"substractHeight": float64(400),
"type": "builtin",
"typeName": "secrets",
"readOnly": true,
},
},
},
@@ -1201,7 +1201,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",

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,439 @@
package controller
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"text/template"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"k8s.io/apimachinery/pkg/api/errors"
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/types"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// WorkloadMonitorFromCRDReconciler reconciles HelmReleases and creates WorkloadMonitors
// based on CozystackResourceDefinition templates
type WorkloadMonitorFromCRDReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch
// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=get;list;watch
// +kubebuilder:rbac:groups=cozystack.io,resources=workloadmonitors,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch
const (
WorkloadMonitorOwnerLabel = "workloadmonitor.cozystack.io/owned-by-crd"
WorkloadMonitorSourceLabel = "workloadmonitor.cozystack.io/helm-release"
)
// Reconcile processes HelmRelease resources and creates corresponding WorkloadMonitors
func (r *WorkloadMonitorFromCRDReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Get the HelmRelease
hr := &helmv2.HelmRelease{}
if err := r.Get(ctx, req.NamespacedName, hr); err != nil {
if errors.IsNotFound(err) {
// HelmRelease deleted - cleanup will be handled by owner references
return ctrl.Result{}, nil
}
logger.Error(err, "unable to fetch HelmRelease")
return ctrl.Result{}, err
}
// Skip system HelmReleases
if strings.HasPrefix(hr.Name, "tenant-") {
return ctrl.Result{}, nil
}
// Find the matching CozystackResourceDefinition
crd, err := r.findCRDForHelmRelease(ctx, hr)
if err != nil {
if errors.IsNotFound(err) {
// No CRD found for this HelmRelease - skip
logger.V(1).Info("No CozystackResourceDefinition found for HelmRelease", "name", hr.Name)
return ctrl.Result{}, nil
}
logger.Error(err, "unable to find CozystackResourceDefinition")
return ctrl.Result{}, err
}
// If CRD doesn't have WorkloadMonitors, cleanup any existing ones we created
if len(crd.Spec.WorkloadMonitors) == 0 {
if err := r.cleanupWorkloadMonitors(ctx, hr); err != nil {
logger.Error(err, "failed to cleanup WorkloadMonitors")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// Get the HelmRelease values for template rendering
values, err := r.getHelmReleaseValues(ctx, hr)
if err != nil {
logger.Error(err, "unable to get HelmRelease values")
return ctrl.Result{}, err
}
// Create/update WorkloadMonitors based on templates
if err := r.reconcileWorkloadMonitors(ctx, hr, crd, values); err != nil {
logger.Error(err, "failed to reconcile WorkloadMonitors")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// findCRDForHelmRelease finds the CozystackResourceDefinition for a given HelmRelease
func (r *WorkloadMonitorFromCRDReconciler) findCRDForHelmRelease(ctx context.Context, hr *helmv2.HelmRelease) (*cozyv1alpha1.CozystackResourceDefinition, error) {
// List all CozystackResourceDefinitions
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := r.List(ctx, &crdList); err != nil {
return nil, err
}
// Match by chart name and prefix
for i := range crdList.Items {
crd := &crdList.Items[i]
if crd.Spec.Release.Chart.Name == hr.Spec.Chart.Spec.Chart {
// Check if HelmRelease name matches the prefix
if strings.HasPrefix(hr.Name, crd.Spec.Release.Prefix) {
return crd, nil
}
}
}
return nil, errors.NewNotFound(schema.GroupResource{Group: "cozystack.io", Resource: "cozystackresourcedefinitions"}, "")
}
// getHelmReleaseValues extracts the values from HelmRelease spec
func (r *WorkloadMonitorFromCRDReconciler) getHelmReleaseValues(ctx context.Context, hr *helmv2.HelmRelease) (map[string]interface{}, error) {
if hr.Spec.Values == nil {
return make(map[string]interface{}), nil
}
// Convert apiextensionsv1.JSON to map
values := make(map[string]interface{})
if err := json.Unmarshal(hr.Spec.Values.Raw, &values); err != nil {
return nil, fmt.Errorf("failed to unmarshal values: %w", err)
}
return values, nil
}
// reconcileWorkloadMonitors creates or updates WorkloadMonitors based on CRD templates
func (r *WorkloadMonitorFromCRDReconciler) reconcileWorkloadMonitors(
ctx context.Context,
hr *helmv2.HelmRelease,
crd *cozyv1alpha1.CozystackResourceDefinition,
values map[string]interface{},
) error {
logger := log.FromContext(ctx)
// Get chart version from HelmRelease
chartVersion := ""
if hr.Status.History != nil && len(hr.Status.History) > 0 {
chartVersion = hr.Status.History[0].ChartVersion
}
// Template context
templateData := map[string]interface{}{
"Release": map[string]interface{}{
"Name": hr.Name,
"Namespace": hr.Namespace,
},
"Chart": map[string]interface{}{
"Version": chartVersion,
},
"Values": values,
}
// Track which monitors we should have
expectedMonitors := make(map[string]bool)
// Process each WorkloadMonitor template
for _, tmpl := range crd.Spec.WorkloadMonitors {
// Check condition
if tmpl.Condition != "" {
shouldCreate, err := evaluateCondition(tmpl.Condition, templateData)
if err != nil {
logger.Error(err, "failed to evaluate condition", "template", tmpl.Name, "condition", tmpl.Condition)
continue
}
if !shouldCreate {
logger.V(1).Info("Skipping WorkloadMonitor due to condition", "template", tmpl.Name)
continue
}
}
// Render monitor name
monitorName, err := renderTemplate(tmpl.Name, templateData)
if err != nil {
logger.Error(err, "failed to render monitor name", "template", tmpl.Name)
continue
}
expectedMonitors[monitorName] = true
// Render selector values
selector := make(map[string]string)
for key, valueTmpl := range tmpl.Selector {
renderedValue, err := renderTemplate(valueTmpl, templateData)
if err != nil {
logger.Error(err, "failed to render selector value", "key", key, "template", valueTmpl)
continue
}
selector[key] = renderedValue
}
// Render replicas
var replicas *int32
if tmpl.Replicas != "" {
replicasStr, err := renderTemplate(tmpl.Replicas, templateData)
if err != nil {
logger.Error(err, "failed to render replicas", "template", tmpl.Replicas)
} else {
if replicasInt, err := strconv.ParseInt(replicasStr, 10, 32); err == nil {
replicas = pointer.Int32(int32(replicasInt))
}
}
}
// Render minReplicas
var minReplicas *int32
if tmpl.MinReplicas != "" {
minReplicasStr, err := renderTemplate(tmpl.MinReplicas, templateData)
if err != nil {
logger.Error(err, "failed to render minReplicas", "template", tmpl.MinReplicas)
} else {
if minReplicasInt, err := strconv.ParseInt(minReplicasStr, 10, 32); err == nil {
minReplicas = pointer.Int32(int32(minReplicasInt))
}
}
}
// Create or update WorkloadMonitor
monitor := &cozyv1alpha1.WorkloadMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: monitorName,
Namespace: hr.Namespace,
},
}
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, monitor, func() error {
// Set labels
if monitor.Labels == nil {
monitor.Labels = make(map[string]string)
}
monitor.Labels[WorkloadMonitorOwnerLabel] = "true"
monitor.Labels[WorkloadMonitorSourceLabel] = hr.Name
// Set owner reference to HelmRelease for automatic cleanup
if err := controllerutil.SetControllerReference(hr, monitor, r.Scheme); err != nil {
return err
}
// Update spec
monitor.Spec.Selector = selector
monitor.Spec.Kind = tmpl.Kind
monitor.Spec.Type = tmpl.Type
monitor.Spec.Version = chartVersion
monitor.Spec.Replicas = replicas
monitor.Spec.MinReplicas = minReplicas
return nil
})
if err != nil {
logger.Error(err, "failed to create/update WorkloadMonitor", "name", monitorName)
continue
}
logger.V(1).Info("WorkloadMonitor reconciled", "name", monitorName)
}
// Cleanup WorkloadMonitors that are no longer in templates
if err := r.cleanupUnexpectedMonitors(ctx, hr, expectedMonitors); err != nil {
logger.Error(err, "failed to cleanup unexpected WorkloadMonitors")
return err
}
return nil
}
// cleanupWorkloadMonitors removes all WorkloadMonitors created for a HelmRelease
func (r *WorkloadMonitorFromCRDReconciler) cleanupWorkloadMonitors(ctx context.Context, hr *helmv2.HelmRelease) error {
return r.cleanupUnexpectedMonitors(ctx, hr, make(map[string]bool))
}
// cleanupUnexpectedMonitors removes WorkloadMonitors that are no longer expected
func (r *WorkloadMonitorFromCRDReconciler) cleanupUnexpectedMonitors(
ctx context.Context,
hr *helmv2.HelmRelease,
expectedMonitors map[string]bool,
) error {
logger := log.FromContext(ctx)
// List all WorkloadMonitors in the namespace that we created
var monitorList cozyv1alpha1.WorkloadMonitorList
labelSelector := labels.SelectorFromSet(labels.Set{
WorkloadMonitorOwnerLabel: "true",
WorkloadMonitorSourceLabel: hr.Name,
})
if err := r.List(ctx, &monitorList,
client.InNamespace(hr.Namespace),
client.MatchingLabelsSelector{Selector: labelSelector},
); err != nil {
return err
}
// Delete monitors that are not expected
for i := range monitorList.Items {
monitor := &monitorList.Items[i]
if !expectedMonitors[monitor.Name] {
logger.Info("Deleting unexpected WorkloadMonitor", "name", monitor.Name)
if err := r.Delete(ctx, monitor); err != nil && !errors.IsNotFound(err) {
logger.Error(err, "failed to delete WorkloadMonitor", "name", monitor.Name)
}
}
}
return nil
}
// renderTemplate renders a Go template string with the given data
func renderTemplate(tmplStr string, data interface{}) (string, error) {
// Check if it's already a simple value (no template markers)
if !strings.Contains(tmplStr, "{{") {
return tmplStr, nil
}
// Add Sprig functions for compatibility with Helm templates
tmpl, err := template.New("").Funcs(getTemplateFuncs()).Parse(tmplStr)
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
return strings.TrimSpace(buf.String()), nil
}
// evaluateCondition evaluates a template condition (should return "true" or non-empty for true)
func evaluateCondition(condition string, data interface{}) (bool, error) {
result, err := renderTemplate(condition, data)
if err != nil {
return false, err
}
// Check for truthy values
result = strings.TrimSpace(strings.ToLower(result))
return result == "true" || result == "1" || result == "yes", nil
}
// getTemplateFuncs returns template functions compatible with Helm
func getTemplateFuncs() template.FuncMap {
return template.FuncMap{
// Math functions
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b int) int { return a * b },
"div": func(a, b int) int {
if b == 0 {
return 0
}
return a / b
},
"add1": func(a int) int { return a + 1 },
"sub1": func(a int) int { return a - 1 },
// String functions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"trim": strings.TrimSpace,
"trimAll": func(cutset, s string) string { return strings.Trim(s, cutset) },
"replace": func(old, new string, n int, s string) string { return strings.Replace(s, old, new, n) },
// Logic functions
"default": func(defaultVal, val interface{}) interface{} {
if val == nil || val == "" {
return defaultVal
}
return val
},
"empty": func(val interface{}) bool {
return val == nil || val == ""
},
"not": func(val bool) bool {
return !val
},
}
}
// SetupWithManager sets up the controller with the Manager
func (r *WorkloadMonitorFromCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Named("workloadmonitor-from-crd-controller").
For(&helmv2.HelmRelease{}).
Owns(&cozyv1alpha1.WorkloadMonitor{}).
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(r.mapCRDToHelmReleases),
).
Complete(r)
}
// mapCRDToHelmReleases maps CRD changes to HelmRelease reconcile requests
func (r *WorkloadMonitorFromCRDReconciler) mapCRDToHelmReleases(ctx context.Context, obj client.Object) []reconcile.Request {
crd, ok := obj.(*cozyv1alpha1.CozystackResourceDefinition)
if !ok {
return nil
}
// List all HelmReleases
var hrList helmv2.HelmReleaseList
if err := r.List(ctx, &hrList); err != nil {
return nil
}
var requests []reconcile.Request
for i := range hrList.Items {
hr := &hrList.Items[i]
// Skip tenant HelmReleases
if strings.HasPrefix(hr.Name, "tenant-") {
continue
}
// Match by chart name and prefix
if crd.Spec.Release.Chart.Name == hr.Spec.Chart.Spec.Chart {
if strings.HasPrefix(hr.Name, crd.Spec.Release.Prefix) {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: hr.Name,
Namespace: hr.Namespace,
},
})
}
}
}
return requests
}

View File

@@ -1,28 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: clickhouse
type: clickhouse
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}
{{- if .Values.clickhouseKeeper.enabled }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-keeper
spec:
replicas: {{ .Values.clickhouseKeeper.replicas }}
minReplicas: 1
kind: clickhouse
type: clickhouse
selector:
app: {{ $.Release.Name }}-keeper
version: {{ $.Chart.Version }}
{{- end }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: ferretdb
type: ferretdb
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,20 +0,0 @@
{{- if .Values.monitoring.enabled }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ .Release.Name }}
labels:
app.kubernetes.io/name: foundationdb
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.cluster.processCounts.storage }}
minReplicas: {{ include "foundationdb.minReplicas" . }}
kind: foundationdb
type: foundationdb
selector:
foundationdb.org/fdb-cluster-name: {{ .Release.Name }}
foundationdb.org/fdb-process-class: storage
version: {{ .Chart.Version }}
{{- end }}

View File

@@ -1,39 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-haproxy
spec:
replicas: {{ .Values.haproxy.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app: {{ $.Release.Name }}-haproxy
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-nginx
spec:
replicas: {{ .Values.nginx.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app: {{ $.Release.Name }}-nginx-cache
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: http-cache
type: http-cache
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,30 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: kafka
type: kafka
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
app.kubernetes.io/name: kafka
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-zookeeper
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: kafka
type: zookeeper
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
app.kubernetes.io/name: zookeeper
version: {{ $.Chart.Version }}

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,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: mysql
type: mysql
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -47,18 +47,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 +63,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

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: nats
type: nats
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}-system
version: {{ $.Chart.Version }}

View File

@@ -79,17 +79,3 @@ spec:
policy.cozystack.io/allow-to-apiserver: "true"
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: {{ $.Release.Name }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: postgres
type: postgres
selector:
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: rabbitmq
type: rabbitmq
selector:
app.kubernetes.io/name: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

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 }}
@@ -67,34 +68,3 @@ spec:
auth:
secretPath: {{ .Release.Name }}-auth
{{- end }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-redis
namespace: {{ $.Release.Namespace }}
spec:
minReplicas: 1
replicas: {{ .Values.replicas }}
kind: redis
type: redis
selector:
app.kubernetes.io/component: redis
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}-sentinel
namespace: {{ $.Release.Namespace }}
spec:
minReplicas: 2
replicas: 3
kind: redis
type: sentinel
selector:
app.kubernetes.io/component: sentinel
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -1,13 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: tcp-balancer
type: haproxy
selector:
app.kubernetes.io/instance: {{ $.Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -28,6 +28,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
@@ -113,6 +114,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -184,6 +186,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
@@ -270,6 +273,7 @@ rules:
- vmdisks
- vminstances
- infos
- virtualprivateclouds
verbs:
- get
- list
@@ -282,6 +286,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
@@ -356,6 +361,7 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io

View File

@@ -48,6 +48,8 @@ virtctl ssh <user>@<vm>
| `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` | `{}` |

View File

@@ -0,0 +1,40 @@
# if subnets are specified and image is either ubunu or alpine
{{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "virtual-machine.fullname" $ }}-network-config
stringData:
networkData: |
network:
version: 1
config:
{{- if eq .Values.systemDisk.image "ubuntu" }}
# main iface
- type: physical
name: enp1s0
subnets:
- type: dhcp
# additional ifaces attached to subnets
{{- range $i, $subnet := .Values.subnets }}
- type: physical
name: enp{{ add 2 $i }}s0
subnets:
- type: dhcp
{{- end }}
{{- else if eq .Values.systemDisk.image "alpine" }}
#main iface
- type: physical
name: eth0
subnets:
- type: dhcp
# additional ifaces attached to subnets
{{- range $i, $subnet := .Values.subnets }}
- type: physical
name: eth{{ add1 $i }}
subnets:
- type: dhcp
{{- end }}
{{- end }}
{{- end }}

View File

@@ -51,9 +51,9 @@ spec:
{{- else if eq .Values.systemDisk.image "ubuntu" }}
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
{{- else if eq .Values.systemDisk.image "fedora" }}
url: https://download.fedoraproject.org/pub/fedora/linux/releases/40/Cloud/x86_64/images/Fedora-Cloud-Base-Generic.x86_64-40-1.14.qcow2
url: https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2
{{- else if eq .Values.systemDisk.image "alpine" }}
url: https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.2-x86_64-bios-tiny-r0.qcow2
url: https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.8-x86_64-bios-cloudinit-r0.qcow2
{{- else if eq .Values.systemDisk.image "talos" }}
url: https://github.com/siderolabs/talos/releases/download/v1.7.6/nocloud-amd64.raw.xz
{{- end }}
@@ -93,7 +93,7 @@ spec:
- disk:
bus: scsi
name: systemdisk
{{- if or .Values.cloudInit .Values.sshKeys }}
{{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }}
- disk:
bus: virtio
name: cloudinitdisk
@@ -102,6 +102,12 @@ spec:
interfaces:
- name: default
bridge: {}
{{- if .Values.subnets }}
{{- range $i, $subnet := .Values.subnets }}
- name: {{ $subnet.name }}
bridge: {}
{{- end }}
{{- end }}
machine:
type: ""
@@ -123,13 +129,26 @@ spec:
- name: systemdisk
dataVolume:
name: {{ include "virtual-machine.fullname" . }}
{{- if or .Values.cloudInit .Values.sshKeys }}
{{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }}
- name: cloudinitdisk
cloudInitNoCloud:
{{- if or .Values.cloudInit .Values.sshKeys }}
secretRef:
name: {{ include "virtual-machine.fullname" . }}-cloud-init
{{- end }}
{{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }}
networkDataSecretRef:
name: {{ include "virtual-machine.fullname" . }}-network-config
{{- end }}
{{- end }}
networks:
- name: default
pod: {}
{{- if .Values.subnets }}
{{- range $i, $subnet := .Values.subnets }}
- name: {{ $subnet.name }}
multus:
networkName: {{ $.Release.Namespace }}/{{ $subnet.name }}
{{- end }}
{{- end }}

View File

@@ -167,6 +167,20 @@
"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",

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.
@@ -50,6 +53,14 @@ systemDisk:
storage: 5Gi
storageClass: replicated
## @param {[]Subnet} subnets - Additional subnets
subnets: []
## Example:
## subnets:
## - name: subnet-84dbec17
## - name: subnet-aa8896b5
## - name: subnet-e9b97196
## @param {[]GPU} gpus - List of GPUs to attach.
gpus: []
## Example:

View File

@@ -1,12 +0,0 @@
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: 0
minReplicas: 0
kind: vm-disk
type: vm-disk
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
version: {{ $.Chart.Version }}

View File

@@ -47,6 +47,8 @@ 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` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |

View File

@@ -77,6 +77,12 @@ spec:
interfaces:
- name: default
bridge: {}
{{- if .Values.subnets }}
{{- range $i, $subnet := .Values.subnets }}
- name: {{ $subnet.name }}
bridge: {}
{{- end }}
{{- end }}
machine:
type: ""
{{- with .Values.sshKeys }}
@@ -105,3 +111,10 @@ spec:
networks:
- name: default
pod: {}
{{- if .Values.subnets }}
{{- range $i, $subnet := .Values.subnets }}
- name: {{ $subnet.name }}
multus:
networkName: {{ $.Release.Namespace }}/{{ $subnet.name }}
{{- end }}
{{- end }}

View File

@@ -187,6 +187,20 @@
"items": {
"type": "string"
}
},
"subnets": {
"description": "Additional subnets",
"type": "array",
"default": [],
"items": {
"type": "object",
"properties": {
"name": {
"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.
@@ -45,6 +48,14 @@ disks: []
## - name: example-data
## bus: sata
## @param {[]Subnet} subnets - Additional subnets
subnets: []
## Example:
## subnets:
## - name: subnet-84dbec17
## - name: subnet-aa8896b5
## - name: subnet-e9b97196
## @param {[]GPU} gpus - List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).
gpus: []
## Example:

View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: virtualprivatecloud
description: Isolated networks
icon: logos/vpc.svg
type: application
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process

View File

@@ -0,0 +1,8 @@
include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json
../../../hack/update-crd.sh
update:
echo

View File

@@ -0,0 +1,48 @@
# 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
The service utilizes kube-ovn VPC and Subnet resources, which use ovn logical routers and logical switches under the hood.
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 be going through it.
VPC subnets are for now an additional dedicated networking spaces.
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.
## 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.
## 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

@@ -0,0 +1,10 @@
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="144" height="144" rx="24" fill="url(#paint0_linear_1025_3)"/>
<path d="M109.6 86.1H114.3C116.885 86.1 119 88.215 119 90.847V104.853C119 107.485 116.885 109.6 114.3 109.6H95.5C92.915 109.6 90.8 107.485 90.8 104.853V90.847C90.8 88.215 92.915 86.1 95.5 86.1H100.2V76.7H76.7V86.1H81.4C83.985 86.1 86.1 88.215 86.1 90.847V104.853C86.1 107.485 83.985 109.6 81.4 109.6H62.6C60.015 109.6 57.9 107.485 57.9 104.853V90.847C57.9 88.215 60.015 86.1 62.6 86.1H67.3V76.7H43.8V86.1H48.5C51.085 86.1 53.2 88.215 53.2 90.847V104.853C53.2 107.485 51.085 109.6 48.5 109.6H29.7C27.115 109.6 25 107.485 25 104.853V90.847C25 88.215 27.115 86.1 29.7 86.1H34.4V76.7C34.4 71.53 38.63 67.3 43.8 67.3H67.3V57.9H62.6C60.015 57.9 57.9 55.785 57.9 53.153V39.147C57.9 36.515 60.015 34.4 62.6 34.4H81.4C83.985 34.4 86.1 36.515 86.1 39.147V53.153C86.1 55.785 83.985 57.9 81.4 57.9H76.7V67.3H100.2C105.37 67.3 109.6 71.53 109.6 76.7V86.1Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_1025_3" x1="142.5" y1="143" x2="3.99999" y2="9.49999" gradientUnits="userSpaceOnUse">
<stop stop-color="#00082E"/>
<stop offset="1" stop-color="#2E3067"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,72 @@
## Release.Namespace == tenant name
## Release.Name == vpc name
{{ $vpcId := print "vpc-" (print .Release.Namespace "/" .Release.Name | sha256sum | trunc 6) }}
---
apiVersion: kubeovn.io/v1
kind: Vpc
metadata:
name: {{ $vpcId }}
labels:
cozystack.io/vpcName: {{ .Release.Name }}
cozystack.io/tenantName: {{ .Release.Namespace }}
spec:
enableExternal: false
namespaces:
- {{ .Release.Namespace }}
{{- range $subnetName, $subnetConfig := .Values.subnets }}
{{- $subnetId := print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
---
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: {{ $subnetId }}
namespace: {{ $.Release.Namespace }}
labels:
cozystack.io/subnetName: {{ $subnetName }}
cozystack.io/vpcId: {{ $vpcId }}
cozystack.io/vpcName: {{ $.Release.Name }}
cozystack.io/tenantName: {{ $.Release.Namespace }}
spec:
config: '{
"cniVersion": "0.3.0",
"type": "kube-ovn",
"server_socket": "/run/openvswitch/kube-ovn-daemon.sock",
"provider": "{{ $subnetId }}.{{ $.Release.Namespace }}.ovn"
}'
---
apiVersion: kubeovn.io/v1
kind: Subnet
metadata:
name: {{ $subnetId }}
labels:
cozystack.io/subnetName: {{ $subnetName }}
cozystack.io/vpcId: {{ $vpcId }}
cozystack.io/vpcName: {{ $.Release.Name }}
cozystack.io/tenantName: {{ $.Release.Namespace }}
spec:
vpc: {{ $vpcId }}
cidrBlock: {{ $subnetConfig.cidr }}
provider: "{{ $subnetId }}.{{ $.Release.Namespace }}.ovn"
protocol: IPv4
enableLb: false
private: true
{{- end }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $.Release.Name }}-subnets
labels:
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 }}

View File

@@ -0,0 +1,20 @@
{
"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"
}
}
}
}
}
}

View File

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

View File

@@ -1,12 +0,0 @@
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: 1
kind: vpn
type: vpn
selector:
app.kubernetes.io/instance: {{ .Release.Name }}
version: {{ $.Chart.Version }}

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

@@ -1,16 +0,0 @@
---
apiVersion: cozystack.io/v1alpha1
kind: WorkloadMonitor
metadata:
name: {{ $.Release.Name }}
namespace: {{ $.Release.Namespace }}
spec:
replicas: {{ .Values.replicas }}
minReplicas: {{ div .Values.replicas 2 | add1 }}
kind: ingress
type: controller
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx-system
app.kubernetes.io/name: ingress-nginx
version: {{ $.Chart.Version }}

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

@@ -671,6 +671,62 @@ spec:
x-kubernetes-map-type: atomic
type: array
type: object
workloadMonitors:
description: |-
WorkloadMonitors configuration for this resource
List of WorkloadMonitor templates to be created for each application instance
items:
description: |-
WorkloadMonitorTemplate defines a template for creating WorkloadMonitor resources
for application instances. Fields support Go template syntax with the following variables:
- {{ .Release.Name }}: The name of the Helm release
- {{ .Release.Namespace }}: The namespace of the Helm release
- {{ .Chart.Version }}: The version of the Helm chart
- {{ .Values.<path> }}: Any value from the Helm values
properties:
condition:
description: |-
Condition is a Go template expression that must evaluate to "true" for the monitor to be created.
Example: "{{ .Values.clickhouseKeeper.enabled }}"
If empty, the monitor is always created.
type: string
kind:
description: Kind specifies the kind of the workload (e.g.,
"postgres", "kafka")
type: string
minReplicas:
description: |-
MinReplicas is a Go template expression that evaluates to the minimum number of replicas.
Example: "1" or "{{ div .Values.replicas 2 | add1 }}"
type: string
name:
description: |-
Name is the name of the WorkloadMonitor.
Supports Go template syntax (e.g., "{{ .Release.Name }}-keeper")
type: string
replicas:
description: |-
Replicas is a Go template expression that evaluates to the desired number of replicas.
Example: "{{ .Values.replicas }}" or "{{ .Values.clickhouseKeeper.replicas }}"
type: string
selector:
additionalProperties:
type: string
description: |-
Selector is a map of label key-value pairs for matching workloads.
Supports Go template syntax in values (e.g., "app.kubernetes.io/instance: {{ .Release.Name }}")
type: object
type:
description: Type specifies the type of the workload (e.g.,
"postgres", "zookeeper")
type: string
required:
- kind
- name
- selector
- type
type: object
type: array
required:
- application
- release

View File

@@ -37,3 +37,19 @@ spec:
include:
- resourceNames:
- chendpoint-clickhouse-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: clickhouse
type: clickhouse
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-keeper"
kind: clickhouse
type: clickhouse
selector:
app: "{{ .Release.Name }}-keeper"
replicas: "{{ .Values.clickhouseKeeper.replicas }}"
minReplicas: "1"
condition: "{{ .Values.clickhouseKeeper.enabled }}"

View File

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- ferretdb-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: ferretdb
type: ferretdb
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -28,3 +28,13 @@ spec:
- database
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF84NThfMzA3MikiLz4KPHBhdGggZD0iTTEzNS43ODQgNzUuNjQ0NkwxMzUuOTM5IDg3Ljc2MzhMODkuNjg0NiA4MS41MzYyTDYyLjA4NjggODQuNTA3OUwzNS4zNDE3IDgxLjQzMjlMOC43NTE2NyA4NC41ODU0TDguNzI1ODMgODEuNTEwNEwzNS4zNjc2IDc3LjU4MjZWNjQuMTcxM0w2Mi4yOTM1IDcwLjczNDhMNjIuMzQ1MiA4MS4yNzc4TDg5LjQ3NzkgNzcuNjg2TDg5LjQwMDQgNjQuMTk3MkwxMzUuNzg0IDc1LjY0NDZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNODkuNDc3OCA4Ni4wMzI1TDEzNS44ODggOTAuODM4OFYxMDIuNzI2SDguNjQ4MjVMOC41MTkwNCA5OS41NzNIMzUuMjY0MUMzNS4yNjQxIDk5LjU3MyAzNS4yNjQxIDkwLjczNTUgMzUuMjY0MSA4Ni4wNTgzQzQ0LjI1NjcgODYuOTM2OSA2Mi4wODY3IDg4LjY5NDEgNjIuMDg2NyA4OC42OTQxVjk5LjI2MjlIODkuNDc3OFY4Ni4wMzI1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYyLjI5MzQgNjYuODg0Nkw2Mi4yMTU4IDYzLjYyODZDNjIuMjE1OCA2My42Mjg2IDc5LjgxMzMgNTguMzU3MSA4OC45MDkyIDU1LjY2OTdDODguOTA5MiA1MS4zMDI2IDg4LjkwOTIgNDcuMDkwNiA4OC45MDkyIDQyQzEwNC44NzkgNDguNDA4NSAxMjAuMjI4IDU0LjYxMDIgMTM1LjczMyA2MC44Mzc4QzEzNS43MzMgNjQuNzEzOSAxMzUuNzMzIDY4LjQzNSAxMzUuNzMzIDcyLjU2OTVDMTE5Ljg0MSA2OC4yMDI0IDEwNC4yODQgNjMuOTEyOSA4OS4xNjc2IDU5Ljc1MjVDNzkuOTY4NCA2Mi4yMDc0IDYyLjI5MzQgNjYuODg0NiA2Mi4yOTM0IDY2Ljg4NDZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMzUuMzk2MiA4MS43MDczTDguODA2MTIgODQuODU5OEw4Ljc4MDI3IDgxLjc4NDhMMzUuNDIyIDc3Ljg1N1Y2NC40NDU3TDYyLjM0OCA3MS4wMDkzTDYyLjM5OTYgODEuNTUyMkw4OS41MzIzIDc3Ljk2MDRMODkuNDU0OCA2NC40NzE2TDEzNS44MzkgNzUuOTE5TDEzNS45OTQgODguMDM4Mkw4OS43MzkxIDgxLjgxMDZMNjIuMTQxMiA4NC43ODIzTDM1LjM5NjIgODEuNzA3M1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04OS41MzIzIDg2LjMwNjlMMTM1Ljk0MiA5MS4xMTMzVjEwM0g4LjcwMjdMOC41NzM0OSA5OS44NDc0SDM1LjMxODZDMzUuMzE4NiA5OS44NDc0IDM1LjMxODYgOTEuMDA5OSAzNS4zMTg2IDg2LjMzMjhDNDQuMzExMSA4Ny4yMTE0IDYyLjE0MTIgODguOTY4NSA2Mi4xNDEyIDg4Ljk2ODVWOTkuNTM3M0g4OS41MzIzVjg2LjMwNjlaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNjIuMzQ4MyA2Ny4xNTlMNjIuMjcwOCA2My45MDMxQzYyLjI3MDggNjMuOTAzMSA3OS44NjgyIDU4LjYzMTYgODguOTY0MiA1NS45NDQyQzg4Ljk2NDIgNTEuNTc3MSA4OC45NjQyIDQ3LjM2NTEgODguOTY0MiA0Mi4yNzQ0QzEwNC45MzQgNDguNjgyOSAxMjAuMjgzIDU0Ljg4NDcgMTM1Ljc4NyA2MS4xMTIzQzEzNS43ODcgNjQuOTg4NCAxMzUuNzg3IDY4LjcwOTQgMTM1Ljc4NyA3Mi44NDM5QzExOS44OTUgNjguNDc2OSAxMDQuMzM5IDY0LjE4NzMgODkuMjIyNiA2MC4wMjdDODAuMDIzMyA2Mi40ODE4IDYyLjM0ODMgNjcuMTU5IDYyLjM0ODMgNjcuMTU5WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF84NThfMzA3MiIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMjkuNSAtMTgpIHJvdGF0ZSgzOS42OTYzKSBzY2FsZSgzMDIuMTY4IDI3NS4yNzEpIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0JFRERGRiIvPgo8c3RvcCBvZmZzZXQ9IjAuMjU5NjE1IiBzdG9wLWNvbG9yPSIjOUVDQ0ZEIi8+CjxzdG9wIG9mZnNldD0iMC41OTEzNDYiIHN0b3AtY29sb3I9IiMzRjlBRkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMEI3MEUwIi8+CjwvcmFkaWFsR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==
# keysOrder: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: foundationdb
type: foundationdb
selector:
foundationdb.org/fdb-cluster-name: "{{ .Release.Name }}"
foundationdb.org/fdb-process-class: storage
replicas: "{{ .Values.cluster.processCounts.storage }}"
minReplicas: "{{ include \"foundationdb.minReplicas\" . }}"
condition: "{{ .Values.monitoring.enabled }}"

View File

@@ -32,3 +32,25 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}-haproxy"
kind: http-cache
type: http-cache
selector:
app: "{{ .Release.Name }}-haproxy"
replicas: "{{ .Values.haproxy.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-nginx"
kind: http-cache
type: http-cache
selector:
app: "{{ .Release.Name }}-nginx-cache"
replicas: "{{ .Values.nginx.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}"
kind: http-cache
type: http-cache
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -37,3 +37,13 @@ spec:
include:
- resourceNames:
- "{{ slice .namespace 7 }}-ingress-controller"
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: ingress
type: controller
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx-system
app.kubernetes.io/name: ingress-nginx
replicas: "{{ .Values.replicas }}"
minReplicas: "{{ div .Values.replicas 2 | add1 }}"

View File

@@ -38,3 +38,20 @@ spec:
include:
- resourceNames:
- kafka-{{ .name }}-kafka-bootstrap
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: kafka
type: kafka
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/name: kafka
replicas: "{{ .Values.kafka.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-zookeeper"
kind: kafka
type: zookeeper
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
app.kubernetes.io/name: zookeeper
replicas: "{{ .Values.zookeeper.replicas }}"
minReplicas: "1"

View File

@@ -39,3 +39,11 @@ spec:
- resourceNames:
- mysql-{{ .name }}-primary
- mysql-{{ .name }}-secondary
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: mysql
type: mysql
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- nats-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: nats
type: nats
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}-system"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -49,3 +49,12 @@ spec:
- postgres-{{ .name }}-ro
- postgres-{{ .name }}-rw
- postgres-{{ .name }}-external-write
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: postgres
type: postgres
selector:
app.kubernetes.io/name: postgres.apps.cozystack.io
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -40,3 +40,11 @@ spec:
include:
- resourceNames:
- rabbitmq-{{ .name }}
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: rabbitmq
type: rabbitmq
selector:
app.kubernetes.io/name: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

View File

@@ -41,3 +41,20 @@ spec:
- rfrm-redis-{{ .name }}
- rfrs-redis-{{ .name }}
- redis-{{ .name }}-external-lb
workloadMonitors:
- name: "{{ .Release.Name }}-redis"
kind: redis
type: redis
selector:
app.kubernetes.io/component: redis
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"
- name: "{{ .Release.Name }}-sentinel"
kind: redis
type: sentinel
selector:
app.kubernetes.io/component: sentinel
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "3"
minReplicas: "2"

View File

@@ -31,3 +31,11 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: tcp-balancer
type: haproxy
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

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"}},"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:
@@ -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", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "gpus"], ["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", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
secrets:
exclude: []
include: []

View File

@@ -0,0 +1,34 @@
apiVersion: cozystack.io/v1alpha1
kind: CozystackResourceDefinition
metadata:
name: virtualprivatecloud
spec:
application:
kind: VirtualPrivateCloud
plural: virtualprivateclouds
singular: virtualprivatecloud
openAPISchema: |-
{"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:
cozystack.io/ui: "true"
chart:
name: virtualprivatecloud
sourceRef:
kind: HelmRepository
name: cozystack-apps
namespace: cozy-public
dashboard:
category: IaaS
singular: VPC
plural: VPCs
description: "Isolated networks"
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8xMDI1XzMpIi8+CjxwYXRoIGQ9Ik0xMDkuNiA4Ni4xSDExNC4zQzExNi44ODUgODYuMSAxMTkgODguMjE1IDExOSA5MC44NDdWMTA0Ljg1M0MxMTkgMTA3LjQ4NSAxMTYuODg1IDEwOS42IDExNC4zIDEwOS42SDk1LjVDOTIuOTE1IDEwOS42IDkwLjggMTA3LjQ4NSA5MC44IDEwNC44NTNWOTAuODQ3QzkwLjggODguMjE1IDkyLjkxNSA4Ni4xIDk1LjUgODYuMUgxMDAuMlY3Ni43SDc2LjdWODYuMUg4MS40QzgzLjk4NSA4Ni4xIDg2LjEgODguMjE1IDg2LjEgOTAuODQ3VjEwNC44NTNDODYuMSAxMDcuNDg1IDgzLjk4NSAxMDkuNiA4MS40IDEwOS42SDYyLjZDNjAuMDE1IDEwOS42IDU3LjkgMTA3LjQ4NSA1Ny45IDEwNC44NTNWOTAuODQ3QzU3LjkgODguMjE1IDYwLjAxNSA4Ni4xIDYyLjYgODYuMUg2Ny4zVjc2LjdINDMuOFY4Ni4xSDQ4LjVDNTEuMDg1IDg2LjEgNTMuMiA4OC4yMTUgNTMuMiA5MC44NDdWMTA0Ljg1M0M1My4yIDEwNy40ODUgNTEuMDg1IDEwOS42IDQ4LjUgMTA5LjZIMjkuN0MyNy4xMTUgMTA5LjYgMjUgMTA3LjQ4NSAyNSAxMDQuODUzVjkwLjg0N0MyNSA4OC4yMTUgMjcuMTE1IDg2LjEgMjkuNyA4Ni4xSDM0LjRWNzYuN0MzNC40IDcxLjUzIDM4LjYzIDY3LjMgNDMuOCA2Ny4zSDY3LjNWNTcuOUg2Mi42QzYwLjAxNSA1Ny45IDU3LjkgNTUuNzg1IDU3LjkgNTMuMTUzVjM5LjE0N0M1Ny45IDM2LjUxNSA2MC4wMTUgMzQuNCA2Mi42IDM0LjRIODEuNEM4My45ODUgMzQuNCA4Ni4xIDM2LjUxNSA4Ni4xIDM5LjE0N1Y1My4xNTNDODYuMSA1NS43ODUgODMuOTg1IDU3LjkgODEuNCA1Ny45SDc2LjdWNjcuM0gxMDAuMkMxMDUuMzcgNjcuMyAxMDkuNiA3MS41MyAxMDkuNiA3Ni43Vjg2LjFaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzEwMjVfMyIgeDE9IjE0Mi41IiB5MT0iMTQzIiB4Mj0iMy45OTk5OSIgeTI9IjkuNDk5OTkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzAwMDgyRSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyRTMwNjciLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "subnets"]]
secrets:
exclude: []
include: []
services:
exclude: []
include: []

View File

@@ -32,3 +32,11 @@ spec:
secrets:
exclude: []
include: []
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: vm-disk
type: vm-disk
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "0"
minReplicas: "0"

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"}}}}
{"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", "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

@@ -38,3 +38,11 @@ spec:
include:
- resourceNames:
- vpn-{{ .name }}-vpn
workloadMonitors:
- name: "{{ .Release.Name }}"
kind: vpn
type: vpn
selector:
app.kubernetes.io/instance: "{{ .Release.Name }}"
replicas: "{{ .Values.replicas }}"
minReplicas: "1"

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=92906a7f21050cfb8e352f98d36b209c57844f63
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=7086a2d8a07dcf6a94bb4276433db5d84acfcf3b
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
@@ -17,10 +22,10 @@ FROM node:${NODE_VERSION}-alpine AS builder
RUN apk add git
WORKDIR /src
ARG COMMIT_REF=65e7fa8b3dc530a36e94c8435622bb09961aef97
ARG COMMIT_REF=fe237518348e94cead6d4f3283b2fce27f26aa12
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
COPY patches /patches
COPY openapi-ui/patches /patches
RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH

View File

@@ -0,0 +1,230 @@
diff --git a/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx b/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
index a7135d4..2fea0bb 100644
--- a/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
+++ b/src/components/molecules/BlackholeForm/molecules/FormObjectFromSwagger/FormObjectFromSwagger.tsx
@@ -68,13 +68,60 @@ export const FormObjectFromSwagger: FC<TFormObjectFromSwaggerProps> = ({
properties?: OpenAPIV2.SchemaObject['properties']
required?: string
}
+
+ // Check if the field name exists in additionalProperties.properties
+ // If so, use the type from that property definition
+ const nestedProp = addProps?.properties?.[additionalPropValue] as OpenAPIV2.SchemaObject | undefined
+ let fieldType: string = addProps.type
+ let fieldItems: { type: string } | undefined = addProps.items
+ let fieldNestedProperties = addProps.properties || {}
+ let fieldRequired: string | undefined = addProps.required
+
+ if (nestedProp) {
+ // Use the nested property definition if it exists
+ // Handle type - it can be string or string[] in OpenAPI v2
+ if (nestedProp.type) {
+ if (Array.isArray(nestedProp.type)) {
+ fieldType = nestedProp.type[0] || addProps.type
+ } else if (typeof nestedProp.type === 'string') {
+ fieldType = nestedProp.type
+ } else {
+ fieldType = addProps.type
+ }
+ } else {
+ fieldType = addProps.type
+ }
+
+ // Handle items - it can be ItemsObject or ReferenceObject
+ if (nestedProp.items) {
+ // Check if it's a valid ItemsObject with type property
+ if ('type' in nestedProp.items && typeof nestedProp.items.type === 'string') {
+ fieldItems = { type: nestedProp.items.type }
+ } else {
+ fieldItems = addProps.items
+ }
+ } else {
+ fieldItems = addProps.items
+ }
+
+ fieldNestedProperties = nestedProp.properties || {}
+ // Handle required field - it can be string[] in OpenAPI schema
+ if (Array.isArray(nestedProp.required)) {
+ fieldRequired = nestedProp.required.join(',')
+ } else if (typeof nestedProp.required === 'string') {
+ fieldRequired = nestedProp.required
+ } else {
+ fieldRequired = addProps.required
+ }
+ }
+
inputProps?.addField({
path: Array.isArray(name) ? [...name, String(collapseTitle)] : [name, String(collapseTitle)],
name: additionalPropValue,
- type: addProps.type,
- items: addProps.items,
- nestedProperties: addProps.properties || {},
- required: addProps.required,
+ type: fieldType,
+ items: fieldItems,
+ nestedProperties: fieldNestedProperties,
+ required: fieldRequired,
})
setAddditionalPropValue(undefined)
}
diff --git a/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx b/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
index 487d480..3ca46c1 100644
--- a/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
+++ b/src/components/molecules/BlackholeForm/molecules/FormStringInput/FormStringInput.tsx
@@ -42,7 +42,11 @@ export const FormStringInput: FC<TFormStringInputProps> = ({
const formValue = Form.useWatch(formFieldName)
// Derive multiline based on current local value
- const isMultiline = useMemo(() => isMultilineString(formValue), [formValue])
+ const isMultiline = useMemo(() => {
+ // Normalize value for multiline check
+ const value = typeof formValue === 'string' ? formValue : (formValue === null || formValue === undefined ? '' : String(formValue))
+ return isMultilineString(value)
+ }, [formValue])
const title = (
<>
@@ -77,6 +81,23 @@ export const FormStringInput: FC<TFormStringInputProps> = ({
rules={[{ required: forceNonRequired === false && required?.includes(getStringByName(name)) }]}
validateTrigger="onBlur"
hasFeedback={designNewLayout ? { icons: feedbackIcons } : true}
+ normalize={(value) => {
+ // Normalize value to string - prevent "[object Object]" display
+ if (value === undefined || value === null) {
+ return ''
+ }
+ if (typeof value === 'string') {
+ return value
+ }
+ if (typeof value === 'number' || typeof value === 'boolean') {
+ return String(value)
+ }
+ // If it's an object or array, it shouldn't be in a string field - return empty string
+ if (typeof value === 'object') {
+ return ''
+ }
+ return String(value)
+ }}
>
<Input.TextArea
placeholder={getStringByName(name)}
diff --git a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
index 6f9eb39..835224c 100644
--- a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
+++ b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/helpers/casts.ts
@@ -124,8 +124,26 @@ export const materializeAdditionalFromValues = (
*
* This is used when a new field appears in the data but doesn't yet exist in the schema.
*/
- const makeChildFromAP = (ap: any): OpenAPIV2.SchemaObject => {
- const t = ap?.type ?? 'object'
+ const makeChildFromAP = (ap: any, value?: unknown): OpenAPIV2.SchemaObject => {
+ // Determine type based on actual value if not explicitly defined in additionalProperties
+ let t = ap?.type
+ if (!t && value !== undefined && value !== null) {
+ if (Array.isArray(value)) {
+ t = 'array'
+ } else if (typeof value === 'object') {
+ t = 'object'
+ } else if (typeof value === 'string') {
+ t = 'string'
+ } else if (typeof value === 'number') {
+ t = 'number'
+ } else if (typeof value === 'boolean') {
+ t = 'boolean'
+ } else {
+ t = 'object'
+ }
+ }
+ t = t ?? 'object'
+
const child: OpenAPIV2.SchemaObject = { type: t } as any
// Copy common schema details (if present)
@@ -134,6 +152,20 @@ export const materializeAdditionalFromValues = (
if (ap?.required)
(child as any).required = _.cloneDeep(ap.required)
+ // If value is an array and items type is not defined, infer it from the first item
+ if (t === 'array' && Array.isArray(value) && value.length > 0 && !ap?.items) {
+ const firstItem = value[0]
+ if (typeof firstItem === 'string') {
+ ;(child as any).items = { type: 'string' }
+ } else if (typeof firstItem === 'number') {
+ ;(child as any).items = { type: 'number' }
+ } else if (typeof firstItem === 'boolean') {
+ ;(child as any).items = { type: 'boolean' }
+ } else if (typeof firstItem === 'object') {
+ ;(child as any).items = { type: 'object' }
+ }
+ }
+
// Mark as originating from `additionalProperties`
;(child as any).isAdditionalProperties = true
return child
@@ -177,7 +209,16 @@ export const materializeAdditionalFromValues = (
// If the key doesn't exist in schema, create it from `additionalProperties`
if (!schemaNode.properties![k]) {
- schemaNode.properties![k] = makeChildFromAP(ap)
+ // Check if there's a nested property definition in additionalProperties
+ const nestedProp = ap?.properties?.[k]
+ if (nestedProp) {
+ // Use the nested property definition from additionalProperties
+ schemaNode.properties![k] = _.cloneDeep(nestedProp) as any
+ ;(schemaNode.properties![k] as any).isAdditionalProperties = true
+ } else {
+ // Create from additionalProperties with value-based type inference
+ schemaNode.properties![k] = makeChildFromAP(ap, vo[k])
+ }
// If it's an existing additional property, merge any nested structure
} else if ((schemaNode.properties![k] as any).isAdditionalProperties && ap?.properties) {
;(schemaNode.properties![k] as any).properties ??= _.cloneDeep(ap.properties)
diff --git a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
index 2d887c7..d69d711 100644
--- a/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
+++ b/src/components/molecules/BlackholeForm/organisms/BlackholeForm/utils.tsx
@@ -394,9 +394,11 @@ export const getArrayFormItemFromSwagger = ({
{(fields, { add, remove }, { errors }) => (
<>
{fields.map(field => {
- const fieldType = (
+ const rawFieldType = (
schema.items as (OpenAPIV2.ItemsObject & { properties?: OpenAPIV2.SchemaObject }) | undefined
)?.type
+ // Handle type as string or string[] (OpenAPI v2 allows both)
+ const fieldType = Array.isArray(rawFieldType) ? rawFieldType[0] : rawFieldType
const description = (schema.items as (OpenAPIV2.ItemsObject & { description?: string }) | undefined)
?.description
const entry = schema.items as
@@ -577,7 +579,29 @@ export const getArrayFormItemFromSwagger = ({
type="text"
size="small"
onClick={() => {
- add()
+ // Determine initial value based on item type
+ const fieldType = (
+ schema.items as (OpenAPIV2.ItemsObject & { properties?: OpenAPIV2.SchemaObject }) | undefined
+ )?.type
+
+ let initialValue: unknown
+ // Handle type as string or string[] (OpenAPI v2 allows both)
+ const typeStr = Array.isArray(fieldType) ? fieldType[0] : fieldType
+ if (typeStr === 'string') {
+ initialValue = ''
+ } else if (typeStr === 'number' || typeStr === 'integer') {
+ initialValue = 0
+ } else if (typeStr === 'boolean') {
+ initialValue = false
+ } else if (typeStr === 'array') {
+ initialValue = []
+ } else if (typeStr === 'object') {
+ initialValue = {}
+ } else {
+ initialValue = ''
+ }
+
+ add(initialValue)
}}
>
<PlusIcon />

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,16 +1,18 @@
diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
index 577ba0f..018df9c 100644
index ac56e5f..c6e2350 100644
--- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
+++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
@@ -1,11 +1,16 @@
@@ -1,6 +1,6 @@
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, useBuiltinResources, useApiResources } 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'
@@ -11,6 +11,11 @@ import {
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
import { Styled } from './styled'
+import {
+ BASE_PROJECTS_API_GROUP,
@@ -20,22 +22,22 @@ index 577ba0f..018df9c 100644
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>()
@@ -33,9 +38,11 @@ export const ListInsideClusterAndNs: FC = () => {
typeof CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME.length > 0
- const namespacesData = useBuiltinResources({
+ const namespacesData = useApiResources({
clusterName: cluster,
clusterName: selectedCluster || '',
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
isEnabled: selectedCluster !== undefined && !isCustomNamespaceResource,
})
diff --git a/src/hooks/useNavSelectorInside.ts b/src/hooks/useNavSelectorInside.ts
index d69405e..5adbd5d 100644
index 5736e2b..1ec0f71 100644
--- a/src/hooks/useNavSelectorInside.ts
+++ b/src/hooks/useNavSelectorInside.ts
@@ -1,6 +1,11 @@
@@ -63,8 +65,8 @@ index d69405e..5adbd5d 100644
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
isEnabled: Boolean(clusterName),
})
diff --git a/src/utils/getBacklink.ts b/src/utils/getBacklink.ts
index a862354..f24e2bc 100644
--- a/src/utils/getBacklink.ts

View File

@@ -0,0 +1,15 @@
diff --git a/src/components/organisms/Header/organisms/User/User.tsx b/src/components/organisms/Header/organisms/User/User.tsx
index efe7ac3..80b715c 100644
--- a/src/components/organisms/Header/organisms/User/User.tsx
+++ b/src/components/organisms/Header/organisms/User/User.tsx
@@ -23,10 +23,6 @@ export const User: FC = () => {
// key: '1',
// label: <ThemeSelector />,
// },
- {
- key: '2',
- label: <div onClick={() => navigate(`${baseprefix}/inside/clusters`)}>Inside</div>,
- },
{
key: '3',
label: (

File diff suppressed because one or more lines are too long

View File

@@ -42,6 +42,8 @@ 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"
- name: LOGGER_WITH_HEADERS
@@ -122,6 +124,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 +148,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:b942d98ff0ea36e3c6e864b6459b404d37ed68bc2b0ebc5d3007a1be4faf60c5
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:5ddc6546baf3acdb8e0572536665fe73053a7f985b05e51366454efa11c201d2
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

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

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