Compare commits

..

146 Commits

Author SHA1 Message Date
Timofei Larkin
072aa9ebc0 Release v0.37.0 (#1504)
This PR prepares the release `v0.37.0`.
2025-10-10 12:27:31 +04:00
cozystack-bot
aff8b0c30a Prepare release v0.37.0
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-10 07:46:34 +00:00
Andrei Kvapil
51883cfc69 Release v0.37.0-beta.2 (#1496)
This PR prepares the release `v0.37.0-beta.2`.

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

- Chores
- Upgraded many platform components and container images to
v0.37.0-beta.2 (installer, controllers, API, dashboard services,
networking, storage, MySQL backup, KubeVirt CSI, NGINX cache, and
related sidecars). Image digests/tags updated only; no user-facing
configuration or behavior changes.
- Style
  - Dashboard tenant version now shown as v0.37.0-beta.2.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-10 02:54:15 +02:00
Andrei Kvapil
29a6cdec05 Update MAINTAINERS.md (#1491)
Exclude Andrei Gumilev

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

* **Documentation**
* Updated the Maintainers documentation to reflect the current team by
removing an outdated entry.
* Improves accuracy of ownership and contact information for project
stewardship.

* **Chores**
* Performed repository housekeeping to keep governance information
current.
  * No changes to product functionality; no user-facing impact.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-10 02:29:30 +02:00
Andrei Kvapil
929dae8e24 Update CONTRIBUTOR_LADDER.md (#1492)
<!-- 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

* **Documentation**
* Updated the Contributor Ladder guide title for clarity and consistency
across the documentation.
* Adjusted in-page navigation to point to the correct section, ensuring
links align with the updated heading.
* Standardized section anchors to improve reliability of internal
navigation.
* Improved readability and structure without affecting product
functionality or user workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-10 02:28:56 +02:00
cozystack-bot
a50f53de2e Prepare release v0.37.0-beta.2
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-10 00:21:12 +00:00
Andrei Kvapil
484211f7a0 [kubernetes] fix: spec.selector: Required value (#1502)
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

- **Bug Fixes**
- Corrected MachineDeployment label selectors to match existing template
labels, ensuring resources are properly targeted and managed.
- Improves reliability of scaling and rolling updates by preventing
orphaned or unmanaged machines/pods.
- Aligns selectors with cluster and deployment labels, enabling
consistent behavior across environments.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-10 02:15:36 +02:00
Andrei Kvapil
b6eefe4453 [dashboard] Remove Tenant resource from Marketplace; fix field override when typing (#1503)
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
[]
```
2025-10-10 02:14:19 +02:00
Andrei Kvapil
3b9fa33240 [dashboard] Remove Tenant resource from Marketplace; fix field override when typing
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-10 02:13:12 +02:00
Andrei Kvapil
9184450b39 [kubernetes] fix: spec.selector: Required value
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-10 02:01:41 +02:00
Andrei Kvapil
59f42de1db [dashboard] Fix listing modules (#1501)
<img width="2620" height="1970" alt="image"
src="https://github.com/user-attachments/assets/a8d0417b-214f-4c6c-8cab-2539043c62e8"
/>

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
[]
```
2025-10-10 01:25:38 +02:00
Andrei Kvapil
2ae926d04e [dashboard] Fix listing modules
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-10 01:24:10 +02:00
Andrei Kvapil
0ba4d4494e [dashboard] Add filter for tenantresources (#1500)
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
[]
```
2025-10-09 23:42:59 +02:00
Andrei Kvapil
19c91071d8 [dashboard] Add filter for tenantresources
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-09 23:42:08 +02:00
Andrei Kvapil
9ce3f8e53f [dashboard] fix yaml highlighting and handle x-preserve-unknown-fields (#1499)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[]
```

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

## Summary by CodeRabbit

* **Chores**
* Updated the OpenAPI UI container build to use a newer underlying
toolkit revision, refreshing dependencies and ensuring alignment with
upstream.
* Improves build reliability and maintainability with routine
configuration maintenance.
  * No user-facing features or behavior changes are expected.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 23:41:45 +02:00
Andrei Kvapil
7eb701d846 [dashboard] fix yaml highlighting and handle x-preserve-unknown-fields
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-09 23:33:10 +02:00
Andrei Kvapil
40a3ec1e70 [installer] Even more rigorous migration (#1498)
## What this PR does

Due to a deficiency of cozypkg (--with-source reconciles the HelmChart,
but not the HelmRepository), we have to use workarounds to bulletproof
the latest migration, by applying directly from the assets server.

### Release note

```release-note
[installer] Run 20th migration using helm charts directly from the
assets server instead of relying on cozypkg to reconcile its resources
properly.
```

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

- **Bug Fixes**
- Mutating webhook now excludes both the default and kube-system
namespaces to avoid unintended mutations of core workloads.

- **Chores**
- Hardened migration sequence: ordered release removals with waits,
switched to packaged apply steps with short pauses, added readiness
checks, removed obsolete webhook upgrade/reconciliation, and
standardized RFC3339(nano) migration stamping.
- Removed bundled resource-definition CRD and adjusted CRD
generation/output handling.
- Installer image now includes Helm as a runtime/build-time dependency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 23:31:00 +02:00
Timofei Larkin
43c222decf [installer] Even more rigorous migration
Due to a deficiency of cozypkg (--with-source reconciles the HelmChart,
but not the HelmRepository), we have to use workarounds to bulletproof
the latest migration, by applying directly from the assets server.

```release-note
[installer] Run 20th migration using helm charts directly from the
assets server instead of relying on cozypkg to reconcile its resources
properly.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-09 21:34:14 +03:00
Timofei Larkin
13dccea84b [lineage] Use an auto-refreshing RESTMapper (#1497)
## What this PR does

Since the Cozystack extension API can now change dynamically while there
are live clients (the lineage webhook) querying this API, the REST
mapper of the client should "expect" that things may change and refresh
their discovery information when they get a cache miss to see if new
kinds have been registered.

### Release note

```release-note
[lineage] Use an auto-refreshing RESTMapper in the webhook's API client
that tries to update its API discovery info when it fails to GET a
resource kind that was previously not registered in its schema.
```

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

- Refactor
- Streamlined webhook initialization by removing redundant
discovery/cache components, reducing startup complexity and overhead.
- Improved error handling during webhook setup for clearer diagnostics
on manager startup.
- Reduced runtime dependencies to improve reliability across diverse
cluster environments.
- Minor import and initialization cleanups to align with current
controller-runtime practices.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 15:42:30 +04:00
Timofei Larkin
e2b4cd8bd0 [lineage] Use an auto-refreshing RESTMapper
Since the Cozystack extension API can now change dynamically while there
are live clients (the lineage webhook) querying this API, the REST
mapper of the client should "expect" that things may change and refresh
their discovery information when they get a cache miss to see if new
kinds have been registered.

```release-note
[lineage] Use an auto-refreshing RESTMapper in the webhook's API client
that tries to update its API discovery info when it fails to GET a
resource kind that was previously not registered in its schema.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-09 13:12:06 +03:00
Andrei Kvapil
ad2858e113 Update CODE_OF_CONDUCT.md (#1493)
<!-- 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

* **Documentation**
* Expanded the Code of Conduct with a Vendor Neutrality Manifesto
outlining commitments, principles, and expectations for interactions
with vendors and community members.
* Added an affirmation and signature section to reinforce accountability
and clarity.
* Clarifies standards for fair, transparent collaboration and community
engagement.
* No product or UI changes; this update improves guidance for
contributors and partners.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 09:22:42 +02:00
Andrei Kvapil
c6e9131f60 [oidc] Check APIVersions before deploying (#1495)
## What this PR does

When enabling OIDC, the Tenant applications may try to deploy
KeycloakRealmGroups before the Keycloak operator is live. This may lead
to a race where neither HelmRelease is able to progress. This patch
addresses this.

### Release note

```release-note
[oidc] Do not deploy KeycloakRealmGroup resources as part of the Tenant
application if the v1.edp.epam.com API is not yet available.
```

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improves deployment reliability by conditionally creating the initial
Keycloak realm group only when the required API version is available.
This prevents install/upgrade failures in environments lacking the
corresponding CRD.
* Other Keycloak realm groups continue to be created as before, ensuring
no change to existing group provisioning where supported.
* Enhances cross-environment compatibility for tenant deployments
without impacting users on fully supported clusters.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-08 22:32:09 +02:00
Timofei Larkin
45036ff249 [oidc] Check APIVersions before deploying
When enabling OIDC, the Tenant applications may try to deploy
KeycloakRealmGroups before the Keycloak operator is live. This may
lead to a race where neither HelmRelease is able to progress. This patch
addresses this.

```release-note
[oidc] Do not deploy KeycloakRealmGroup resources as part of the Tenant
application if the v1.edp.epam.com API is not yet available.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-08 20:37:21 +03:00
Timofei Larkin
6dd08947ae [api,platform] Decouple CozyRDs from API HR (#1494)
## What this PR does

This commit patches the Cozystack API server to tolerate an absence of
Cozystack Resource Definitions either registered as CRDs on the k8s API
or simply as an absence of CozyRDs persisted to etcd. This decouples the
upgrade of the CozyRD CRD from the upgrade of the Cozystack API.

### Release note

```release-note
[api,platform] Decouple the Cozystack API from the Cozystack Resource
Definitions, allowing independent upgrades of either one and a more
reliable migration from 0.36 to 0.37.
```

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

## Summary by CodeRabbit

- New Features
- Introduced Cozystack Resource Definition CRD and charts, now included
in hosted and full bundles to provision CRDs before dependent
components.

- Bug Fixes
- Improved startup reliability by retrying resource discovery with
exponential backoff, reducing failures on slow cluster readiness.
- OpenAPI generation no longer errors when no kinds are present,
preventing unnecessary startup failures.

- Chores
  - Added packaging scaffolding and default values for new charts.
  - Updated internal script paths for CRD generation outputs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-08 18:35:37 +04:00
Timofei Larkin
a1fd97f2d7 Update issue templates (#1408)
Add an issue template for bug reports.
2025-10-08 18:31:55 +04:00
Timofei Larkin
8076f120d8 Update .github/ISSUE_TEMPLATE/bug_report.md
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-08 17:30:56 +03:00
Timofei Larkin
4e766ed82e [api,platform] Decouple CozyRDs from API HR
This commit patches the Cozystack API server to tolerate an absence of
Cozystack Resource Definitions either registered as CRDs on the k8s API
or simply as an absence of CozyRDs persisted to etcd. This decouples the
upgrade of the CozyRD CRD from the upgrade of the Cozystack API.

```release-note
[api,platform] Decouple the Cozystack API from the Cozystack Resource
Definitions, allowing independent upgrades of either one and a more
reliable migration from 0.36 to 0.37.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-08 16:18:47 +03:00
Timur Tukaev
d42f4b1097 Update CODE_OF_CONDUCT.md
Signed-off-by: Timur Tukaev <90071493+tym83@users.noreply.github.com>
2025-10-08 09:43:34 +05:00
Timur Tukaev
6b6cee8103 Update CONTRIBUTOR_LADDER.md
Signed-off-by: Timur Tukaev <90071493+tym83@users.noreply.github.com>
2025-10-08 09:28:27 +05:00
Timur Tukaev
7f62e14e86 Update MAINTAINERS.md
Exclude Andrei Gumilev

Signed-off-by: Timur Tukaev <90071493+tym83@users.noreply.github.com>
2025-10-08 09:16:26 +05:00
Timur Tukaev
a369171a20 Create CONTRIBUTOR_LADDER.md (#1224)
Contributor ladder is an important tool for community participants who
are loyal to project and would like to take more responsibility in
project. Besides, it's needed for CNCF Incubated applications

<!-- 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
Adding description on how community member might become a contributor
and a project maintainer.

### Release note
v0.1

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

## Summary by CodeRabbit

* **Documentation**
* Added a contributor ladder document outlining roles, responsibilities,
and progression paths for project contributors, including policies on
advancement, inactivity, and removal. Links to related resources and
contact information are also provided.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-08 09:15:11 +05:00
Andrei Kvapil
dfcdf19554 Release v0.37.0-beta.1 (#1490)
This PR prepares the release `v0.37.0-beta.1`.

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

## Summary by CodeRabbit

- Chores
- Pinned multiple container images from “latest” to specific versions
and refreshed digests for improved stability and reproducibility.
- Upgraded core components from v0.37.0-alpha.2 to v0.37.0-beta.1 across
installer, API, controller, dashboard services, Kamaji, kubeovn tools,
and object storage sidecar/controller.
- Updated Cilium to 1.17.8 and refreshed digests for KubeOVN, MetalLB,
Grafana, and related apps.
- Documentation
  - Dashboard branding text updated to display v0.37.0-beta.1.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 23:02:48 +02:00
cozystack-bot
458ca63729 Prepare release v0.37.0-beta.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-10-06 20:18:29 +00:00
Timofei Larkin
1ee3d00128 [etcd] Add VPA for etcd (#1489)
## What this PR does

The etcd tenant module deploys by default with a large resource
limit/request and these values are not exposed at deploy time. This
patch lowers the default resources and adds a VPA to autoconfigure them
according to the real needs.

### Release note

```release-note
[etcd] Attach VPA to etcd and lower initial default resource requests.
```

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

- New Features
- Enabled automatic resource autoscaling for etcd with a Vertical Pod
Autoscaler (VPA).

- Chores
- Updated default etcd resource requests to CPU 1000m and memory 512Mi
(previously 4 and 1Gi), reflected across chart values and API schema.
  - Changed the output location for generated CRDs.

- Documentation
- Revised README to document the new default CPU and memory values for
etcd.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 23:43:40 +04:00
Andrei Kvapil
00199a788a Upd Velero v1.17.0 (#1484)
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

- enables nodeAgent by default
- fixes https://github.com/cozystack/cozystack/issues/1442

### 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
- Per-repository maintenance via ConfigMap with global and repo-specific
settings.
- PodVolumeBackup/Restore: cancel requests, progress reporting,
node/uploader visibility, expanded phases.
  - New volumeGroupSnapshotLabelKey on Backups and Schedules.
  - DataUpload: specify CSI driver.
  - Metrics Service: ipFamilyPolicy and ipFamilies support.
  - Optional container resizePolicy.

- Changes
  - Upgraded to Velero 1.17.0; Helm chart v11.0.0.
  - Deployment name standardized to “velero”.
  - Node agent enabled by default.
  - Templates now block deprecated options with clear error messages.

- Documentation
- Expanded README on repository maintenance, deprecations, and upgrade
guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 21:00:45 +02:00
Andrei Kvapil
dfb0838a1e feat/impruvement-kubernetes-tests (#1485)
<!-- 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
Improved tests for verifying installed kubernetes client clusters
```

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

* New Features
* Added node readiness checks (expected node count, detailed node
display) and kubelet version validation with compatibility handling.

* Improvements
* Increased API port-forward timeout and extended rollout/machine
deployment waits for more reliable rollouts.
  * Added per-component readiness waits for core cluster services.

* Chores
  * Bumped default Kubernetes version to v1.33.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 20:59:59 +02:00
Timofei Larkin
42c9d65c7c [etcd] Add VPA for etcd
The etcd tenant module deploys by default with a large resource
limit/request and these values are not exposed at deploy time. This
patch lowers the default resources and adds a VPA to autoconfigure them
according to the real needs.

```release-note
[etcd] Attach VPA to etcd and lower initial default resource requests.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-06 21:57:33 +03:00
Andrei Kvapil
4afda63440 Upd Velero v1.17.0
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-06 17:59:48 +02:00
Andrei Kvapil
50b8dda38e Add me to MAINTAINERS.md (#1487)
Add me to MAINTAINERS

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

## Summary by CodeRabbit

* **Documentation**
* Added Nikita Bykov to the public maintainers list, including name,
GitHub handle, company, and area of responsibility.
* Ensures the maintainer roster is current and transparent for
contributors and users seeking points of contact.
  * No product functionality, UI, or API behavior changes.
  * Helps improve project governance visibility and support routing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 12:46:26 +02:00
Andrei Kvapil
e3cebeab47 [kafka] Disable noisy alerts (#1488)
## What this PR does

The alerts deployed with the Kafka Strimzi operator are noisy and not
useful, when a given namespace does not deploy any kafka clusters. This
patch removes them.

### Release note

```release-note
[kafka] Disable useless alerts for Kafka which fire when not called for,
e.g. when Kafka isn't deployed.
```

fixes https://github.com/cozystack/cozystack/issues/790

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

## Summary by CodeRabbit

* **Chores**
* Temporarily disabled rendering of monitoring alert snippets for the
Kafka Operator, resulting in no alerts being generated from this
component.
* Keeps existing deployments unaffected beyond the absence of these
alerts; no configuration changes required by users.
* Preserves previous alert definitions internally for potential
reactivation in a future update.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 12:45:53 +02:00
Timofei Larkin
e986e7c16a [controller, api] Select ingresses and services (#1486)
## What this PR does

This patch extends the resource-selecting function of the webhook to
also apply selectors to ingresses and services, like has been already
done for secrets. The Cozystack resource definitions have been upgraded
to contain two more fields: `ingresses` and `services` and populated
with counterparts of the legacy selectors from the dashboard roles.

### Release note

```release-note
[controller, api] Enable marking ingresses and services as user-facing
and implement selectors for existing CozystackResourceDefinitions.
```

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

* **New Features**
* CRD and API now support selecting Services and Ingresses alongside
Secrets.
* Lineage/labeling logic updated to evaluate Services and Ingresses when
computing tenant/resource labels.
* System resource definitions updated to expose Service/Ingress
selectors across many system apps (Bucket, Bootbox, ClickHouse, etcd,
Ferretdb, Ingress, Kafka, Kubernetes, Monitoring, MySQL, NATS, Postgres,
RabbitMQ, Redis, SeaweedFS, VM Instance, VPN).
* VM service templates add apps.cozystack.io/user-service: "true" label.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-06 13:41:17 +04:00
Timofei Larkin
1f158fa909 [kafka] Disable noisy alerts
The alerts deployed with the Kafka Strimzi operator are noisy and not
useful, when a given namespace does not deploy any kafka clusters. This
patch removes them.

```release-note
[kafka] Disable useless alerts for Kafka which fire when not called for,
e.g. when Kafka isn't deployed.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-06 12:23:08 +03:00
Nikita
13dba1b8b4 Add me to MAINTAINERS.md
Signed-off-by: Nikita <166552198+nbykov0@users.noreply.github.com>
2025-10-06 12:17:48 +03:00
Timofei Larkin
9b0f919052 [controller, api] Select ingresses and services
This patch extends the resource-selecting function of the webhook to
also apply selectors to ingresses and services, like has been already
done for secrets. The Cozystack resource definitions have been upgraded
to contain two more fields: `ingresses` and `services` and populated
with counterparts of the legacy selectors from the dashboard roles.

```release-note
[controller, api] Enable marking ingresses and services as user-facing
and implement selectors for existing CozystackResourceDefinitions.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-06 11:31:09 +03:00
Andrei Kvapil
da0eb7a829 Update cilium v1.17.8 (#1473)
<!-- 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
- Added hooks to inject extra volumes/volumeMounts and a configurable
dnsPolicy for cilium-agent.
  - Introduced podSecurityContext.seccompProfile (type: Unconfined).

- Bug Fixes
- Refined kubeProxyReplacement-driven settings (healthz bind,
hostPort/nodePort) and broadened Hubble IPv6 preference logic.
  - Removed externalIPs configuration.

- Documentation
- Updated README to reflect new versions, image digests, security
context, and removed externalIPs references.

- Chores
- Bumped Cilium and related images to v1.17.8, Hubble UI to v0.13.3,
Envoy to v1.33.9; refreshed image digests and Dockerfile default
version.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-03 14:46:44 +02:00
IvanHunters
012906cd59 feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 10:54:09 +03:00
IvanHunters
f2cd585b45 feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 10:42:28 +03:00
IvanHunters
6937b8e2b6 feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 10:33:28 +03:00
IvanHunters
a8562f03d1 feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 10:25:34 +03:00
IvanHunters
2383bc9f13 feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 09:30:58 +03:00
IvanHunters
670341f6bd feat/impruvement-kubernetes-tests
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-10-03 09:07:14 +03:00
Andrei Kvapil
945887f30d [seaweedfs] Fix timeout while uploading hude files (#1483)
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

related to https://github.com/seaweedfs/seaweedfs/pull/7294

### 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**
* Allow supplying extra S3 server startup arguments via configuration,
enabling custom runtime flags for the S3 service.

* **Chores**
* Set default S3 idle timeout to 60 seconds for improved default
connection handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-02 18:46:26 +02:00
Andrei Kvapil
408b8dde3a [seaweedfs] Fix timeout while uploading hude files
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-10-02 13:00:07 +02:00
Timofei Larkin
295fdf1b8e [lineage, controller] Implement name selectors (#1477)
## What this PR does

This patch implements name-based selectors for
`CozystackResourceDefinitions.spec.secrets`. Application developers may
now specify secrets that should or should not be visible to end users by
specifying a `resourceNames` field with a string slice of acceptable
names. This will, for instance, let developers exclude a secret like
`postgres-dbname-superuser` that has a predictable name even if it does
not have predictable labels. Simple templates are supported, so
`postgres-{{ .name }}-superuser` is also a valid entry under
`resourceNames`.

### Release note

```release-note
[lineage, controller] Let application developers determine resource
visibility for end users by name, as well as by labels.
```

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

- **New Features**
- Resource selectors now support exact resource-name filtering (with
templating); include/exclude selectors operate at the resource level and
require both label and name matches.

- **Chores**
- Many service templates switched from label-based default exclusions to
empty excludes with explicit name-based includes.
- Updated several component image tags to latest and refreshed CRD
packaging/templates.
- Standardized tenant-resource label keys/values and adjusted secret
labels in manifests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-01 18:35:05 +04:00
Timofei Larkin
8d50dfb73f [controller,api] Specify visible secrets
This patch carries the selectors for secrets to be shown to end users
over from the legacy dashboard-resourcemap roles into the new
CozystackResourceDefinition selectors. Also a {{ .namespace }} template
variable is added to the variables supported in the `resourceNames`
field in the selector.

```release-note
[controller,api] Support {{ .namespace }} in `resourceNames` resource
selectors, add whitelist of secrets to show to end-users.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-01 16:56:52 +03:00
Timofei Larkin
c16e37e079 [controller,api] Refactor tenant resource label
This patch refactors the secret selectors to use the
`internal.cozystack.io/tenantresource` label for managing secret
visibility and removes any selectors based on it or the previous
`apps.cozystack.io/tenantresource` label, the idea being that this label
will only ever be set by the controller.

```
[controller,api] Refactor labels for the secret selector.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-01 13:24:40 +03:00
Timofei Larkin
66004c83e2 [lineage, controller] Implement name selectors
This patch implements name-based selectors for
`CozystackResourceDefinitions.spec.secrets`. Application developers may
now specify secrets that should or should not be visible to end users by
specifying a `resourceNames` field with a string slice of acceptable
names. This will, for instance, let developers exclude a secret like
`postgres-dbname-superuser` that has a predictable name even if it does
not have predictable labels. Simple templates are supported, so
`postgres-{{ .name }}-superuser` is also a valid entry under
`resourceNames`.

```release-note
[lineage, controller] Let application developers determine resource
visibility for end users by name, as well as by labels.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-01 12:29:04 +03:00
Andrei Kvapil
86d6706ee1 Release v0.37.0-alpha.2 (#1481)
This PR prepares the release `v0.37.0-alpha.2`.

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

## Summary by CodeRabbit

- Chores
- Bumped platform version to v0.37.0-alpha.2 across core and system
components.
- Updated images for installer, e2e sandbox, API, controller, dashboard
(OpenAPI UI, K8s BFF, token proxy), Kamaji, kube-ovn (webhook and core),
object storage controller, SeaweedFS sidecar, S3 manager, and nginx
cache.
- Synchronized version string displayed in the dashboard to
v0.37.0-alpha.2.
- No functional behavior changes; updates focus on image/tag refreshes
and digests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-30 20:28:03 +02:00
cozystack-bot
6de14d679d Prepare release v0.37.0-alpha.2
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-30 17:46:53 +00:00
Andrei Kvapil
da13a6a2e5 [dashboard] fix: showing secrets with empty values (#1480)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[dashboard] fix: showing secrets with empty values
```

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

* **Bug Fixes**
* Fixed base64 extraction for secret data in the dashboard so secret
values are parsed and shown correctly.
* Addresses cases where secret fields could appear blank or incorrect
due to parsing issues.
* Improves consistency and reliability of secret-related columns across
dashboard views, reducing confusion and manual checks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-30 19:34:51 +02:00
Andrei Kvapil
82926a8b2a [dashboard] fix: showing secrets with empty values
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-30 19:04:40 +02:00
Andrei Kvapil
cbc7070269 feature/make info app unconditionally (#1474)
<!-- 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
- make info app unconditionally
```

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

- New Features
- Dashboard resource mapping now adapts to the OIDC setting, switching
resource names and RBAC subjects accordingly for OIDC and non-OIDC
environments.
- Bug Fixes
- Helm release is now consistently deployed without being gated by the
OIDC flag, ensuring reliable rollout across environments.
- Refactor
- Introduced configuration-driven branching for resource names and
access subjects in the dashboard, improving alignment with environment
settings and reducing manual adjustments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-30 12:13:05 +02:00
Andrei Kvapil
94375f3161 [seaweedfs] Fix setting size for multi-dc volumes (#1476)
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 setting size for multi-dc volumes
```

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

* **Bug Fixes**
* Per-zone data directory size now falls back to the global volume size
value when a zone doesn’t specify one, fixing incorrect fallback
behavior.
* Users relying on the previous fallback may need to set zone-specific
sizes to preserve prior behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-30 12:11:28 +02:00
Andrei Kvapil
0bdc801d9a Fix migration to v0.37.0 (#1475)
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

* **Bug Fixes**
* Prevented unintended deletion of platform resource definitions during
migrations.
* Made timestamp generation resilient to environment differences to
avoid script failures.
* Made annotation steps tolerate failures so migrations continue if
overwrite fails.

* **Chores**
* Re-enabled automatic chart update path and added periodic
reconciliation to keep platform components up to date.

* **Refactor**
* Switched VM cloud-init to use native Kubernetes Secret for improved
compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-30 12:11:17 +02:00
Timofei Larkin
4e618adf0a [monitoring] Add whitelist for labels in cadvisor/kubelet metrics
This patch introduces a whitelist-based label filtering mechanism in
cadvisor/kubelet metrics collection. By explicitly keeping only the
desired labels, we avoid noisy and high-cardinality dimensions while
retaining meaningful CPU metrics for analysis.

This improves the stability of the metrics pipeline and ensures
consistent visibility into application workloads.

```release-note
[monitoring] Introduce whitelist label filtering for cadvisor/kubelet
metrics to reduce noise and improve CPU metric reliability.
```
2025-09-30 13:21:38 +04:00
IvanHunters
8601299a91 [platform] Add whitelist for labels in cadvisor/kubelet metrics
This patch introduces a whitelist-based label filtering mechanism in
cadvisor/kubelet metrics collection. By explicitly keeping only the
desired labels, we avoid noisy and high-cardinality dimensions while
retaining meaningful CPU metrics for analysis.

This improves the stability of the metrics pipeline and ensures
consistent visibility into application workloads.

```release-note
[platform] Introduce whitelist label filtering for cadvisor/kubelet
metrics to reduce noise and improve CPU metric reliability.
```

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2025-09-30 11:04:19 +03:00
kklinch0
65bee1a8dc feature/make info app unconditionally
Signed-off-by: kklinch0 <kklinch0@gmail.com>
2025-09-30 00:40:28 +03:00
Andrei Kvapil
ffb1b89d2e [seaweedfs] Fix setting size for multi-dc volumes
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-29 16:57:16 +02:00
Andrei Kvapil
bb9db7fcaf Fix migration to v0.37.0
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-29 08:44:29 +02:00
Andrei Kvapil
1753df590e Update Cilium v1.17.8
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-26 15:08:37 +02:00
Andrei Kvapil
7c1e103197 Release v0.37.0-alpha.1 (#1467)
This PR prepares the release `v0.37.0-alpha.1`.

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

- New Features
  - None
- Bug Fixes
  - None
- Chores
- Pinned many container images to explicit versioned tags and digests
for stability and reproducibility.
- Upgraded core components to v0.37.0-alpha.1 (installer, API,
controller, dashboard services, Kamaji, object storage, sidecars).
- Updated third-party images: Cilium 1.17.5, Kube-OVN v1.14.5, MetalLB
digests, s3manager v0.5.0, Ubuntu container disk v1.32, Grafana 0.0.0.
- Style
  - Dashboard branding updated to show v0.37.0-alpha.1.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-26 12:11:03 +02:00
cozystack-bot
93ddc4e2c4 Prepare release v0.37.0-alpha.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-26 08:34:15 +00:00
Andrei Kvapil
ded6a9fd69 Flux Operator 0.29.0 (#1466)
Release tag:

*
https://github.com/controlplaneio-fluxcd/flux-operator/releases/tag/v0.29.0

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

## Summary by CodeRabbit

- New Features
- Added multitenancy workload identity support, including enablement
toggle and default workload identity service account.
- FluxInstance gains fields for multitenant workload identity and
default service accounts, plus schema validations for safer configs.
- ResourceSet introduces input strategy (Flatten/Permute) and enhanced
input provider references and validations.

- Documentation
- Updated README to reflect new multitenancy settings and version
badges.

- Chores
- Bumped Helm chart versions/appVersions to 0.29.0 across related
charts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-26 10:25:01 +02:00
Andrei Kvapil
bff8a5b8c7 [kubernetes] Fix coredns tag (#1469)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does

fixes https://github.com/cozystack/cozystack/issues/1468

### Release note

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

```release-note
[]
```

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

## Summary by CodeRabbit

* **Chores**
* Pinned CoreDNS image to registry.k8s.io/coredns/coredns:v1.12.4 for
consistent, reproducible deployments.
  * Confirmed replica count remains at 2 (no scaling changes).

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-26 10:23:57 +02:00
Andrei Kvapil
8be9ac48ba [kubernetes] Fix coredns tag
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-26 09:47:51 +02:00
Kingdon B
fcab75177e Flux Operator 0.29.0
Signed-off-by: Kingdon B <kingdon@urmanac.com>
2025-09-25 20:40:44 -04:00
Andrei Kvapil
b13ce92024 [dashboard] Cumulative fixes (#1465)
- **Exclude bootbox from marketplace**
- **[dashboard] fix: disable auto-expanding**

<!-- 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
[]
```
2025-09-25 23:38:47 +02:00
Andrei Kvapil
ab11b8e4dd [dashboard] fix: disable auto-expanding
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 23:36:46 +02:00
Andrei Kvapil
e9403425a7 Exclude bootbox from marketplace
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 23:36:38 +02:00
Andrei Kvapil
28cd5bebf2 [dashboard] Cumulative fixes (#1463)
- **[dashboard] Fix API group for the applications**
- **fix sidebars**
- **Introduce module parameter**
- **fix keysAndTags for info**
- **always prefill name in dashboard**
- **Add factory for ingress resources**
- **Add formated tables for tenantnamespaces**

<!-- 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
- Module-aware dashboards: module resources are grouped under “Modules”
in the sidebar and shown as “Tenant Modules” in breadcrumbs.
- New Kubernetes details views for Services, Secrets, and Ingresses with
enriched tabs and ingress rules.

- Improvements
  - Marketplace hides module resources to reduce clutter.
  - Consistent navigation and links aligned to tenant namespaces.
- Sidebars expanded with additional built-in Kubernetes entries and
per-resource detail sidebars.
- Custom forms now always prefill the name field for smoother creation
flows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 20:10:10 +02:00
Andrei Kvapil
364cba3100 Add formated tables for tenantnamespaces
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:01:07 +02:00
Andrei Kvapil
dd76166e44 Add factory for ingress resources
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:01:07 +02:00
Andrei Kvapil
ef7dcabe64 always prefill name in dashboard
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:01:06 +02:00
Andrei Kvapil
b4c9ca36a9 fix keysAndTags for info
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:01:06 +02:00
Andrei Kvapil
37f9065d55 Introduce module parameter
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:01:06 +02:00
Andrei Kvapil
f130895b30 fix sidebars
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 19:00:39 +02:00
Andrei Kvapil
907dcb5e8b [dashboard] Fix API group for the applications
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 17:14:35 +02:00
Andrei Kvapil
d52a2fbe94 [dashboard] Fix /docs redirect (#1462)
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
[]
```
2025-09-25 17:04:21 +02:00
Andrei Kvapil
f41ab0d251 [dashboard] Fix /docs redirect
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 17:03:55 +02:00
Andrei Kvapil
58b7a6456c [dashboard] Add branding config (#1460)
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
- Introduces dynamic branding support. Tenant name, footer text, title,
logo text, logo SVG, and icon SVG can now be customized via cluster
configuration.
- Branding values are pulled automatically at runtime, enabling
per-tenant look and feel without app redeploys.
- Ensures consistent, centralized control over visual identity across
the dashboard.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 17:01:49 +02:00
Andrei Kvapil
772d663bc1 [cozystack-api] Specify tenantmodules labels (#1461)
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
- Tenant components are now consistently tagged as modules, enabling
clearer grouping, filtering, and management in dashboards and APIs.
- Improves discoverability and automation by making module scope
explicit across tenant services.

- Chores
- Standardized an internal module label across tenant releases and
system resource definitions (etcd, ingress, monitoring, SeaweedFS, info)
for consistency.
  - Metadata-only update with no runtime behavior changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 17:01:34 +02:00
Andrei Kvapil
e5c1cf97bd [cozystack-api] Specify tenantmodules labels
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 16:37:19 +02:00
Andrei Kvapil
7605df5f29 [dashboard] Add branding config
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 16:17:58 +02:00
Andrei Kvapil
df89117fa1 [dashboard] refactor dashboard configuration (#1457)
- Refactor code for dashboard resources creation
- Move dashboard-config helm chart to dynamic dashboard controller

<!-- 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**
* Static dashboard resources (breadcrumbs, factories, forms, marketplace
panels, table mappings) are initialized at startup and materialized
automatically.

* **Improvements**
* Unified UI construction with consistent badges, headers and
deterministic IDs.
  * Automatic cleanup of stale/orphaned dashboard resources.
  * Increased controller client throughput for faster operations.

* **Refactor**
* Consolidated static dashboard resource generation into a unified,
config-driven flow.

* **Chores**
* Removed legacy dashboard-config templates; updated controller and
dashboard image digests.
  * Added dashboard ConfigMap and wired UI env vars to it.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 16:16:50 +02:00
Andrei Kvapil
9873011ebf [dashboard] refactor dashboard configuration
- Refactor code for dashboard resources creation
- Move dashboard-config helm chart to dynamic dashboard controller
- Move white-label configuration to separate configmap

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 14:57:33 +02:00
Andrei Kvapil
b25aa10243 [dashboard] fix listing namespaces as unprivileged user (#1456)
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

* **Improvements**
* Updated namespace data source to a new API, ensuring tenant namespaces
display correctly and stay in sync.

* **Bug Fixes**
* Improved reliability of streamed requests by removing problematic
headers, preventing errors during form-based operations.

* **Chores**
* Adjusted image build process to apply patches during build, enabling
quicker delivery of fixes without altering runtime behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 14:30:58 +02:00
Andrei Kvapil
f3b317ceea Update Cozystack screenshot (#1459)
ref https://github.com/cozystack/website/pull/335

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

* **Documentation**
* Updated README screenshot to use the dark-theme image, improving
visual consistency for viewers using dark mode.
* Purely presentational change — no impact on application behavior or
public interfaces.
* Clarifies repository appearance for users browsing in different
themes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-25 14:30:21 +02:00
Andrei Kvapil
16496e238a Update Cozystack screenshot
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-25 14:28:22 +02:00
Timofei Larkin
c282d03330 [platform] Update lineage labels at upgrade (#1452)
## What this PR does

1. Since the VictoriaMetrics operator aggressively manages the metadata on
all owned components, the addition of labels by the lineage webhook
causes non-stop updates sent to the k8s API server. We mitigate this by
modifying the Monitoring Helm chart to set the `managedMetadata` field
on all VictoriaMetrics custom resources, where applicable.

2. This patch adds a migration script, that adds an annotation to all
resources that may be of interest, triggering an update event on the
lineage webhook. This will analyze the ancestor tree of these resources
and add labels to them, referencing their managing application.

3. This patch makes sure that migration #20 really uses the very latest
chart versions by forcing a reconcile with cozypkg, instead of
annotating the underlying HelmRelease.

### Release note

```release-note
[monitoring] Explicitly set lineage labels on VictoriaMetrics' resources
known not to play nice when something modifies their owned resources in
flight.
[platform] Add migration script to update pre-existing resources with
lineage labels.
[installer] Update cozypkg in installer and use it to bulletproof the
20th migration script by reconciling the HelmReleases with the
--with-source flag.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-25 15:55:32 +04:00
Timofei Larkin
0f8a9ac9ef [installer] Update cozypkg and improve migration 20
This patch makes sure that migration #20 really uses the very latest
chart versions by forcing a reconcile with cozypkg, instead of
annotating the underlying HelmRelease.

```release-note
[installer] Update cozypkg in installer and use it to bulletproof the
20th migration script by reconciling the HelmReleases with the
--with-source flag.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-25 12:03:22 +03:00
Timofei Larkin
21ca1349c4 [monitoring] Add lineage labels to VM components
Since the VictoriaMetrics operator aggressively manages the metadata on
all owned components, the addition of labels by the lineage webhook
causes non-stop updates sent to the k8s API server. We mitigate this by
modifying the Monitoring Helm chart to set the `managedMetadata` field
on all VictoriaMetrics custom resources, where applicable.

```release-note
[monitoring] Explicitly set lineage labels on VictoriaMetrics' resources
known not to play nice when something modifies their owned resources in
flight.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-25 10:53:34 +03:00
Andrei Kvapil
23e59ea654 New dashboard based on OpenAPI schema (#1269)
A new dashboard based on https://github.com/PRO-Robotech/openapi-ui
project

<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-01-00
OpenAPI UI"
src="https://github.com/user-attachments/assets/7ae04789-24ec-4e4b-830b-6f16e96513eb"
/>
<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-01-14
OpenAPI UI"
src="https://github.com/user-attachments/assets/ca5aa85d-43f0-4b5b-b87a-3bc237834f10"
/>
<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-02-05
OpenAPI UI"
src="https://github.com/user-attachments/assets/ebee7bfa-c3ac-4fe6-b5e1-43e9e7042c6a"
/>

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

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

```release-note
[cozystack-api] Implement TenantNamespace, TenantModules, TenantSecret and TenantSecretsTable resources
[cozystack-controller] Introduce new dashboard-controller
[dashboard] Introduce new dashboard based on openapi-ui
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-25 00:07:07 +03:00
Andrei Kvapil
c81a1aa2b0 [dashboard] fix listing namespaces as unprivileged user
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-24 21:38:12 +02:00
Andrei Kvapil
bb653e5a87 New dashboard based on OpenAPI schema (#1269)
A new dashboard based on https://github.com/PRO-Robotech/openapi-ui
project

<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-01-00
OpenAPI UI"
src="https://github.com/user-attachments/assets/7ae04789-24ec-4e4b-830b-6f16e96513eb"
/>
<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-01-14
OpenAPI UI"
src="https://github.com/user-attachments/assets/ca5aa85d-43f0-4b5b-b87a-3bc237834f10"
/>
<img width="1720" height="1373" alt="Screenshot 2025-08-01 at 09-02-05
OpenAPI UI"
src="https://github.com/user-attachments/assets/ebee7bfa-c3ac-4fe6-b5e1-43e9e7042c6a"
/>




<!-- 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
[cozystack-api] Implement TenantNamespace, TenantModules, TenantSecret and TenantSecretsTable resources
[cozystack-controller] Introduce new dashboard-controller
[dashboard] Introduce new dashboard based on openapi-ui
```
2025-09-24 20:11:41 +02:00
Andrei Kvapil
0afc3c1e86 [cozystack-api] Implement TenantNamespace, TenantModules, TenantSecret and TenantSecretsTable resources
[cozystack-controller] Introduce new dashboard-controller
[dashboard] Introduce new dashboard based on openapi-ui

Co-authored-by: kklinch0 <kklinch0@gmail.com>
Signed-off-by: kklinch0 <kklinch0@gmail.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-24 18:27:54 +02:00
Andrei Kvapil
789666d53b Remove versions_map logic (#1453)
TBD: How can we ensure that migrations were completed **before**
updating user-charts

## What this PR does

This PR removes logic for user apps versioning.
It is not needed anymore for new dashboard and does not make sence for
cozystack-api server, which always validates values accourding to the
latest spec from CozystackResourceDefinition.

### Release note

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

```release-note
[]
```

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

- Chores
- Removed legacy version maps and packaging scripts (including
gen_versions_map and package_chart); pre-commit hook for versions
removed.
- Makefiles updated to unified chart discovery and shared env includes;
logo copy step removed and installer image no longer bundles logos.
- Many charts’ version fields replaced with build-time placeholders
(0.0.0); appVersion metadata added.

- Refactor
  - Added standardized fix-charts and repo targets for packaging.
- HelmRelease defaults tightened: explicit version constraints, longer
intervals/timeouts, remediation retries, and upgrade.force.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-24 17:33:13 +02:00
Andrei Kvapil
152ab20a17 Update linage webhook configuration (#1454)
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**
* Expanded lineage webhook coverage to include WorkloadMonitor
resources, enabling automatic mutation and consistent metadata across
these workloads.
* Added mutation support for Ingresses, helping propagate lineage
metadata across HTTP entrypoints for improved traceability and
governance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-24 17:20:38 +02:00
Andrei Kvapil
9f9d8f8530 Allign timeouts for HelmReleases
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-24 16:54:17 +02:00
Andrei Kvapil
97f1b29975 Update linage webhook configuration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-24 14:58:16 +02:00
Andrei Kvapil
f871fbdb1e Remove versions_map logic
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-24 12:32:37 +02:00
Andrei Kvapil
5d76e6b626 Feat/webhook workload monitors (#1448)
## What this PR does

Many resources created as part of managed apps in cozystack (pods,
secrets, etc) do not carry predictable labels that unambiguously
indicate which app originally triggered their creation. Some resources
are managed by controllers and other custom resources and this
indirection can lead to loss of information. Other controllers sometimes
simply do not allow setting labels on controlled resources and the
latter do not inherit labels from the owner. This patch implements a
webhook that sidesteps this problem with a universal solution. On
creation of a pod/secret/PVC etc it walks through the owner references
until a HelmRelease is found that can be matched with a managed app
dynamically registered in the Cozystack API server. The pod is mutated
with labels identifying the managed app. This resubmission of the PR now
includes semantics to compare secrets to label selectors in
CozystackResourceDefinitions to determine, whether they should be marked
as user-facing or not.

### Release note

```release-note
[cozystack-controller] Add a mutating webhook to identify the Cozystack
managed app that ultimately owns low-level resources created in the
cluster and label these resources with a reference to said app.
```

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

## Summary by CodeRabbit

- New Features
- Adds a lineage mutating webhook that auto-applies ancestry labels to
core resources and VMCluster.
- Introduces secret include/exclude selectors in resource definitions
for fine-grained tenant secret visibility.
- Deploys webhook service with TLS via cert-manager (issuers,
certificates) and updates deployment to expose webhook port.

- Chores
- Updates numerous container images to latest tags and digests across
system and app components (controller, dashboard, kubeovn, cilium,
kamaji, storage, etc.).

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-24 11:10:19 +02:00
Timofei Larkin
4620f7dfa1 [platform] Add secret selectors to CozyRDs
This patch populates existing CozystackResourceDefinitions with minimal
working examples of secret selectors to take advantage of the newest
revision of the ancestor tracking webhook.

```release-note
[platform] Specify secret selectors for existing managed apps in their
respective CozystackResourceDefinitions, which provides the last bit of
information necessary for the lineage webhook to correctly mark secrets
as user-facing or not.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-24 12:07:21 +03:00
Timofei Larkin
562145e69b [cozystack-controller] Ancestor tracking webhook
Many resources created as part of managed apps in cozystack (pods,
secrets, etc) do not carry predictable labels that unambiguously
indicate which app originally triggered their creation. Some resources
are managed by controllers and other custom resources and this
indirection can lead to loss of information. Other controllers sometimes
simply do not allow setting labels on controlled resources and the
latter do not inherit labels from the owner. This patch implements a
webhook that sidesteps this problem with a universal solution. On
creation of a pod/secret/PVC etc it walks through the owner references
until a HelmRelease is found that can be matched with a managed app
dynamically registered in the Cozystack API server. The pod is mutated
with labels identifying the managed app.

```release-note
[cozystack-controller] Add a mutating webhook to identify the Cozystack
managed app that ultimately owns low-level resources created in the
cluster and label these resources with a reference to said app.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-24 12:07:18 +03:00
Timofei Larkin
0e1f73999b [platform] Add secret selectors to app definitions
This patch expands the CozystackResourceDefinitions with new label
selector fields to include and exclude secrets by their labelsets.
This will enable application developers to selectively show or hide
application secrets to and from end-users.

```release-note
[platform] Add selectors for application secrets, offering developers
an API to control secret visibility for end users.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-24 12:06:41 +03:00
Andrei Kvapil
744a0f3ca6 [platform] Mark some secrets as non-user-facing (#1446)
## What this PR does

Some k8s secrets created when deploying managed applications are
unhelpful to the end user or are outright not meant to be shown, because
they contain internal credentials not meant to be presented to the user.
This patch adds an `apps.cozystack.io/tenantresource=false` label to
such resources which will be later used to filter out such secrets in
the web UI.

### Release note

```release-note
[platform] Mark non-user-facing secrets as such to avoid clutter in the
dashboard and leaking internal credentials.
```

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

* **New Features**
* Automatic creation of a ServiceAccount token Secret via the Info
add-on.

* **Improvements**
  * VPN TLS Secret CA field standardized to ca.crt for consistency.

* **Removals**
* Removed the explicit ServiceAccount token Secret from the Tenant app
(token now managed by Info).

* **Chores**
  * Added non-functional metadata labels to several Secrets.
  * Bumped chart/package metadata versions and updated version mappings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-24 11:04:54 +02:00
klinch0
3ac83ac48c [k8s] add expanding persistent volumes in tenant clusters (#1438)
<!-- 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
- add expanding persistent volumes in tenant clusters
```

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

## Summary by CodeRabbit

- New Features
- Enabled PersistentVolumeClaim expansion in the KubeVirt CSI
StorageClass.
- Added CSI resizer sidecar to the controller for online volume
resizing.
- Introduced cluster-scoped RBAC to allow required access to
PersistentVolumes.

- Chores
- Updated Kubernetes app chart to 0.29.2 and set app version to 1.32.6.
  - Upgraded KubeVirt CSI driver image to 0.37.0.
  - Refreshed versions map entries for the new release.
- Simplified CoreDNS configuration to use the default image repository.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-23 19:58:54 +03:00
Andrei Kvapil
d991c49254 [platform] Add secret selectors to app definitions (#1447)
## What this PR does

This patch expands the CozystackResourceDefinitions with new label
selector fields to include and exclude secrets by their labelsets. This
will enable application developers to selectively show or hide
application secrets to and from end-users.

### Release note

```release-note
[platform] Add selectors for application secrets, offering developers
an API to control secret visibility for end users.
```

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

## Summary by CodeRabbit

* **New Features**
* Added support for configuring secret visibility on resource
definitions using include/exclude label selectors. This lets you
precisely control which secrets are considered without affecting
existing setups.
* The configuration is optional; if not set, behavior remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-23 18:29:56 +02:00
Timofei Larkin
48919c0cfe [platform] Add secret selectors to app definitions
This patch expands the CozystackResourceDefinitions with new label
selector fields to include and exclude secrets by their labelsets.
This will enable application developers to selectively show or hide
application secrets to and from end-users.

```release-note
[platform] Add selectors for application secrets, offering developers
an API to control secret visibility for end users.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-23 17:37:26 +03:00
Timofei Larkin
7e4883dfcc [platform] Mark some secrets as non-user-facing
Some k8s secrets created when deploying managed applications are
unhelpful to the end user or are outright not meant to be shown, because
they contain internal credentials not meant to be presented to the user.
This patch adds an `apps.cozystack.io/tenantresource=false` label to
such resources which will be later used to filter out such secrets in
the web UI.

```release-note
[platform] Mark non-user-facing secrets as such to avoid clutter in the
dashboard and leaking internal credentials.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-23 15:09:18 +03:00
Andrei Kvapil
66b53cb1ae [vm-disk] New SVG icon (#1435)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Co-authored-by: Viktoriia Kvapil
<159528100+kvapsova@users.noreply.github.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
[]
```
2025-09-22 16:29:58 +02:00
Andrei Kvapil
6005b76e96 [dashboard] Fix FerretDB spec (#1440)
## What this PR does

Due to a typo in the spec, the dashboard couldn't deploy or display
instances of FerretDB. This patch fixes the typo.

### Release note

```release-note
[dashboard] Fix FerretDB management in the web UI.
```

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

## Summary by CodeRabbit

- Bug Fixes
- Corrected FerretDB resource pluralization to “ferretdbs,” aligning
with Kubernetes conventions. This ensures resources display and behave
correctly in the dashboard, preventing discovery issues and errors in
listing, navigation, and management.
- Improves reliability of installs and upgrades with Flux/Helm workflows
by matching expected resource names. No other FerretDB settings were
changed, maintaining backward compatibility for existing configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-22 16:29:17 +02:00
Timofei Larkin
e34d9613c7 [dashboard] Fix FerretDB spec
Due to a typo in the spec, the dashboard couldn't deploy or display
instances of FerretDB. This patch fixes the typo.

```release-note
[dashboard] Fix FerretDB management in the web UI.
```

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-09-22 16:15:18 +03:00
kklinch0
ca19529c7d [k8s] add expanding persistent volumes in tenant clusters
Signed-off-by: kklinch0 <kklinch0@gmail.com>
2025-09-20 14:10:00 +03:00
Andrei Kvapil
b3be1f4e1e [vm-disk] New SVG icon
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Co-authored-by: Viktoriia Kvapil <159528100+kvapsova@users.noreply.github.com>
2025-09-18 16:27:38 +02:00
Andrei Kvapil
53fbe7c2ee Release v0.36.1 (#1434)
This PR prepares the release `v0.36.1`.
2025-09-18 05:26:48 +02:00
cozystack-bot
18ff789256 Prepare release v0.36.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-18 02:58:44 +00:00
Andrei Kvapil
3d02fbfba4 [cozystack-api] Update defaulting API schemas (#1433)
## 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
[cozystack-api] Update defaulting API schemas
```
2025-09-18 04:54:07 +02:00
Andrei Kvapil
8c6fc68367 [cozystack-api] Update defaulting API schemas
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-18 04:49:50 +02:00
Andrei Kvapil
9d2fe2605f [cozystack-api] Implement Kubernetes-like defaulting (#1432)
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**
- Application specs now get recursive, Kubernetes-like defaulting:
missing fields in nested objects and arrays are auto-populated safely
without mutating shared defaults.
- No changes to public APIs; existing manifests remain compatible while
gaining broader defaulting.

- **Tests**
- Added unit tests validating defaulting behavior, per-item defaults,
and non-creation of absent keys.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-18 03:01:39 +02:00
Andrei Kvapil
edb3e92585 [cozystack-api] Implement Kubernetes-like defaulting
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-18 02:32:16 +02:00
Andrei Kvapil
7118232490 Update ADOPTERS.md (#1429)
<!-- 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

* **Documentation**
* Added Hidora to the Adopters list, including contact
(@matthieu-robin), date (2025-09-17), and a description highlighting
Hikube’s Swiss-based, multi-datacenter, sovereign cloud capabilities.
Users can reference these details for real-world usage context.
* Updated the table with a spacer row to maintain formatting
consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:59:54 +02:00
Andrei Kvapil
19f81a2d32 [seaweedfs] fix seaweedfs migration (#1430)
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
  - None
- Bug Fixes
- Prevented duplicate or incorrect chart version updates during
migrations.
- Avoided failures when configuration values are missing or not
correctly structured.
- Preserved existing volume settings (size and storage class) during
updates.
- Refactor
- Reorganized migration steps to validate and create parent
configuration before modifying nested fields.
- Made the migration process more defensive and order-aware for smoother
upgrades.
- Chores
- Improved migration scripts for reliability during version and
configuration updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:59:42 +02:00
Andrei Kvapil
b93fe65992 [seaweedfs] fix seaweedfs migration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 22:58:43 +02:00
Andrei Kvapil
541347d321 [dashboard] Fix bitnami dependencies (#1431)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

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

## What this PR does


### Release note

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

```release-note
[]
```

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

## Summary by CodeRabbit

- Chores
- Updated container base images for the dashboard and APIs to maintained
legacy variants to improve build stability and align with security
patching.
- No user-facing changes: functionality, performance, and UI remain
unchanged.
- Runtime versions are consistent with previous releases; deployment
artifacts are equivalent.
- Existing workflows and configurations continue to work as before; no
action required from users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 22:58:03 +02:00
Andrei Kvapil
1827d29412 [dashboard] Fix bitnami dependencies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 22:34:31 +02:00
Andrei Kvapil
a1a107a90b Release v0.36.0 (#1428)
This PR prepares the release `v0.36.0`.

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

## Summary by CodeRabbit

- Chores
- Promoted images from v0.36.0-beta.4 to stable v0.36.0 across the
platform (installer, controller, API, dashboard, Kamaji,
kubeovn-webhook, kubeovn-plunger, object storage components, SeaweedFS
sidecar, matchbox, e2e).
- Updated image digests for multiple components to latest builds (nginx
cache, KubeVirt CSI driver, S3 manager, KubeOVN).
- Style
  - Dashboard now displays app version v0.36.0.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 16:45:35 +02:00
Matthieu ROBIN
6cd0a3409e Update ADOPTERS.md
Signed-off-by: Matthieu ROBIN <info@matthieurobin.com>
2025-09-17 14:26:55 +02:00
cozystack-bot
f5c575d12f Prepare release v0.36.0
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2025-09-17 11:45:04 +00:00
Andrei Kvapil
d10b3635cc [cozystack-controller] Implement cache for CozystackResourceDefinitions (#1427)
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 introduces shared cache for CozystackResourceDefinitions and
warbs it up before making decidion on restart cozystack-api server.

Reastart logic was also updated to trigger restart only if consistent
hash from the configuration has been changed.

### Release note

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

```release-note
[cozystack-controller] Implement cache for CozystackResourceDefinitions
```

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

## Summary by CodeRabbit

- New Features
- Smarter, hash-based restarts for the API component, triggered only
when configuration truly changes.
- Debounced restart behavior to avoid rapid, repeated restarts during
bursts of updates.

- Performance
- Introduces an internal in-memory configuration cache to speed up
evaluations and reduce API calls.
  - Cache is primed at startup for faster, more responsive operations.

- Bug Fixes
- Eliminates unnecessary restarts when there are no effective config
changes, improving stability and reducing disruption.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 13:40:07 +02:00
Andrei Kvapil
cdf53e89e9 Replace Ancestor tracking webhook with controller
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-09-17 12:55:43 +02:00
Andrei Kvapil
37720b9609 Revert "[cozystack-controller] Ancestor tracking webhook" (#1425)
Reverts cozystack/cozystack#1400

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

## Summary by CodeRabbit

* New Features
  * None
* Refactor
* Removed the lineage mutating admission webhook and its controller
logic; objects are no longer auto-labeled/mutated.
* Deployment now targets the cozy-system namespace and no longer exposes
a webhook port or mounts webhook certs.
* Chores
* Removed Service and cert-manager resources previously used for webhook
TLS; cert-manager is no longer required.
* Tests
  * Removed lineage-related tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-17 12:49:30 +02:00
Andrei Kvapil
ce522284c4 Revert "[cozystack-controller] Ancestor tracking webhook" 2025-09-17 12:46:00 +02:00
Marian Koreniuk
16a700dabf Fix bug_report.md
Signed-off-by: Marian Koreniuk <moriarti@cp.if.ua>
2025-09-11 16:26:20 +02:00
Marian Koreniuk
7f8b673dbc Update bug_report.md
Signed-off-by: Marian Koreniuk <moriarti@cp.if.ua>
2025-09-10 22:01:37 +02:00
Marian Koreniuk
24482d958b Update issue templates 2025-09-10 21:55:24 +02:00
Timur Tukaev
14aba9edb2 Create CONTRIBUTOR_LADDER.md
Contributor ladder is an important tool for community participants who are loyal to project and would like to take more responsibility in project. Besides, it's needed for CNCF Incubated  applications

Signed-off-by: Timur Tukaev <90071493+tym83@users.noreply.github.com>
2025-07-20 15:56:25 +05:00
526 changed files with 17437 additions and 26779 deletions

50
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,50 @@
---
name: Bug report
about: Create a report to help us improve
labels: 'bug'
assignees: ''
---
<!--
Thank you for submitting a bug report!
Please fill in the fields below to help us investigate the problem.
-->
**Describe the bug**
A clear and concise description of what the bug is.
**Environment**
- Cozystack version
- Provider: on-prem, Hetzner, and so on
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behaviour**
When taking the steps to reproduce, what should have happened differently?
**Actual behaviour**
A clear and concise description of what happens when the bug occurs. Explain how the system currently behaves, including error messages, unexpected results, or incorrect functionality observed during execution.
**Logs**
```
Paste any relevant logs here. Please redact tokens, passwords, private keys.
```
**Screenshots**
If applicable, add screenshots to help explain the problem.
**Additional context**
Add any other context about the problem here.
**Checklist**
- [ ] I have checked the documentation
- [ ] I have searched for similar issues
- [ ] I have included all required information
- [ ] I have provided clear steps to reproduce
- [ ] I have included relevant logs

View File

@@ -28,7 +28,7 @@ jobs:
- name: Install generate
run: |
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v0.8.5/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v0.9.0/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
- name: Run pre-commit hooks
run: |

View File

@@ -1,18 +1,11 @@
repos:
- repo: local
hooks:
- id: gen-versions-map
name: Generate versions map and check for changes
entry: sh -c 'make -C packages/apps check-version-map && make -C packages/extra check-version-map'
language: system
types: [file]
pass_filenames: false
description: Run the script and fail if it generates changes
- id: run-make-generate
name: Run 'make generate' in all app directories
entry: |
flock -x .git/pre-commit.lock sh -c '
for dir in ./packages/apps/*/ ./packages/extra/*/ ./packages/system/cozystack-api/; do
for dir in ./packages/apps/*/ ./packages/extra/*/; do
if [ -d "$dir" ]; then
echo "Running make generate in $dir"
make generate -C "$dir" || exit $?

View File

@@ -30,3 +30,5 @@ This list is sorted in chronological order, based on the submission date.
| [Bootstack](https://bootstack.app/) | @mrkhachaturov | 2024-08-01| At Bootstack, we utilize a Kubernetes operator specifically designed to simplify and streamline cloud infrastructure creation.|
| [gohost](https://gohost.kz/) | @karabass_off | 2024-02-01 | Our company has been working in the market of Kazakhstan for more than 15 years, providing clients with a standard set of services: VPS/VDC, IaaS, shared hosting, etc. Now we are expanding the lineup by introducing Bare Metal Kubenetes cluster under Cozystack management. |
| [Urmanac](https://urmanac.com) | @kingdonb | 2024-12-04 | Urmanac is the future home of a hosting platform for the knowledge base of a community of personal server enthusiasts. We use Cozystack to provide support services for web sites hosted using both conventional deployments and on SpinKube, with WASM. |
| [Hidora](https://hikube.cloud) | @matthieu-robin | 2025-09-17 | Hidora is a Swiss cloud provider delivering managed services and infrastructure solutions through datacenters located in Switzerland, ensuring data sovereignty and reliability. Its sovereign cloud platform, Hikube, is designed to run workloads with high availability across multiple datacenters, providing enterprises with a secure and scalable foundation for their applications based on Cozystack. |
|

View File

@@ -1,3 +1,22 @@
# Code of Conduct
Cozystack follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
# Cozystack Vendor Neutrality Manifesto
Cozystack exists for the cloud-native community. We are committed to a project culture where no single company, product, or commercial agenda directs our roadmap, governance, brand, or releases. Our North Star is user value, technical excellence, and open collaboration under the CNCF umbrella.
## Our Commitments
- **Community-first:** Decisions prioritize the broader community over any vendor interest.
- **Open collaboration:** Ideas, discussions, and outcomes happen in public spaces; contributions are welcomed from all.
- **Merit over affiliation:** Proposals are evaluated on technical merit and user impact, not on who submits them.
- **Inclusive stewardship:** Leadership and maintenance are open to contributors who demonstrate sustained, constructive impact.
- **Technology choice:** We prefer open, pluggable designs that interoperate with multiple ecosystems and providers.
- **Neutral brand & voice:** Our name, logo, website, and documentation do not imply endorsement or preference for any vendor.
- **Transparent practices:** Funding acknowledgments, partnerships, and potential conflicts are communicated openly.
- **User trust:** Security handling, releases, and communications aim to be timely, transparent, and fair to all users.
By contributing to Cozystack, we affirm these principles and work together to keep the project open, welcoming, and vendor-neutral.
*— The Cozystack community*

151
CONTRIBUTOR_LADDER.md Normal file
View File

@@ -0,0 +1,151 @@
# Contributor Ladder
* [Contributor Ladder](#contributor-ladder)
* [Community Participant](#community-participant)
* [Contributor](#contributor)
* [Reviewer](#reviewer)
* [Maintainer](#maintainer)
* [Inactivity](#inactivity)
* [Involuntary Removal](#involuntary-removal-or-demotion)
* [Stepping Down/Emeritus Process](#stepping-downemeritus-process)
* [Contact](#contact)
## Contributor Ladder
Hello! We are excited that you want to learn more about our project contributor ladder! This contributor ladder outlines the different contributor roles within the project, along with the responsibilities and privileges that come with them. Community members generally start at the first levels of the "ladder" and advance up it as their involvement in the project grows. Our project members are happy to help you advance along the contributor ladder.
Each of the contributor roles below is organized into lists of three types of things. "Responsibilities" are things that a contributor is expected to do. "Requirements" are qualifications a person needs to meet to be in that role, and "Privileges" are things contributors on that level are entitled to.
### Community Participant
Description: A Community Participant engages with the project and its community, contributing their time, thoughts, etc. Community participants are usually users who have stopped being anonymous and started being active in project discussions.
* Responsibilities:
* Must follow the [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
* How users can get involved with the community:
* Participating in community discussions
* Helping other users
* Submitting bug reports
* Commenting on issues
* Trying out new releases
* Attending community events
### Contributor
Description: A Contributor contributes directly to the project and adds value to it. Contributions need not be code. People at the Contributor level may be new contributors, or they may only contribute occasionally.
* Responsibilities include:
* Follow the [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
* Follow the project [contributing guide] (https://github.com/cozystack/cozystack/blob/main/CONTRIBUTING.md)
* Requirements (one or several of the below):
* Report and sometimes resolve issues
* Occasionally submit PRs
* Contribute to the documentation
* Show up at meetings, takes notes
* Answer questions from other community members
* Submit feedback on issues and PRs
* Test releases and patches and submit reviews
* Run or helps run events
* Promote the project in public
* Help run the project infrastructure
* Privileges:
* Invitations to contributor events
* Eligible to become a Maintainer
### Reviewer
Description: A Reviewer has responsibility for specific code, documentation, test, or other project areas. They are collectively responsible, with other Reviewers, for reviewing all changes to those areas and indicating whether those changes are ready to merge. They have a track record of contribution and review in the project.
Reviewers are responsible for a "specific area." This can be a specific code directory, driver, chapter of the docs, test job, event, or other clearly-defined project component that is smaller than an entire repository or subproject. Most often it is one or a set of directories in one or more Git repositories. The "specific area" below refers to this area of responsibility.
Reviewers have all the rights and responsibilities of a Contributor, plus:
* Responsibilities include:
* Continues to contribute regularly, as demonstrated by having at least 15 PRs a year, as demonstrated by [Cozystack devstats](https://cozystack.devstats.cncf.io).
* Following the reviewing guide
* Reviewing most Pull Requests against their specific areas of responsibility
* Reviewing at least 40 PRs per year
* Helping other contributors become reviewers
* Requirements:
* Must have successful contributions to the project, including at least one of the following:
* 10 accepted PRs,
* Reviewed 20 PRs,
* Resolved and closed 20 Issues,
* Become responsible for a key project management area,
* Or some equivalent combination or contribution
* Must have been contributing for at least 6 months
* Must be actively contributing to at least one project area
* Must have two sponsors who are also Reviewers or Maintainers, at least one of whom does not work for the same employer
* Has reviewed, or helped review, at least 20 Pull Requests
* Has analyzed and resolved test failures in their specific area
* Has demonstrated an in-depth knowledge of the specific area
* Commits to being responsible for that specific area
* Is supportive of new and occasional contributors and helps get useful PRs in shape to commit
* Additional privileges:
* Has GitHub or CI/CD rights to approve pull requests in specific directories
* Can recommend and review other contributors to become Reviewers
* May be assigned Issues and Reviews
* May give commands to CI/CD automation
* Can recommend other contributors to become Reviewers
The process of becoming a Reviewer is:
1. The contributor is nominated by opening a PR against the appropriate repository, which adds their GitHub username to the OWNERS file for one or more directories.
2. At least two members of the team that owns that repository or main directory, who are already Approvers, approve the PR.
### Maintainer
Description: Maintainers are very established contributors who are responsible for the entire project. As such, they have the ability to approve PRs against any area of the project, and are expected to participate in making decisions about the strategy and priorities of the project.
A Maintainer must meet the responsibilities and requirements of a Reviewer, plus:
* Responsibilities include:
* Reviewing at least 40 PRs per year, especially PRs that involve multiple parts of the project
* Mentoring new Reviewers
* Writing refactoring PRs
* Participating in CNCF maintainer activities
* Determining strategy and policy for the project
* Participating in, and leading, community meetings
* Requirements
* Experience as a Reviewer for at least 6 months
* Demonstrates a broad knowledge of the project across multiple areas
* Is able to exercise judgment for the good of the project, independent of their employer, friends, or team
* Mentors other contributors
* Can commit to spending at least 10 hours per month working on the project
* Additional privileges:
* Approve PRs to any area of the project
* Represent the project in public as a Maintainer
* Communicate with the CNCF on behalf of the project
* Have a vote in Maintainer decision-making meetings
Process of becoming a maintainer:
1. Any current Maintainer may nominate a current Reviewer to become a new Maintainer, by opening a PR against the root of the cozystack repository adding the nominee as an Approver in the [MAINTAINERS](https://github.com/cozystack/cozystack/blob/main/MAINTAINERS.md) file.
2. The nominee will add a comment to the PR testifying that they agree to all requirements of becoming a Maintainer.
3. A majority of the current Maintainers must then approve the PR.
## Inactivity
It is important for contributors to be and stay active to set an example and show commitment to the project. Inactivity is harmful to the project as it may lead to unexpected delays, contributor attrition, and a lost of trust in the project.
* Inactivity is measured by:
* Periods of no contributions for longer than 6 months
* Periods of no communication for longer than 3 months
* Consequences of being inactive include:
* Involuntary removal or demotion
* Being asked to move to Emeritus status
## Involuntary Removal or Demotion
Involuntary removal/demotion of a contributor happens when responsibilities and requirements aren't being met. This may include repeated patterns of inactivity, extended period of inactivity, a period of failing to meet the requirements of your role, and/or a violation of the Code of Conduct. This process is important because it protects the community and its deliverables while also opens up opportunities for new contributors to step in.
Involuntary removal or demotion is handled through a vote by a majority of the current Maintainers.
## Stepping Down/Emeritus Process
If and when contributors' commitment levels change, contributors can consider stepping down (moving down the contributor ladder) vs moving to emeritus status (completely stepping away from the project).
Contact the Maintainers about changing to Emeritus status, or reducing your contributor level.
## Contact
* For inquiries, please reach out to: @kvaps, @tym83

View File

@@ -7,6 +7,6 @@
| Kingdon Barrett | [@kingdonb](https://github.com/kingdonb) | Urmanac | FluxCD and flux-operator |
| Timofei Larkin | [@lllamnyp](https://github.com/lllamnyp) | 3commas | Etcd-operator Lead |
| Artem Bortnikov | [@aobort](https://github.com/aobort) | Timescale | Etcd-operator Lead |
| Andrei Gumilev | [@chumkaska](https://github.com/chumkaska) | Ænix | Platform Documentation |
| Timur Tukaev | [@tym83](https://github.com/tym83) | Ænix | Cozystack Website, Marketing, Community Management |
| Kirill Klinchenkov | [@klinch0](https://github.com/klinch0) | Ænix | Core Maintainer |
| Nikita Bykov | [@nbykov0](https://github.com/nbykov0) | Ænix | Maintainer of ARM and stuff |

View File

@@ -30,14 +30,9 @@ build: build-deps
repos:
rm -rf _out
make -C packages/apps check-version-map
make -C packages/extra check-version-map
make -C packages/system repo
make -C packages/apps repo
make -C packages/extra repo
mkdir -p _out/logos
cp ./packages/apps/*/logos/*.svg ./packages/extra/*/logos/*.svg _out/logos/
manifests:
mkdir -p _out/assets

View File

@@ -19,7 +19,7 @@ Database-as-a-Service, virtual machines, load balancers, HTTP caching services,
Use Cozystack to build your own cloud or provide a cost-effective development environment.
![Cozystack user interface](https://cozystack.io/img/screenshot.png)
![Cozystack user interface](https://cozystack.io/img/screenshot-dark.png)
## Use-Cases

View File

@@ -1,4 +1,5 @@
API rule violation: list_type_missing,github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1,ApplicationStatus,Conditions
API rule violation: list_type_missing,github.com/cozystack/cozystack/pkg/apis/core/v1alpha1,TenantModuleStatus,Conditions
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource

View File

@@ -0,0 +1,255 @@
// SPDX-License-Identifier: Apache-2.0
// Package v1alpha1 defines front.in-cloud.io API types.
//
// Group: dashboard.cozystack.io
// Version: v1alpha1
package v1alpha1
import (
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// -----------------------------------------------------------------------------
// Shared shapes
// -----------------------------------------------------------------------------
// CommonStatus is a generic Status block with Kubernetes conditions.
type CommonStatus struct {
// ObservedGeneration reflects the most recent generation observed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions represent the latest available observations of an object's state.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// ArbitrarySpec holds schemaless user data and preserves unknown fields.
// We map the entire .spec to a single JSON payload to mirror the CRDs you provided.
// NOTE: Using apiextensionsv1.JSON avoids losing arbitrary structure during round-trips.
type ArbitrarySpec struct {
// +kubebuilder:validation:XPreserveUnknownFields
// +kubebuilder:pruning:PreserveUnknownFields
v1.JSON `json:",inline"`
}
// -----------------------------------------------------------------------------
// Sidebar
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=sidebars,scope=Cluster
// +kubebuilder:subresource:status
type Sidebar struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type SidebarList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Sidebar `json:"items"`
}
// -----------------------------------------------------------------------------
// CustomFormsPrefill (shortName: cfp)
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=customformsprefills,scope=Cluster,shortName=cfp
// +kubebuilder:subresource:status
type CustomFormsPrefill struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type CustomFormsPrefillList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CustomFormsPrefill `json:"items"`
}
// -----------------------------------------------------------------------------
// BreadcrumbInside
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=breadcrumbsinside,scope=Cluster
// +kubebuilder:subresource:status
type BreadcrumbInside struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type BreadcrumbInsideList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BreadcrumbInside `json:"items"`
}
// -----------------------------------------------------------------------------
// CustomFormsOverride (shortName: cfo)
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=customformsoverrides,scope=Cluster,shortName=cfo
// +kubebuilder:subresource:status
type CustomFormsOverride struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type CustomFormsOverrideList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CustomFormsOverride `json:"items"`
}
// -----------------------------------------------------------------------------
// TableUriMapping
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=tableurimappings,scope=Cluster
// +kubebuilder:subresource:status
type TableUriMapping struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type TableUriMappingList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []TableUriMapping `json:"items"`
}
// -----------------------------------------------------------------------------
// Breadcrumb
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=breadcrumbs,scope=Cluster
// +kubebuilder:subresource:status
type Breadcrumb struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type BreadcrumbList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Breadcrumb `json:"items"`
}
// -----------------------------------------------------------------------------
// MarketplacePanel
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=marketplacepanels,scope=Cluster
// +kubebuilder:subresource:status
type MarketplacePanel struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type MarketplacePanelList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MarketplacePanel `json:"items"`
}
// -----------------------------------------------------------------------------
// Navigation
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=navigations,scope=Cluster
// +kubebuilder:subresource:status
type Navigation struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type NavigationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Navigation `json:"items"`
}
// -----------------------------------------------------------------------------
// CustomColumnsOverride
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=customcolumnsoverrides,scope=Cluster
// +kubebuilder:subresource:status
type CustomColumnsOverride struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type CustomColumnsOverrideList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CustomColumnsOverride `json:"items"`
}
// -----------------------------------------------------------------------------
// Factory
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=factories,scope=Cluster
// +kubebuilder:subresource:status
type Factory struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type FactoryList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Factory `json:"items"`
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.
// +kubebuilder:object:generate=true
// +groupName=dashboard.cozystack.io
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "dashboard.cozystack.io", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
GroupVersion,
&Sidebar{},
&SidebarList{},
&CustomFormsPrefill{},
&CustomFormsPrefillList{},
&BreadcrumbInside{},
&BreadcrumbInsideList{},
&CustomFormsOverride{},
&CustomFormsOverrideList{},
&TableUriMapping{},
&TableUriMappingList{},
&Breadcrumb{},
&BreadcrumbList{},
&MarketplacePanel{},
&MarketplacePanelList{},
&Navigation{},
&NavigationList{},
&CustomColumnsOverride{},
&CustomColumnsOverrideList{},
&Factory{},
&FactoryList{},
)
metav1.AddToGroupVersion(scheme, GroupVersion)
return nil
}

View File

@@ -0,0 +1,654 @@
//go:build !ignore_autogenerated
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArbitrarySpec) DeepCopyInto(out *ArbitrarySpec) {
*out = *in
in.JSON.DeepCopyInto(&out.JSON)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArbitrarySpec.
func (in *ArbitrarySpec) DeepCopy() *ArbitrarySpec {
if in == nil {
return nil
}
out := new(ArbitrarySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Breadcrumb) DeepCopyInto(out *Breadcrumb) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Breadcrumb.
func (in *Breadcrumb) DeepCopy() *Breadcrumb {
if in == nil {
return nil
}
out := new(Breadcrumb)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Breadcrumb) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BreadcrumbInside) DeepCopyInto(out *BreadcrumbInside) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BreadcrumbInside.
func (in *BreadcrumbInside) DeepCopy() *BreadcrumbInside {
if in == nil {
return nil
}
out := new(BreadcrumbInside)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BreadcrumbInside) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BreadcrumbInsideList) DeepCopyInto(out *BreadcrumbInsideList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]BreadcrumbInside, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BreadcrumbInsideList.
func (in *BreadcrumbInsideList) DeepCopy() *BreadcrumbInsideList {
if in == nil {
return nil
}
out := new(BreadcrumbInsideList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BreadcrumbInsideList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BreadcrumbList) DeepCopyInto(out *BreadcrumbList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Breadcrumb, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BreadcrumbList.
func (in *BreadcrumbList) DeepCopy() *BreadcrumbList {
if in == nil {
return nil
}
out := new(BreadcrumbList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BreadcrumbList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonStatus) DeepCopyInto(out *CommonStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonStatus.
func (in *CommonStatus) DeepCopy() *CommonStatus {
if in == nil {
return nil
}
out := new(CommonStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomColumnsOverride) DeepCopyInto(out *CustomColumnsOverride) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomColumnsOverride.
func (in *CustomColumnsOverride) DeepCopy() *CustomColumnsOverride {
if in == nil {
return nil
}
out := new(CustomColumnsOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomColumnsOverride) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomColumnsOverrideList) DeepCopyInto(out *CustomColumnsOverrideList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CustomColumnsOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomColumnsOverrideList.
func (in *CustomColumnsOverrideList) DeepCopy() *CustomColumnsOverrideList {
if in == nil {
return nil
}
out := new(CustomColumnsOverrideList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomColumnsOverrideList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomFormsOverride) DeepCopyInto(out *CustomFormsOverride) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomFormsOverride.
func (in *CustomFormsOverride) DeepCopy() *CustomFormsOverride {
if in == nil {
return nil
}
out := new(CustomFormsOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomFormsOverride) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomFormsOverrideList) DeepCopyInto(out *CustomFormsOverrideList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CustomFormsOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomFormsOverrideList.
func (in *CustomFormsOverrideList) DeepCopy() *CustomFormsOverrideList {
if in == nil {
return nil
}
out := new(CustomFormsOverrideList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomFormsOverrideList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomFormsPrefill) DeepCopyInto(out *CustomFormsPrefill) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomFormsPrefill.
func (in *CustomFormsPrefill) DeepCopy() *CustomFormsPrefill {
if in == nil {
return nil
}
out := new(CustomFormsPrefill)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomFormsPrefill) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomFormsPrefillList) DeepCopyInto(out *CustomFormsPrefillList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CustomFormsPrefill, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomFormsPrefillList.
func (in *CustomFormsPrefillList) DeepCopy() *CustomFormsPrefillList {
if in == nil {
return nil
}
out := new(CustomFormsPrefillList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CustomFormsPrefillList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Factory) DeepCopyInto(out *Factory) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Factory.
func (in *Factory) DeepCopy() *Factory {
if in == nil {
return nil
}
out := new(Factory)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Factory) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FactoryList) DeepCopyInto(out *FactoryList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Factory, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FactoryList.
func (in *FactoryList) DeepCopy() *FactoryList {
if in == nil {
return nil
}
out := new(FactoryList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FactoryList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MarketplacePanel) DeepCopyInto(out *MarketplacePanel) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MarketplacePanel.
func (in *MarketplacePanel) DeepCopy() *MarketplacePanel {
if in == nil {
return nil
}
out := new(MarketplacePanel)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MarketplacePanel) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MarketplacePanelList) DeepCopyInto(out *MarketplacePanelList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]MarketplacePanel, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MarketplacePanelList.
func (in *MarketplacePanelList) DeepCopy() *MarketplacePanelList {
if in == nil {
return nil
}
out := new(MarketplacePanelList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MarketplacePanelList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Navigation) DeepCopyInto(out *Navigation) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Navigation.
func (in *Navigation) DeepCopy() *Navigation {
if in == nil {
return nil
}
out := new(Navigation)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Navigation) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NavigationList) DeepCopyInto(out *NavigationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Navigation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NavigationList.
func (in *NavigationList) DeepCopy() *NavigationList {
if in == nil {
return nil
}
out := new(NavigationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NavigationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Sidebar) DeepCopyInto(out *Sidebar) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sidebar.
func (in *Sidebar) DeepCopy() *Sidebar {
if in == nil {
return nil
}
out := new(Sidebar)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Sidebar) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SidebarList) DeepCopyInto(out *SidebarList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Sidebar, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidebarList.
func (in *SidebarList) DeepCopy() *SidebarList {
if in == nil {
return nil
}
out := new(SidebarList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SidebarList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TableUriMapping) DeepCopyInto(out *TableUriMapping) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableUriMapping.
func (in *TableUriMapping) DeepCopy() *TableUriMapping {
if in == nil {
return nil
}
out := new(TableUriMapping)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TableUriMapping) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TableUriMappingList) DeepCopyInto(out *TableUriMappingList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TableUriMapping, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableUriMappingList.
func (in *TableUriMappingList) DeepCopy() *TableUriMappingList {
if in == nil {
return nil
}
out := new(TableUriMappingList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TableUriMappingList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@@ -21,6 +21,7 @@ import (
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// CozystackResourceDefinition is the Schema for the cozystackresourcedefinitions API
type CozystackResourceDefinition struct {
@@ -32,7 +33,7 @@ type CozystackResourceDefinition struct {
// +kubebuilder:object:root=true
// CozystackResourceDefinitionList contains a list of CozystackResourceDefinition
// CozystackResourceDefinitionList contains a list of CozystackResourceDefinitions
type CozystackResourceDefinitionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
@@ -48,6 +49,16 @@ type CozystackResourceDefinitionSpec struct {
Application CozystackResourceDefinitionApplication `json:"application"`
// Release configuration
Release CozystackResourceDefinitionRelease `json:"release"`
// Secret selectors
Secrets CozystackResourceDefinitionResources `json:"secrets,omitempty"`
// Service selectors
Services CozystackResourceDefinitionResources `json:"services,omitempty"`
// Ingress selectors
Ingresses CozystackResourceDefinitionResources `json:"ingresses,omitempty"`
// Dashboard configuration for this resource
Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"`
}
type CozystackResourceDefinitionChart struct {
@@ -87,3 +98,96 @@ type CozystackResourceDefinitionRelease struct {
// Prefix for the release name
Prefix string `json:"prefix"`
}
// CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.
// A resource matches this selector only if it satisfies ALL criteria:
// - Label selector conditions (matchExpressions and matchLabels)
// - AND has a name that matches one of the names in resourceNames (if specified)
//
// The resourceNames field supports Go templates with the following variables available:
// - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name)
// - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind)
// - {{ .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"
type CozystackResourceDefinitionResourceSelector struct {
metav1.LabelSelector `json:",inline"`
// ResourceNames is a list of resource names to match
// If specified, the resource must have one of these exact names to match the selector
// +optional
ResourceNames []string `json:"resourceNames,omitempty"`
}
type CozystackResourceDefinitionResources struct {
// Exclude contains an array of resource selectors that target resources.
// If a resource matches the selector in any of the elements in the array, it is
// hidden from the user, regardless of the matches in the include array.
Exclude []*CozystackResourceDefinitionResourceSelector `json:"exclude,omitempty"`
// Include contains an array of resource selectors that target resources.
// If a resource matches the selector in any of the elements in the array, and
// matches none of the selectors in the exclude array that resource is marked
// as a tenant resource and is visible to users.
Include []*CozystackResourceDefinitionResourceSelector `json:"include,omitempty"`
}
// ---- Dashboard types ----
// DashboardTab enumerates allowed UI tabs.
// +kubebuilder:validation:Enum=workloads;ingresses;services;secrets;yaml
type DashboardTab string
const (
DashboardTabWorkloads DashboardTab = "workloads"
DashboardTabIngresses DashboardTab = "ingresses"
DashboardTabServices DashboardTab = "services"
DashboardTabSecrets DashboardTab = "secrets"
DashboardTabYAML DashboardTab = "yaml"
)
// CozystackResourceDefinitionDashboard describes how this resource appears in the UI.
type CozystackResourceDefinitionDashboard struct {
// Human-readable name shown in the UI (e.g., "Bucket")
Singular string `json:"singular"`
// Plural human-readable name (e.g., "Buckets")
Plural string `json:"plural"`
// Hard-coded name used in the UI (e.g., "bucket")
// +optional
Name string `json:"name,omitempty"`
// Whether this resource is singular (not a collection) in the UI
// +optional
SingularResource bool `json:"singularResource,omitempty"`
// Order weight for sorting resources in the UI (lower first)
// +optional
Weight int `json:"weight,omitempty"`
// Short description shown in catalogs or headers (e.g., "S3 compatible storage")
// +optional
Description string `json:"description,omitempty"`
// Icon encoded as a string (e.g., inline SVG, base64, or data URI)
// +optional
Icon string `json:"icon,omitempty"`
// Category used to group resources in the UI (e.g., "Storage", "Networking")
Category string `json:"category"`
// Free-form tags for search and filtering
// +optional
Tags []string `json:"tags,omitempty"`
// Which tabs to show for this resource
// +optional
Tabs []DashboardTab `json:"tabs,omitempty"`
// Order of keys in the YAML view
// +optional
KeysOrder [][]string `json:"keysOrder,omitempty"`
// Whether this resource is a module (tenant module)
// +optional
Module bool `json:"module,omitempty"`
}

View File

@@ -82,6 +82,42 @@ func (in *CozystackResourceDefinitionChart) DeepCopy() *CozystackResourceDefinit
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionDashboard) DeepCopyInto(out *CozystackResourceDefinitionDashboard) {
*out = *in
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Tabs != nil {
in, out := &in.Tabs, &out.Tabs
*out = make([]DashboardTab, len(*in))
copy(*out, *in)
}
if in.KeysOrder != nil {
in, out := &in.KeysOrder, &out.KeysOrder
*out = make([][]string, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = make([]string, len(*in))
copy(*out, *in)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionDashboard.
func (in *CozystackResourceDefinitionDashboard) DeepCopy() *CozystackResourceDefinitionDashboard {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionDashboard)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionList) DeepCopyInto(out *CozystackResourceDefinitionList) {
*out = *in
@@ -137,11 +173,77 @@ func (in *CozystackResourceDefinitionRelease) DeepCopy() *CozystackResourceDefin
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionResourceSelector) DeepCopyInto(out *CozystackResourceDefinitionResourceSelector) {
*out = *in
in.LabelSelector.DeepCopyInto(&out.LabelSelector)
if in.ResourceNames != nil {
in, out := &in.ResourceNames, &out.ResourceNames
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResourceSelector.
func (in *CozystackResourceDefinitionResourceSelector) DeepCopy() *CozystackResourceDefinitionResourceSelector {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionResourceSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionResources) DeepCopyInto(out *CozystackResourceDefinitionResources) {
*out = *in
if in.Exclude != nil {
in, out := &in.Exclude, &out.Exclude
*out = make([]*CozystackResourceDefinitionResourceSelector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(CozystackResourceDefinitionResourceSelector)
(*in).DeepCopyInto(*out)
}
}
}
if in.Include != nil {
in, out := &in.Include, &out.Include
*out = make([]*CozystackResourceDefinitionResourceSelector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(CozystackResourceDefinitionResourceSelector)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResources.
func (in *CozystackResourceDefinitionResources) DeepCopy() *CozystackResourceDefinitionResources {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionResources)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDefinitionSpec) {
*out = *in
out.Application = in.Application
in.Release.DeepCopyInto(&out.Release)
in.Secrets.DeepCopyInto(&out.Secrets)
in.Services.DeepCopyInto(&out.Services)
in.Ingresses.DeepCopyInto(&out.Ingresses)
if in.Dashboard != nil {
in, out := &in.Dashboard, &out.Dashboard
*out = new(CozystackResourceDefinitionDashboard)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec.

View File

@@ -26,8 +26,8 @@ import (
func main() {
ctx := genericapiserver.SetupSignalContext()
options := server.NewAppsServerOptions(os.Stdout, os.Stderr)
cmd := server.NewCommandStartAppsServer(ctx, options)
options := server.NewCozyServerOptions(os.Stdout, os.Stderr)
cmd := server.NewCommandStartCozyServer(ctx, options)
code := cli.Run(cmd)
os.Exit(code)
}

View File

@@ -38,7 +38,8 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/lineagelabeler"
"github.com/cozystack/cozystack/internal/controller/dashboard"
lcw "github.com/cozystack/cozystack/internal/lineagecontrollerwebhook"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
@@ -54,6 +55,7 @@ func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(cozystackiov1alpha1.AddToScheme(scheme))
utilruntime.Must(dashboard.AddToScheme(scheme))
utilruntime.Must(helmv2.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
@@ -68,7 +70,6 @@ func main() {
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var watchResources string
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -88,9 +89,6 @@ func main() {
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.StringVar(&cozystackVersion, "cozystack-version", "unknown",
"Version of Cozystack")
flag.StringVar(&watchResources, "watch-resources",
"v1/Pod,v1/Service,v1/Secret,v1/PersistentVolumeClaim",
"Comma-separated list of resources to watch in the form 'group/version/Kind'.")
opts := zap.Options{
Development: false,
}
@@ -155,7 +153,12 @@ func main() {
// this setup is not recommended for production.
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
// Configure rate limiting for the Kubernetes client
config := ctrl.GetConfigOrDie()
config.QPS = 50.0 // Increased from default 5.0
config.Burst = 100 // Increased from default 10
mgr, err := ctrl.NewManager(config, ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
@@ -219,12 +222,17 @@ func main() {
os.Exit(1)
}
if err := (&lineagelabeler.LineageLabelerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
WatchResourceCSV: watchResources,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "LineageLabeler")
// special one that's both a webhook and a reconciler
lineageControllerWebhook := &lcw.LineageControllerWebhook{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err := lineageControllerWebhook.SetupWithManagerAsController(mgr); err != nil {
setupLog.Error(err, "unable to setup controller", "controller", "LineageController")
os.Exit(1)
}
if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil {
setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook")
os.Exit(1)
}

View File

@@ -79,22 +79,73 @@ EOF
# Wait for the machine deployment to scale to 2 replicas (timeout after 1 minute)
kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=1m --for=jsonpath='{.status.replicas}'=2
# Get the admin kubeconfig and save it to a file
kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig
# Update the kubeconfig to use localhost for the API server
yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig
# Set up port forwarding to the Kubernetes API server for a 40 second timeout
bash -c 'timeout 40s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
# 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 &'
# 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 '
until [ "$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
sleep 3
done
'
# Verify the nodes are ready
kubectl --kubeconfig tenantkubeconfig wait node --all --timeout=2m --for=condition=Ready
kubectl --kubeconfig tenantkubeconfig get nodes -o wide
# Verify the kubelet version matches what we expect
versions=$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
node_ok=true
if [[ "$k8s_version" == v1.32* ]]; then
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
fi
for v in $versions; do
case "$k8s_version" in
v1.32|v1.32.*)
case "$v" in
v1.32 | v1.32.* | v1.32-* | v1.33 | v1.33.* | v1.33-*)
;;
*)
node_ok=false
break
;;
esac
;;
*)
case "$v" in
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
;;
*)
node_ok=false
break
;;
esac
;;
esac
done
if ! $node_ok; then
echo "Kubelet versions did not match expected ${k8s_version}" >&2
exit 1
fi
# Wait for all machine deployment replicas to be ready (timeout after 10 minutes)
kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2
for component in cilium coredns csi ingress-nginx vsnap-crd; do
kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=1m --for=condition=ready
done
# Clean up by deleting the Kubernetes resource
kubectl -n tenant-test delete kuberneteses.apps.cozystack.io $test_name

View File

@@ -1,64 +0,0 @@
#!/bin/sh
set -e
file=versions_map
charts=$(find . -mindepth 2 -maxdepth 2 -name Chart.yaml | awk 'sub("/Chart.yaml", "")')
new_map=$(
for chart in $charts; do
awk '/^name:/ {chart=$2} /^version:/ {version=$2} END{printf "%s %s %s\n", chart, version, "HEAD"}' "$chart/Chart.yaml"
done
)
if [ ! -f "$file" ] || [ ! -s "$file" ]; then
echo "$new_map" > "$file"
exit 0
fi
miss_map=$(mktemp)
trap 'rm -f "$miss_map"' EXIT
echo -n "$new_map" | awk 'NR==FNR { nm[$1 " " $2] = $3; next } { if (!($1 " " $2 in nm)) print $1, $2, $3}' - "$file" > $miss_map
# search accross all tags sorted by version
search_commits=$(git ls-remote --tags origin | awk -F/ '$3 ~ /v[0-9]+.[0-9]+.[0-9]+/ {print}' | sort -k2,2 -rV | awk '{print $1}')
resolved_miss_map=$(
while read -r chart version commit; do
# if version is found in HEAD, it's HEAD
if [ "$(awk '$1 == "version:" {print $2}' ./${chart}/Chart.yaml)" = "${version}" ]; then
echo "$chart $version HEAD"
continue
fi
# if commit is not HEAD, check if it's valid
if [ "$commit" != "HEAD" ]; then
if [ "$(git show "${commit}:./${chart}/Chart.yaml" | awk '$1 == "version:" {print $2}')" != "${version}" ]; then
echo "Commit $commit for $chart $version is not valid" >&2
exit 1
fi
commit=$(git rev-parse --short "$commit")
echo "$chart $version $commit"
continue
fi
# if commit is HEAD, but version is not found in HEAD, check all tags
found_tag=""
for tag in $search_commits; do
if [ "$(git show "${tag}:./${chart}/Chart.yaml" | awk '$1 == "version:" {print $2}')" = "${version}" ]; then
found_tag=$(git rev-parse --short "${tag}")
break
fi
done
if [ -z "$found_tag" ]; then
echo "Can't find $chart $version in any version tag, removing it" >&2
continue
fi
echo "$chart $version $found_tag"
done < $miss_map
)
printf "%s\n" "$new_map" "$resolved_miss_map" | sort -k1,1 -k2,2 -V | awk '$1' > "$file"

View File

@@ -1,65 +0,0 @@
#!/bin/sh
set -e
usage() {
printf "%s\n" "Usage:" >&2 ;
printf -- "%s\n" '---' >&2 ;
printf "%s %s\n" "$0" "INPUT_DIR OUTPUT_DIR TMP_DIR [DEPENDENCY_DIR]" >&2 ;
printf -- "%s\n" '---' >&2 ;
printf "%s\n" "Takes a helm repository from INPUT_DIR, with an optional library repository in" >&2 ;
printf "%s\n" "DEPENDENCY_DIR, prepares a view of the git archive at select points in history" >&2 ;
printf "%s\n" "in TMP_DIR and packages helm charts, outputting the tarballs to OUTPUT_DIR" >&2 ;
}
if [ "x$(basename $PWD)" != "xpackages" ]
then
echo "Error: This script must run from the ./packages/ directory" >&2
echo >&2
usage
exit 1
fi
if [ "x$#" != "x3" ] && [ "x$#" != "x4" ]
then
echo "Error: This script takes 3 or 4 arguments" >&2
echo "Got $# arguments:" "$@" >&2
echo >&2
usage
exit 1
fi
input_dir=$1
output_dir=$2
tmp_dir=$3
if [ "x$#" = "x4" ]
then
dependency_dir=$4
fi
rm -rf "${output_dir:?}"
mkdir -p "${output_dir}"
while read package _ commit
do
# this lets devs build the packages from a dirty repo for quick local testing
if [ "x$commit" = "xHEAD" ]
then
helm package "${input_dir}/${package}" -d "${output_dir}"
continue
fi
git archive --format tar "${commit}" "${input_dir}/${package}" | tar -xf- -C "${tmp_dir}/"
# the library chart is not present in older commits and git archive doesn't fail gracefully if the path is not found
if [ "x${dependency_dir}" != "x" ] && git ls-tree --name-only "${commit}" "${dependency_dir}" | grep -qx "${dependency_dir}"
then
git archive --format tar "${commit}" "${dependency_dir}" | tar -xf- -C "${tmp_dir}/"
fi
helm package "${tmp_dir}/${input_dir}/${package}" -d "${output_dir}"
rm -rf "${tmp_dir:?}/${input_dir:?}/${package:?}"
if [ "x${dependency_dir}" != "x" ]
then
rm -rf "${tmp_dir:?}/${dependency_dir:?}"
fi
done < "${input_dir}/versions_map"
helm repo index "${output_dir}"

View File

@@ -53,4 +53,6 @@ kube::codegen::gen_openapi \
"${SCRIPT_ROOT}/pkg/apis"
$CONTROLLER_GEN object:headerFile="hack/boilerplate.go.txt" paths="./api/..."
$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/templates/crds
$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/crds
mv packages/system/cozystack-controller/crds/cozystack.io_cozystackresourcedefinitions.yaml \
packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml

139
hack/update-crd.sh Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env bash
set -euo pipefail
# Requirements: yq (v4), jq, base64
need() { command -v "$1" >/dev/null 2>&1 || { echo "need $1"; exit 1; }; }
need yq; need jq; need base64
CHART_YAML="${CHART_YAML:-Chart.yaml}"
VALUES_YAML="${VALUES_YAML:-values.yaml}"
SCHEMA_JSON="${SCHEMA_JSON:-values.schema.json}"
CRD_DIR="../../system/cozystack-resource-definitions/cozyrds"
[[ -f "$CHART_YAML" ]] || { echo "No $CHART_YAML found"; exit 1; }
[[ -f "$SCHEMA_JSON" ]] || { echo "No $SCHEMA_JSON found"; exit 1; }
# Read basics from Chart.yaml
NAME="$(yq -r '.name // ""' "$CHART_YAML")"
DESC="$(yq -r '.description // ""' "$CHART_YAML")"
ICON_PATH_RAW="$(yq -r '.icon // ""' "$CHART_YAML")"
if [[ -z "$NAME" ]]; then
echo "Chart.yaml: .name is empty"; exit 1
fi
# Resolve icon path
# Accepts:
# /logos/foo.svg -> ./logos/foo.svg
# logos/foo.svg -> logos/foo.svg
# ./logos/foo.svg -> ./logos/foo.svg
# Fallback: ./logos/${NAME}.svg
resolve_icon_path() {
local p="$1"
if [[ -z "$p" || "$p" == "null" ]]; then
echo "./logos/${NAME}.svg"; return
fi
if [[ "$p" == /* ]]; then
echo ".${p}"
else
echo "$p"
fi
}
ICON_PATH="$(resolve_icon_path "$ICON_PATH_RAW")"
if [[ ! -f "$ICON_PATH" ]]; then
# try fallback
ALT="./logos/${NAME}.svg"
if [[ -f "$ALT" ]]; then
ICON_PATH="$ALT"
else
echo "Icon not found: $ICON_PATH"; exit 1
fi
fi
# Base64 (portable: no -w / -b options)
ICON_B64="$(base64 < "$ICON_PATH" | tr -d '\n' | tr -d '\r')"
# Decide which HelmRepository name to use based on path
# .../apps/... -> cozystack-apps
# .../extra/... -> cozystack-extra
# default: cozystack-apps
SOURCE_NAME="cozystack-apps"
case "$PWD" in
*"/apps/"*) SOURCE_NAME="cozystack-apps" ;;
*"/extra/"*) SOURCE_NAME="cozystack-extra" ;;
esac
# If file doesn't exist, create a minimal skeleton
OUT="${OUT:-$CRD_DIR/$NAME.yaml}"
if [[ ! -f "$OUT" ]]; then
cat >"$OUT" <<EOF
apiVersion: cozystack.io/v1alpha1
kind: CozystackResourceDefinition
metadata:
name: ${NAME}
spec: {}
EOF
fi
# Export vars for yq env()
export RES_NAME="$NAME"
export PREFIX="$NAME-"
if [ "$SOURCE_NAME" == "cozystack-extra" ]; then
export PREFIX=""
fi
export DESCRIPTION="$DESC"
export ICON_B64="$ICON_B64"
export SOURCE_NAME="$SOURCE_NAME"
export SCHEMA_JSON_MIN="$(jq -c . "$SCHEMA_JSON")"
# Generate keysOrder from values.yaml
export KEYS_ORDER="$(
yq -o=json '.' "$VALUES_YAML" | jq -c '
def get_paths_recursive(obj; path):
obj | to_entries | map(
.key as $key |
.value as $value |
if $value | type == "object" then
[path + [$key]] + get_paths_recursive($value; path + [$key])
else
[path + [$key]]
end
) | flatten(1)
;
(
[ ["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata","name"] ]
)
+
(
get_paths_recursive(.; []) # get all paths in order
| map(select(length>0)) # drop root
| map(map(select(type != "number"))) # drop array indices
| map(["spec"] + .) # prepend "spec"
)
'
)"
# Update only necessary fields in-place
# - openAPISchema is loaded from file as a multi-line string (block scalar)
# - labels ensure cozystack.io/ui: "true"
# - prefix = "<name>-"
# - sourceRef derived from directory (apps|extra)
yq -i '
.apiVersion = (.apiVersion // "cozystack.io/v1alpha1") |
.kind = (.kind // "CozystackResourceDefinition") |
.metadata.name = strenv(RES_NAME) |
.spec.application.openAPISchema = strenv(SCHEMA_JSON_MIN) |
(.spec.application.openAPISchema style="literal") |
.spec.release.prefix = (strenv(PREFIX)) |
.spec.release.labels."cozystack.io/ui" = "true" |
.spec.release.chart.name = strenv(RES_NAME) |
.spec.release.chart.sourceRef.kind = "HelmRepository" |
.spec.release.chart.sourceRef.name = strenv(SOURCE_NAME) |
.spec.release.chart.sourceRef.namespace = "cozy-public" |
.spec.dashboard.description = strenv(DESCRIPTION) |
.spec.dashboard.icon = strenv(ICON_B64) |
.spec.dashboard.keysOrder = env(KEYS_ORDER)
' "$OUT"
echo "Updated $OUT"

View File

@@ -9,6 +9,7 @@ import (
"sync"
"time"
"github.com/cozystack/cozystack/internal/controller/dashboard"
"github.com/cozystack/cozystack/internal/shared/crdmem"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
@@ -22,8 +23,10 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -38,6 +41,10 @@ type CozystackResourceDefinitionReconciler struct {
lastHandled time.Time
mem *crdmem.Memory
// Track static resources initialization
staticResourcesInitialized bool
staticResourcesMutex sync.Mutex
}
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -50,15 +57,44 @@ func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, r
r.mem.Upsert(crd)
}
mgr := dashboard.NewManager(
r.Client,
r.Scheme,
dashboard.WithCRDListFunc(func(c context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if r.mem != nil {
return r.mem.ListFromCacheOrAPI(c, r.Client)
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := r.Client.List(c, &list); err != nil {
return nil, err
}
return list.Items, nil
}),
)
if res, derr := mgr.EnsureForCRD(ctx, crd); derr != nil || res.Requeue || res.RequeueAfter > 0 {
return res, derr
}
// After processing CRD, perform cleanup of orphaned resources
// This should be done after cache warming to ensure all current resources are known
if cleanupErr := mgr.CleanupOrphanedResources(ctx); cleanupErr != nil {
logger.Error(cleanupErr, "Failed to cleanup orphaned dashboard resources")
// Don't fail the reconciliation, just log the error
}
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return ctrl.Result{}, nil
}
if err != nil && !apierrors.IsNotFound(err) {
// Handle error cases (err is guaranteed to be non-nil here)
if !apierrors.IsNotFound(err) {
return ctrl.Result{}, err
}
if apierrors.IsNotFound(err) && r.mem != nil {
// If resource is not found, clean up from memory
if r.mem != nil {
r.mem.Delete(req.Name)
}
if req.Namespace == "cozy-system" && req.Name == "cozystack-api" {
@@ -67,6 +103,40 @@ func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, r
return ctrl.Result{}, nil
}
// initializeStaticResourcesOnce ensures static resources are created only once
func (r *CozystackResourceDefinitionReconciler) initializeStaticResourcesOnce(ctx context.Context) error {
r.staticResourcesMutex.Lock()
defer r.staticResourcesMutex.Unlock()
if r.staticResourcesInitialized {
return nil // Already initialized
}
// Create dashboard manager and initialize static resources
mgr := dashboard.NewManager(
r.Client,
r.Scheme,
dashboard.WithCRDListFunc(func(c context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
if r.mem != nil {
return r.mem.ListFromCacheOrAPI(c, r.Client)
}
var list cozyv1alpha1.CozystackResourceDefinitionList
if err := r.Client.List(c, &list); err != nil {
return nil, err
}
return list.Items, nil
}),
)
if err := mgr.InitializeStaticResources(ctx); err != nil {
return err
}
r.staticResourcesInitialized = true
log.FromContext(ctx).Info("Static dashboard resources initialized successfully")
return nil
}
func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
@@ -77,6 +147,18 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
// Initialize static resources once during controller startup using manager.Runnable
if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
if err := r.initializeStaticResourcesOnce(ctx); err != nil {
log.FromContext(ctx).Error(err, "Failed to initialize static resources")
return err
}
return nil
})); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
Named("cozystackresource-controller").
For(&cozyv1alpha1.CozystackResourceDefinition{}, builder.WithPredicates()).
@@ -94,6 +176,9 @@ func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manage
}}
}),
).
WithOptions(controller.Options{
MaxConcurrentReconciles: 5, // Allow more concurrent reconciles with proper rate limiting
}).
Complete(r)
}
@@ -102,17 +187,23 @@ type crdHashView struct {
Spec cozyv1alpha1.CozystackResourceDefinitionSpec `json:"spec"`
}
func (r *CozystackResourceDefinitionReconciler) computeConfigHash() (string, error) {
if r.mem == nil {
return "", nil
func (r *CozystackResourceDefinitionReconciler) computeConfigHash(ctx context.Context) (string, error) {
var items []cozyv1alpha1.CozystackResourceDefinition
if r.mem != nil {
list, err := r.mem.ListFromCacheOrAPI(ctx, r.Client)
if err != nil {
return "", err
}
items = list
}
snapshot := r.mem.Snapshot()
sort.Slice(snapshot, func(i, j int) bool { return snapshot[i].Name < snapshot[j].Name })
views := make([]crdHashView, 0, len(snapshot))
for i := range snapshot {
sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name })
views := make([]crdHashView, 0, len(items))
for i := range items {
views = append(views, crdHashView{
Name: snapshot[i].Name,
Spec: snapshot[i].Spec,
Name: items[i].Name,
Spec: items[i].Spec,
})
}
b, err := json.Marshal(views)
@@ -143,7 +234,7 @@ func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Con
return ctrl.Result{}, nil
}
newHash, err := r.computeConfigHash()
newHash, err := r.computeConfigHash(ctx)
if err != nil {
return ctrl.Result{}, err
}

View File

@@ -0,0 +1,80 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureBreadcrumb creates or updates a Breadcrumb resource for the given CRD
func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
group, version, kind := pickGVK(crd)
lowerKind := strings.ToLower(kind)
detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
obj := &dashv1alpha1.Breadcrumb{}
obj.SetName(detailID)
plural := pickPlural(kind, crd)
// Prefer dashboard.Plural for UI label if provided
labelPlural := titleFromKindPlural(kind, plural)
if crd != nil && crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Plural != "" {
labelPlural = crd.Spec.Dashboard.Plural
}
key := plural // e.g., "virtualmachines"
label := labelPlural
link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", strings.ToLower(group), strings.ToLower(version), plural)
// If this is a module, change the first breadcrumb item to "Tenant Modules"
if crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Module {
key = "tenantmodules"
label = "Tenant Modules"
link = "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules"
}
items := []any{
map[string]any{
"key": key,
"label": label,
"link": link,
},
map[string]any{
"key": strings.ToLower(kind), // "etcd"
"label": "{6}", // literal, as in your example
},
}
spec := map[string]any{
"id": detailID,
"breadcrumbItems": items,
}
_, 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
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
b, err := json.Marshal(spec)
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}}
if !compareArbitrarySpecs(obj.Spec, newSpec) {
obj.Spec = newSpec
}
return nil
})
return err
}

View File

@@ -0,0 +1,168 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureCustomColumnsOverride creates or updates a CustomColumnsOverride that
// renders a header row with a colored badge and resource name link, plus a few
// useful columns (Ready, Created, Version).
//
// Naming convention mirrors your example:
//
// metadata.name: stock-namespace-<group>.<version>.<plural>
// spec.id: stock-namespace-/<group>/<version>/<plural>
func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (controllerutil.OperationResult, error) {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
// Details page segment uses lowercase kind, mirroring your example
detailsSegment := strings.ToLower(kind) + "-details"
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)
href := fmt.Sprintf("/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/%s/{reqsJsonPath[0]['.metadata.name']['-']}", detailsSegment)
if g == "apps.cozystack.io" && kind == "Tenant" && plural == "tenants" {
href = "/openapi-ui/{2}/{reqsJsonPath[0]['.status.namespace']['-']}/api-table/core.cozystack.io/v1alpha1/tenantmodules"
}
desired := map[string]any{
"spec": map[string]any{
"id": id,
"additionalPrinterColumns": []any{
map[string]any{
"name": "Name",
"type": "factory",
"jsonPath": ".metadata.name",
"customProps": map[string]any{
"disableEventBubbling": true,
"items": []any{
map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": "header-row",
"align": "center",
"gap": 6,
},
"children": []any{
map[string]any{
"type": "antdText",
"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",
},
},
},
map[string]any{
"type": "antdLink",
"data": map[string]any{
"id": "name-link",
"text": "{reqsJsonPath[0]['.metadata.name']['-']}",
"href": href,
},
},
},
},
},
},
},
map[string]any{
"name": "Ready",
"type": "Boolean",
"jsonPath": `.status.conditions[?(@.type=="Ready")].status`,
},
map[string]any{
"name": "Created",
"type": "factory",
"jsonPath": ".metadata.creationTimestamp",
"customProps": map[string]any{
"disableEventBubbling": true,
"items": []any{
map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": "time-block",
"align": "center",
"gap": 6,
},
"children": []any{
map[string]any{
"type": "antdText",
"data": map[string]any{
"id": "time-icon",
"text": "🌐",
},
},
map[string]any{
"type": "parsedText",
"data": map[string]any{
"id": "time-value",
"text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}",
"formatter": "timestamp",
},
},
},
},
},
},
},
map[string]any{
"name": "Version",
"type": "string",
"jsonPath": ".status.version",
},
},
},
}
_, 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
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
b, err := json.Marshal(desired["spec"])
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}}
if !compareArbitrarySpecs(obj.Spec, newSpec) {
obj.Spec = newSpec
}
return nil
})
// Return OperationResultCreated/Updated is not available here with unstructured; we can mimic Updated when no error.
return controllerutil.OperationResultNone, err
}

View File

@@ -0,0 +1,75 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD
func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
name := fmt.Sprintf("%s.%s.%s", g, v, plural)
customizationID := fmt.Sprintf("default-/%s/%s/%s", g, v, plural)
obj := &dashv1alpha1.CustomFormsOverride{}
obj.SetName(name)
// Replicates your Helm includes (system metadata + api + status).
hidden := []any{}
hidden = append(hidden, hiddenMetadataSystem()...)
hidden = append(hidden, hiddenMetadataAPI()...)
hidden = append(hidden, hiddenStatus()...)
// If Name is set, hide metadata
if crd.Spec.Dashboard != nil && strings.TrimSpace(crd.Spec.Dashboard.Name) != "" {
hidden = append([]interface{}{
[]any{"metadata"},
}, hidden...)
}
var sort []any
if crd.Spec.Dashboard != nil && len(crd.Spec.Dashboard.KeysOrder) > 0 {
sort = make([]any, len(crd.Spec.Dashboard.KeysOrder))
for i, v := range crd.Spec.Dashboard.KeysOrder {
sort[i] = v
}
}
spec := map[string]any{
"customizationId": customizationID,
"hidden": hidden,
"sort": sort,
"schema": map[string]any{}, // {}
"strategy": "merge",
}
_, 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
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
b, err := json.Marshal(spec)
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}}
if !compareArbitrarySpecs(obj.Spec, newSpec) {
obj.Spec = newSpec
}
return nil
})
return err
}

View File

@@ -0,0 +1,81 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// ensureCustomFormsPrefill creates or updates a CustomFormsPrefill resource for the given CRD
func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
logger := log.FromContext(ctx)
app := crd.Spec.Application
group, version, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
name := fmt.Sprintf("%s.%s.%s", group, version, plural)
customizationID := fmt.Sprintf("default-/%s/%s/%s", group, version, plural)
values, err := buildPrefillValues(app.OpenAPISchema)
if err != nil {
return reconcile.Result{}, err
}
// Always prefill metadata.name (empty string if not specified in CRD)
var nameValue string
if crd.Spec.Dashboard != nil {
nameValue = strings.TrimSpace(crd.Spec.Dashboard.Name)
}
values = append([]interface{}{
map[string]interface{}{
"path": toIfaceSlice([]string{"metadata", "name"}),
"value": nameValue,
},
}, values...)
cfp := &dashv1alpha1.CustomFormsPrefill{}
cfp.Name = name // cluster-scoped
specMap := map[string]any{
"customizationId": customizationID,
"values": values,
}
// Use json.Marshal with sorted keys to ensure consistent output
specBytes, err := json.Marshal(specMap)
if err != nil {
return reconcile.Result{}, err
}
_, 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
m.addDashboardLabels(cfp, crd, ResourceTypeDynamic)
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{
JSON: apiextv1.JSON{Raw: specBytes},
}
if !compareArbitrarySpecs(cfp.Spec, newSpec) {
cfp.Spec = newSpec
}
return nil
})
if err != nil {
return reconcile.Result{}, err
}
logger.Info("Applied CustomFormsPrefill", "name", cfp.Name)
return reconcile.Result{}, nil
}

View File

@@ -0,0 +1,515 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureFactory creates or updates a Factory resource for the given CRD
func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
lowerKind := strings.ToLower(kind)
factoryName := fmt.Sprintf("%s-details", lowerKind)
resourceFetch := fmt.Sprintf("/api/clusters/{2}/k8s/apis/%s/%s/namespaces/{3}/%s/{6}", g, v, plural)
flags := factoryFeatureFlags(crd)
var keysOrder [][]string
if crd.Spec.Dashboard != nil {
keysOrder = crd.Spec.Dashboard.KeysOrder
}
tabs := []any{
detailsTab(kind, resourceFetch, crd.Spec.Application.OpenAPISchema, keysOrder),
}
if flags.Workloads {
tabs = append(tabs, workloadsTab(kind))
}
if flags.Ingresses {
tabs = append(tabs, ingressesTab(kind))
}
if flags.Services {
tabs = append(tabs, servicesTab(kind))
}
if flags.Secrets {
tabs = append(tabs, secretsTab(kind))
}
tabs = append(tabs, yamlTab(plural))
// Use unified factory creation
config := UnifiedResourceConfig{
Name: factoryName,
ResourceType: "factory",
Kind: kind,
Plural: plural,
Title: strings.ToLower(plural),
Size: BadgeSizeLarge,
}
spec := createUnifiedFactory(config, tabs, []any{resourceFetch})
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 {
return err
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
b, err := json.Marshal(spec)
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}}
if !compareArbitrarySpecs(obj.Spec, newSpec) {
obj.Spec = newSpec
}
return nil
})
return err
}
// ---------------- Tabs builders ----------------
func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[string]any {
paramsBlocks := buildOpenAPIParamsBlocks(schemaJSON, keysOrder)
paramsList := map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": "params-list",
"vertical": true,
"gap": float64(24),
},
"children": paramsBlocks,
}
leftColStack := []any{
antdText("details-title", true, kind, map[string]any{
"fontSize": float64(20),
"marginBottom": float64(12),
}),
antdFlexVertical("meta-name-block", 4, []any{
antdText("meta-name-label", true, "Name", nil),
parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil),
}),
antdFlexVertical("meta-namespace-block", 8, []any{
antdText("meta-namespace-label", true, "Namespace", nil),
map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": "namespace-row",
"align": "center",
"gap": float64(6),
},
"children": []any{
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
antdLink("namespace-link",
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
),
},
},
}),
antdFlexVertical("meta-created-block", 4, []any{
antdText("time-label", true, "Created", nil),
antdFlex("time-block", 6, []any{
antdText("time-icon", false, "🌐", nil),
parsedTextWithFormatter("time-value", "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", "timestamp"),
}),
}),
antdFlexVertical("meta-version-block", 4, []any{
antdText("version-label", true, "Version", nil),
parsedText("version-value", "{reqsJsonPath[0]['.status.version']['-']}", nil),
}),
antdFlexVertical("meta-released-block", 4, []any{
antdText("released-label", true, "Released", nil),
parsedText("released-value", "{reqsJsonPath[0]['.status.conditions[?(@.type==\"Released\")].status']['-']}", nil),
}),
antdFlexVertical("meta-ready-block", 4, []any{
antdText("ready-label", true, "Ready", nil),
parsedText("ready-value", "{reqsJsonPath[0]['.status.conditions[?(@.type==\"Ready\")].status']['-']}", nil),
}),
}
rightColStack := []any{
antdText("params-title", true, "Parameters", map[string]any{
"fontSize": float64(20),
"marginBottom": float64(12),
}),
paramsList,
}
return map[string]any{
"key": "details",
"label": "Details",
"children": []any{
contentCard("details-card", map[string]any{"marginBottom": float64(24)}, []any{
map[string]any{
"type": "antdRow",
"data": map[string]any{
"id": "details-grid",
"gutter": []any{float64(48), float64(12)},
},
"children": []any{
map[string]any{
"type": "antdCol",
"data": map[string]any{"id": "col-left", "span": float64(12)},
"children": []any{
map[string]any{
"type": "antdFlex",
"data": map[string]any{"id": "col-left-stack", "vertical": true, "gap": float64(24)},
"children": leftColStack,
},
},
},
map[string]any{
"type": "antdCol",
"data": map[string]any{"id": "col-right", "span": float64(12)},
"children": []any{
map[string]any{
"type": "antdFlex",
"data": map[string]any{"id": "col-right-stack", "vertical": true, "gap": float64(24)},
"children": rightColStack,
},
},
},
},
},
spacer("conditions-top-spacer", float64(16)),
antdText("conditions-title", true, "Conditions", map[string]any{"fontSize": float64(20)}),
spacer("conditions-spacer", float64(8)),
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "conditions-table",
"fetchUrl": endpoint,
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-status-conditions",
"baseprefix": "/openapi-ui",
"withoutControls": true,
"pathToItems": []any{"status", "conditions"},
},
},
}),
},
}
}
func workloadsTab(kind string) map[string]any {
return map[string]any{
"key": "workloads",
"label": "Workloads",
"children": []any{
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "workloads-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
},
},
},
},
}
}
func servicesTab(kind string) map[string]any {
return map[string]any{
"key": "services",
"label": "Services",
"children": []any{
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "services-table",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
"internal.cozystack.io/tenantresource": "true",
},
},
},
},
}
}
func ingressesTab(kind string) map[string]any {
return map[string]any{
"key": "ingresses",
"label": "Ingresses",
"children": []any{
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "ingresses-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-networking.k8s.io.v1.ingresses",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
"internal.cozystack.io/tenantresource": "true",
},
},
},
},
}
}
func secretsTab(kind string) map[string]any {
return map[string]any{
"key": "secrets",
"label": "Secrets",
"children": []any{
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "secrets-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables",
"pathToItems": []any{"items"},
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
},
},
},
},
}
}
func yamlTab(plural string) map[string]any {
return map[string]any{
"key": "yaml",
"label": "YAML",
"children": []any{
map[string]any{
"type": "YamlEditorSingleton",
"data": map[string]any{
"id": "yaml-editor",
"cluster": "{2}",
"isNameSpaced": true,
"type": "builtin",
"typeName": plural,
"prefillValuesRequestIndex": float64(0),
"substractHeight": float64(400),
},
},
},
}
}
// ---------------- OpenAPI → Right column ----------------
func buildOpenAPIParamsBlocks(schemaJSON string, keysOrder [][]string) []any {
var blocks []any
fields := collectOpenAPILeafFields(schemaJSON, 2, 20)
// Sort fields according to keysOrder if provided
if len(keysOrder) > 0 {
fields = sortFieldsByKeysOrder(fields, keysOrder)
}
for idx, f := range fields {
id := fmt.Sprintf("param-%d", idx)
blocks = append(blocks,
antdFlexVertical(id, 4, []any{
antdText(id+"-label", true, f.Label, nil),
parsedText(id+"-value", fmt.Sprintf("{reqsJsonPath[0]['.spec.%s']['-']}", f.JSONPathSpec), nil),
}),
)
}
if len(fields) == 0 {
blocks = append(blocks,
antdText("params-empty", false, "No scalar parameters detected in schema (see YAML tab for full spec).", map[string]any{"opacity": float64(0.7)}),
)
}
return blocks
}
// sortFieldsByKeysOrder sorts fields according to the provided keysOrder
func sortFieldsByKeysOrder(fields []fieldInfo, keysOrder [][]string) []fieldInfo {
// Create a map for quick lookup of field positions
orderMap := make(map[string]int)
for i, path := range keysOrder {
// Convert path to dot notation (e.g., ["spec", "systemDisk", "image"] -> "systemDisk.image")
if len(path) > 1 && path[0] == "spec" {
dotPath := strings.Join(path[1:], ".")
orderMap[dotPath] = i
}
}
// Sort fields based on their position in keysOrder
sort.Slice(fields, func(i, j int) bool {
posI, existsI := orderMap[fields[i].JSONPathSpec]
posJ, existsJ := orderMap[fields[j].JSONPathSpec]
// If both exist in orderMap, sort by position
if existsI && existsJ {
return posI < posJ
}
// If only one exists, prioritize the one that exists
if existsI {
return true
}
if existsJ {
return false
}
// If neither exists, maintain original order (stable sort)
return i < j
})
return fields
}
func collectOpenAPILeafFields(schemaJSON string, maxDepth, maxFields int) []fieldInfo {
type node = map[string]any
if strings.TrimSpace(schemaJSON) == "" {
return nil
}
var root any
if err := json.Unmarshal([]byte(schemaJSON), &root); err != nil {
// invalid JSON — skip
return nil
}
props := map[string]any{}
if m, ok := root.(node); ok {
if p, ok := m["properties"].(node); ok {
props = p
}
}
if len(props) == 0 {
return nil
}
var out []fieldInfo
var visit func(prefix []string, n node, depth int)
addField := func(path []string, schema node) {
// Skip excluded paths (backup/bootstrap/password)
if shouldExcludeParamPath(path) {
return
}
// build label "Foo Bar / Baz"
label := humanizePath(path)
desc := getString(schema, "description")
out = append(out, fieldInfo{
JSONPathSpec: strings.Join(path, "."),
Label: label,
Description: desc,
})
}
visit = func(prefix []string, n node, depth int) {
if len(out) >= maxFields {
return
}
// Scalar?
if isScalarType(n) || isIntOrString(n) || hasEnum(n) {
addField(prefix, n)
return
}
// Object with properties
if props, ok := n["properties"].(node); ok {
if depth >= maxDepth {
// too deep — stop
return
}
// deterministic ordering
keys := make([]string, 0, len(props))
for k := range props {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
child, _ := props[k].(node)
visit(append(prefix, k), child, depth+1)
if len(out) >= maxFields {
return
}
}
return
}
// Arrays: try to render item if it's scalar and depth limit allows
if n["type"] == "array" {
if items, ok := n["items"].(node); ok && (isScalarType(items) || isIntOrString(items) || hasEnum(items)) {
addField(prefix, items)
}
return
}
// Otherwise skip (unknown/complex)
}
// top-level: iterate properties
keys := make([]string, 0, len(props))
for k := range props {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if child, ok := props[k].(node); ok {
visit([]string{k}, child, 1)
if len(out) >= maxFields {
break
}
}
}
return out
}
// ---------------- Feature flags ----------------
type factoryFlags struct {
Workloads bool
Ingresses bool
Services bool
Secrets bool
}
// factoryFeatureFlags tries several conventional locations so you can evolve the API
// without breaking the controller. Defaults are false (hidden).
func factoryFeatureFlags(crd *cozyv1alpha1.CozystackResourceDefinition) factoryFlags {
var f factoryFlags
f.Workloads = true
f.Ingresses = true
f.Services = true
f.Secrets = true
return f
}

View File

@@ -0,0 +1,542 @@
package dashboard
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"regexp"
"sort"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
)
// ---------------- Types used by OpenAPI parsing ----------------
type fieldInfo struct {
JSONPathSpec string // dotted path under .spec (e.g., "systemDisk.image")
Label string // "System Disk / Image" or "systemDisk.image"
Description string
}
// ---------------- Public entry: ensure Factory ------------------
// pickGVK tries to read group/version/kind from the CRD. We prefer the "application" section,
// falling back to other likely fields if your schema differs.
func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kind string) {
// Best guess based on your examples:
if crd.Spec.Application.Kind != "" {
kind = crd.Spec.Application.Kind
}
// For applications, always use apps.cozystack.io group, not the CRD's own group
group = "apps.cozystack.io"
version = "v1alpha1"
// Reasonable fallbacks if any are empty:
if kind == "" {
kind = "Resource"
}
return
}
// pickPlural prefers a field on the CRD if you have it; otherwise do a simple lowercase + "s".
func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) string {
// If you have crd.Spec.Application.Plural, prefer it. Example:
if crd.Spec.Application.Plural != "" {
return crd.Spec.Application.Plural
}
// naive pluralization
k := strings.ToLower(kind)
if strings.HasSuffix(k, "s") {
return k
}
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.
func defaultOrZero(sub map[string]interface{}) interface{} {
if v, ok := sub["default"]; ok {
return v
}
typ, _ := sub["type"].(string)
switch typ {
case "string":
return ""
case "boolean":
return false
case "array":
return []interface{}{}
case "integer", "number":
return 0
case "object":
return map[string]interface{}{}
default:
return nil
}
}
// toIfaceSlice converts []string -> []interface{}.
func toIfaceSlice(ss []string) []interface{} {
out := make([]interface{}, len(ss))
for i, s := range ss {
out[i] = s
}
return out
}
// buildPrefillValues converts an OpenAPI schema (JSON string) into a []interface{} "values" list
// suitable for CustomFormsPrefill.spec.values.
// Rules:
// - For top-level primitive/array fields: emit an entry, using default if present, otherwise zero.
// - For top-level objects: recursively process nested objects and emit entries for all default values
// found at any nesting level.
func buildPrefillValues(openAPISchema string) ([]interface{}, error) {
var root map[string]interface{}
if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil {
return nil, fmt.Errorf("cannot parse openAPISchema: %w", err)
}
props, _ := root["properties"].(map[string]interface{})
if props == nil {
return []interface{}{}, nil
}
var values []interface{}
processSchemaProperties(props, []string{"spec"}, &values, true)
return values, nil
}
// processSchemaProperties recursively processes OpenAPI schema properties and extracts default values
func processSchemaProperties(props map[string]interface{}, path []string, values *[]interface{}, topLevel bool) {
for pname, raw := range props {
sub, _ := raw.(map[string]interface{})
if sub == nil {
continue
}
typ, _ := sub["type"].(string)
currentPath := append(path, pname)
switch typ {
case "object":
// Check if this object has a default value
if objDefault, ok := sub["default"].(map[string]interface{}); ok {
// Process the default object recursively
processDefaultObject(objDefault, currentPath, values)
}
// Also process child properties for their individual defaults
if childProps, ok := sub["properties"].(map[string]interface{}); ok {
processSchemaProperties(childProps, currentPath, values, false)
}
default:
// For primitive types, use default if present, otherwise zero value
val := defaultOrZero(sub)
// Only emit zero-value entries when at top level
if val != nil || topLevel {
entry := map[string]interface{}{
"path": toIfaceSlice(currentPath),
"value": val,
}
*values = append(*values, entry)
}
}
}
}
// processDefaultObject recursively processes a default object and creates entries for all nested values
func processDefaultObject(obj map[string]interface{}, path []string, values *[]interface{}) {
for key, value := range obj {
currentPath := append(path, key)
// If the value is a map, process it recursively
if nestedObj, ok := value.(map[string]interface{}); ok {
processDefaultObject(nestedObj, currentPath, values)
} else {
// For primitive values, create an entry
entry := map[string]interface{}{
"path": toIfaceSlice(currentPath),
"value": value,
}
*values = append(*values, entry)
}
}
}
// normalizeJSON makes maps/slices JSON-safe for k8s Unstructured:
// - converts all int/int32/... to float64
// - leaves strings, bools, nil as-is
func normalizeJSON(v any) any {
switch t := v.(type) {
case map[string]any:
out := make(map[string]any, len(t))
for k, val := range t {
out[k] = normalizeJSON(val)
}
return out
case []any:
out := make([]any, len(t))
for i := range t {
out[i] = normalizeJSON(t[i])
}
return out
case int:
return float64(t)
case int8:
return float64(t)
case int16:
return float64(t)
case int32:
return float64(t)
case int64:
return float64(t)
case uint, uint8, uint16, uint32, uint64:
return float64(reflect.ValueOf(t).Convert(reflect.TypeOf(uint64(0))).Uint())
case float32:
return float64(t)
default:
return v
}
}
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 {
switch getString(n, "type") {
case "string", "integer", "number", "boolean":
return true
default:
return false
}
}
func isIntOrString(n map[string]any) bool {
// Kubernetes extension: x-kubernetes-int-or-string: true
if v, ok := n["x-kubernetes-int-or-string"]; ok {
if b, ok := v.(bool); ok && b {
return true
}
}
// anyOf: integer|string
if anyOf, ok := n["anyOf"].([]any); ok {
hasInt := false
hasStr := false
for _, it := range anyOf {
if m, ok := it.(map[string]any); ok {
switch getString(m, "type") {
case "integer":
hasInt = true
case "string":
hasStr = true
}
}
}
return hasInt && hasStr
}
return false
}
func hasEnum(n map[string]any) bool {
_, ok := n["enum"]
return ok
}
func getString(n map[string]any, key string) string {
if v, ok := n[key]; ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// shouldExcludeParamPath returns true if any part of the path contains
// backup / bootstrap / password (case-insensitive)
func shouldExcludeParamPath(parts []string) bool {
for _, p := range parts {
lp := strings.ToLower(p)
if strings.Contains(lp, "backup") || strings.Contains(lp, "bootstrap") || strings.Contains(lp, "password") || strings.Contains(lp, "cloudinit") {
return true
}
}
return false
}
func humanizePath(parts []string) string {
// "systemDisk.image" -> "System Disk / Image"
return strings.Join(parts, " / ")
}
// titleFromKindPlural returns a presentable plural label, e.g.:
// kind="VirtualMachine", plural="virtualmachines" => "VirtualMachines"
func titleFromKindPlural(kind, plural string) string {
return kind + "s"
}
// The hidden lists below mirror the Helm templates you shared.
// Each entry is a path as nested string array, e.g. ["metadata","creationTimestamp"].
func hiddenMetadataSystem() []any {
return []any{
[]any{"metadata", "annotations"},
[]any{"metadata", "labels"},
[]any{"metadata", "namespace"},
[]any{"metadata", "creationTimestamp"},
[]any{"metadata", "deletionGracePeriodSeconds"},
[]any{"metadata", "deletionTimestamp"},
[]any{"metadata", "finalizers"},
[]any{"metadata", "generateName"},
[]any{"metadata", "generation"},
[]any{"metadata", "managedFields"},
[]any{"metadata", "ownerReferences"},
[]any{"metadata", "resourceVersion"},
[]any{"metadata", "selfLink"},
[]any{"metadata", "uid"},
}
}
func hiddenMetadataAPI() []any {
return []any{
[]any{"kind"},
[]any{"apiVersion"},
[]any{"appVersion"},
}
}
func hiddenStatus() []any {
return []any{
[]any{"status"},
}
}
// compareArbitrarySpecs compares two ArbitrarySpec objects by comparing their JSON content
func compareArbitrarySpecs(spec1, spec2 dashv1alpha1.ArbitrarySpec) bool {
// If both are empty, they're equal
if len(spec1.JSON.Raw) == 0 && len(spec2.JSON.Raw) == 0 {
return true
}
// If one is empty and the other is not, they're different
if len(spec1.JSON.Raw) == 0 || len(spec2.JSON.Raw) == 0 {
return false
}
// Parse and normalize both specs
norm1, err := normalizeJSONForComparison(spec1.JSON.Raw)
if err != nil {
return false
}
norm2, err := normalizeJSONForComparison(spec2.JSON.Raw)
if err != nil {
return false
}
// Compare normalized JSON
equal := string(norm1) == string(norm2)
return equal
}
// normalizeJSONForComparison normalizes JSON by sorting arrays and objects
func normalizeJSONForComparison(data []byte) ([]byte, error) {
var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return nil, err
}
// Recursively normalize the object
normalized := normalizeObject(obj)
// Re-marshal to get normalized JSON
return json.Marshal(normalized)
}
// normalizeObject recursively normalizes objects and arrays
func normalizeObject(obj interface{}) interface{} {
switch v := obj.(type) {
case map[string]interface{}:
// For maps, we don't need to sort keys as json.Marshal handles that
result := make(map[string]interface{})
for k, val := range v {
result[k] = normalizeObject(val)
}
return result
case []interface{}:
// For arrays, we need to sort them if they contain objects with comparable fields
if len(v) == 0 {
return v
}
// Check if this is an array of objects that can be sorted
if canSortArray(v) {
// Sort the array
sorted := make([]interface{}, len(v))
copy(sorted, v)
sortArray(sorted)
return sorted
}
// If we can't sort, just normalize each element
result := make([]interface{}, len(v))
for i, val := range v {
result[i] = normalizeObject(val)
}
return result
default:
return v
}
}
// canSortArray checks if an array can be sorted (contains objects with comparable fields)
func canSortArray(arr []interface{}) bool {
if len(arr) == 0 {
return false
}
// Check if all elements are objects
for _, item := range arr {
if _, ok := item.(map[string]interface{}); !ok {
return false
}
}
// Check if objects have comparable fields (like "path" for CustomFormsPrefill values)
firstObj, ok := arr[0].(map[string]interface{})
if !ok {
return false
}
// Look for "path" field which is used in CustomFormsPrefill values
if _, hasPath := firstObj["path"]; hasPath {
return true
}
return false
}
// sortArray sorts an array of objects by their "path" field
func sortArray(arr []interface{}) {
sort.Slice(arr, func(i, j int) bool {
objI, okI := arr[i].(map[string]interface{})
objJ, okJ := arr[j].(map[string]interface{})
if !okI || !okJ {
return false
}
pathI, hasPathI := objI["path"]
pathJ, hasPathJ := objJ["path"]
if !hasPathI || !hasPathJ {
return false
}
// Convert paths to strings for comparison
pathIStr := fmt.Sprintf("%v", pathI)
pathJStr := fmt.Sprintf("%v", pathJ)
return pathIStr < pathJStr
})
}

View File

@@ -0,0 +1,451 @@
package dashboard
import (
"context"
"fmt"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
// Label keys for dashboard resource management
LabelManagedBy = "dashboard.cozystack.io/managed-by"
LabelResourceType = "dashboard.cozystack.io/resource-type"
LabelCRDName = "dashboard.cozystack.io/crd-name"
LabelCRDGroup = "dashboard.cozystack.io/crd-group"
LabelCRDVersion = "dashboard.cozystack.io/crd-version"
LabelCRDKind = "dashboard.cozystack.io/crd-kind"
LabelCRDPlural = "dashboard.cozystack.io/crd-plural"
// Label values
ManagedByValue = "cozystack-dashboard-controller"
ResourceTypeStatic = "static"
ResourceTypeDynamic = "dynamic"
)
// AddToScheme exposes dashboard types registration for controller setup.
func AddToScheme(s *runtime.Scheme) error {
return dashv1alpha1.AddToScheme(s)
}
// 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 }
}
// 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)
}
return m
}
// EnsureForCRD is the single entry-point used by the controller.
// Add more ensure* calls here as you implement support for other resources:
//
// - ensureBreadcrumb (implemented)
// - ensureCustomColumnsOverride (implemented)
// - ensureCustomFormsOverride (implemented)
// - ensureCustomFormsPrefill (implemented)
// - ensureFactory
// - ensureMarketplacePanel (implemented)
// - ensureSidebar (implemented)
// - ensureTableUriMapping (implemented)
func (m *Manager) EnsureForCRD(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
// Early return if crd.Spec.Dashboard is nil to prevent oscillation
if crd.Spec.Dashboard == nil {
return reconcile.Result{}, nil
}
// MarketplacePanel
if res, err := m.ensureMarketplacePanel(ctx, crd); err != nil || res.Requeue || res.RequeueAfter > 0 {
return res, err
}
// CustomFormsPrefill
if res, err := m.ensureCustomFormsPrefill(ctx, crd); err != nil || res.Requeue || res.RequeueAfter > 0 {
return res, err
}
// CustomColumnsOverride
if _, err := m.ensureCustomColumnsOverride(ctx, crd); err != nil {
return reconcile.Result{}, err
}
if err := m.ensureTableUriMapping(ctx, crd); err != nil {
return reconcile.Result{}, err
}
if err := m.ensureBreadcrumb(ctx, crd); err != nil {
return reconcile.Result{}, err
}
if err := m.ensureCustomFormsOverride(ctx, crd); err != nil {
return reconcile.Result{}, err
}
if err := m.ensureSidebar(ctx, crd); err != nil {
return reconcile.Result{}, err
}
if err := m.ensureFactory(ctx, crd); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
// InitializeStaticResources creates all static dashboard resources once during controller startup
func (m *Manager) InitializeStaticResources(ctx context.Context) error {
return m.ensureStaticResources(ctx)
}
// addDashboardLabels adds standard dashboard management labels to a resource
func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.CozystackResourceDefinition, resourceType string) {
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
labels[LabelManagedBy] = ManagedByValue
labels[LabelResourceType] = resourceType
if crd != nil {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
labels[LabelCRDName] = crd.Name
labels[LabelCRDGroup] = g
labels[LabelCRDVersion] = v
labels[LabelCRDKind] = kind
labels[LabelCRDPlural] = plural
}
obj.SetLabels(labels)
}
// getDashboardResourceSelector returns a label selector for dashboard-managed resources
func (m *Manager) getDashboardResourceSelector() client.MatchingLabels {
return client.MatchingLabels{
LabelManagedBy: ManagedByValue,
}
}
// getDynamicResourceSelector returns a label selector for dynamic dashboard resources
func (m *Manager) getDynamicResourceSelector() client.MatchingLabels {
return client.MatchingLabels{
LabelManagedBy: ManagedByValue,
LabelResourceType: ResourceTypeDynamic,
}
}
// getStaticResourceSelector returns a label selector for static dashboard resources
func (m *Manager) getStaticResourceSelector() client.MatchingLabels {
return client.MatchingLabels{
LabelManagedBy: ManagedByValue,
LabelResourceType: ResourceTypeStatic,
}
}
// 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
}
// Build a set of expected resource names for each type
expectedResources := m.buildExpectedResourceSet(allCRDs)
// Clean up each resource type
resourceTypes := []client.Object{
&dashv1alpha1.CustomColumnsOverride{},
&dashv1alpha1.CustomFormsOverride{},
&dashv1alpha1.CustomFormsPrefill{},
&dashv1alpha1.MarketplacePanel{},
&dashv1alpha1.Sidebar{},
&dashv1alpha1.TableUriMapping{},
&dashv1alpha1.Breadcrumb{},
&dashv1alpha1.Factory{},
}
for _, resourceType := range resourceTypes {
if err := m.cleanupResourceType(ctx, resourceType, expectedResources); err != nil {
return err
}
}
return nil
}
// buildExpectedResourceSet creates a map of expected resource names by type
func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResourceDefinition) map[string]map[string]bool {
expected := make(map[string]map[string]bool)
// Initialize maps for each resource type
resourceTypes := []string{
"CustomColumnsOverride",
"CustomFormsOverride",
"CustomFormsPrefill",
"MarketplacePanel",
"Sidebar",
"TableUriMapping",
"Breadcrumb",
"Factory",
}
for _, rt := range resourceTypes {
expected[rt] = make(map[string]bool)
}
// Add static resources (these should always exist)
staticResources := CreateAllStaticResources()
for _, resource := range staticResources {
resourceType := resource.GetObjectKind().GroupVersionKind().Kind
if expected[resourceType] != nil {
expected[resourceType][resource.GetName()] = true
}
}
// Add dynamic resources based on current CRDs
for _, crd := range crds {
if crd.Spec.Dashboard == nil {
continue
}
// Note: We include ALL resources with dashboard config, regardless of module flag
// because ensureFactory and ensureBreadcrumb create resources for all CRDs with dashboard config
g, v, kind := pickGVK(&crd)
plural := pickPlural(kind, &crd)
// CustomColumnsOverride - created for ALL CRDs with dashboard config
name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
expected["CustomColumnsOverride"][name] = true
// CustomFormsOverride - created for ALL CRDs with dashboard config
name = fmt.Sprintf("%s.%s.%s", g, v, plural)
expected["CustomFormsOverride"][name] = true
// CustomFormsPrefill - created for ALL CRDs with dashboard config
expected["CustomFormsPrefill"][name] = true
// MarketplacePanel - only created for non-module CRDs
if !crd.Spec.Dashboard.Module {
expected["MarketplacePanel"][crd.Name] = true
}
// Sidebar resources - created for ALL CRDs with dashboard config
lowerKind := strings.ToLower(kind)
detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
expected["Sidebar"][detailsID] = true
// Add other stock sidebars that are created for each CRD
stockSidebars := []string{
"stock-instance-api-form",
"stock-instance-api-table",
"stock-instance-builtin-form",
"stock-instance-builtin-table",
"stock-project-factory-marketplace",
"stock-project-factory-workloadmonitor-details",
"stock-project-api-form",
"stock-project-api-table",
"stock-project-builtin-form",
"stock-project-builtin-table",
"stock-project-crd-form",
"stock-project-crd-table",
}
for _, sidebarID := range stockSidebars {
expected["Sidebar"][sidebarID] = true
}
// TableUriMapping - created for ALL CRDs with dashboard config
name = fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
expected["TableUriMapping"][name] = true
// Breadcrumb - created for ALL CRDs with dashboard config
detailID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
expected["Breadcrumb"][detailID] = true
// Factory - created for ALL CRDs with dashboard config
factoryName := fmt.Sprintf("%s-details", lowerKind)
expected["Factory"][factoryName] = true
}
return expected
}
// cleanupResourceType removes orphaned resources of a specific type
func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.Object, expectedResources map[string]map[string]bool) error {
var (
list client.ObjectList
resourceKind string
)
switch resourceType.(type) {
case *dashv1alpha1.CustomColumnsOverride:
list = &dashv1alpha1.CustomColumnsOverrideList{}
resourceKind = "CustomColumnsOverride"
case *dashv1alpha1.CustomFormsOverride:
list = &dashv1alpha1.CustomFormsOverrideList{}
resourceKind = "CustomFormsOverride"
case *dashv1alpha1.CustomFormsPrefill:
list = &dashv1alpha1.CustomFormsPrefillList{}
resourceKind = "CustomFormsPrefill"
case *dashv1alpha1.MarketplacePanel:
list = &dashv1alpha1.MarketplacePanelList{}
resourceKind = "MarketplacePanel"
case *dashv1alpha1.Sidebar:
list = &dashv1alpha1.SidebarList{}
resourceKind = "Sidebar"
case *dashv1alpha1.TableUriMapping:
list = &dashv1alpha1.TableUriMappingList{}
resourceKind = "TableUriMapping"
case *dashv1alpha1.Breadcrumb:
list = &dashv1alpha1.BreadcrumbList{}
resourceKind = "Breadcrumb"
case *dashv1alpha1.Factory:
list = &dashv1alpha1.FactoryList{}
resourceKind = "Factory"
default:
return nil // Unknown type
}
expected := expectedResources[resourceKind]
if expected == nil {
return nil // No expected resources for this type
}
// List with dashboard labels
if err := m.client.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
return err
}
// Delete resources that are not in the expected set
switch l := list.(type) {
case *dashv1alpha1.CustomColumnsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.CustomFormsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.CustomFormsPrefillList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.MarketplacePanelList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.SidebarList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.TableUriMappingList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
// Resource already deleted, continue
}
}
}
case *dashv1alpha1.BreadcrumbList:
for _, item := range l.Items {
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 !apierrors.IsNotFound(err) {
return err
}
}
}
}
case *dashv1alpha1.FactoryList:
for _, item := range l.Items {
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 !apierrors.IsNotFound(err) {
return err
}
}
}
}
}
return nil
}

View File

@@ -0,0 +1,111 @@
package dashboard
import (
"context"
"encoding/json"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// ensureMarketplacePanel creates or updates a MarketplacePanel resource for the given CRD
func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
logger := log.FromContext(ctx)
mp := &dashv1alpha1.MarketplacePanel{}
mp.Name = crd.Name // cluster-scoped resource, name mirrors CRD name
// 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)
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) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name)
return reconcile.Result{}, nil
}
// 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)
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) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because resource is a module", "name", mp.Name)
return reconcile.Result{}, nil
}
// Build desired spec from CRD fields
d := crd.Spec.Dashboard
app := crd.Spec.Application
displayName := d.Singular
if displayName == "" {
displayName = app.Kind
}
tags := make([]any, len(d.Tags))
for i, t := range d.Tags {
tags[i] = t
}
specMap := map[string]any{
"description": d.Description,
"name": displayName,
"type": "nonCrd",
"apiGroup": "apps.cozystack.io",
"apiVersion": "v1alpha1",
"typeName": app.Plural, // e.g., "buckets"
"disabled": false,
"hidden": false,
"tags": tags,
"icon": d.Icon,
}
specBytes, err := json.Marshal(specMap)
if err != nil {
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.client, mp, func() error {
if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(mp, crd, ResourceTypeDynamic)
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{
JSON: apiextv1.JSON{Raw: specBytes},
}
if !compareArbitrarySpecs(mp.Spec, newSpec) {
mp.Spec = newSpec
}
return nil
})
if err != nil {
return reconcile.Result{}, err
}
logger.Info("Applied MarketplacePanel", "name", mp.Name)
return reconcile.Result{}, nil
}

View File

@@ -0,0 +1,368 @@
package dashboard
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureSidebar creates/updates multiple Sidebar resources that share the same menu:
// - The "details" sidebar tied to the current kind (stock-project-factory-<kind>-details)
// - The stock-instance sidebars: api-form, api-table, builtin-form, builtin-table
// - The stock-project sidebars: api-form, api-table, builtin-form, builtin-table, crd-form, crd-table
//
// Menu rules:
// - The first section is "Marketplace" with two hardcoded entries:
// - Marketplace (/openapi-ui/{clusterName}/{namespace}/factory/marketplace)
// - Tenant Info (/openapi-ui/{clusterName}/{namespace}/factory/info-details/info)
// - All other sections are built from CRDs where spec.dashboard != nil.
// - Categories are ordered strictly as:
// Marketplace, IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration
// - Items within each category: sort by Weight (desc), then Label (A→Z).
func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
// Build the full menu once.
// 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
}
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
type item struct {
Key string
Label string
Link string
Weight int
}
categories := map[string][]item{} // category label -> children
keysAndTags := map[string]any{} // plural -> []string{ "<lower(kind)>-sidebar" }
// Collect sidebar names for module resources
var moduleSidebars []any
for i := range all {
def := &all[i]
// Include ONLY when spec.dashboard != nil
if def.Spec.Dashboard == nil {
continue
}
g, v, kind := pickGVK(def)
plural := pickPlural(kind, def)
lowerKind := strings.ToLower(kind)
// Check if this resource is a module
if def.Spec.Dashboard.Module {
// Special case: info should have its own keysAndTags, not be in modules
if lowerKind == "info" {
keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)}
} else {
// Add to modules sidebar list
moduleSidebars = append(moduleSidebars, fmt.Sprintf("%s-sidebar", lowerKind))
}
} else {
// Add to keysAndTags for non-module resources
keysAndTags[plural] = []any{fmt.Sprintf("%s-sidebar", lowerKind)}
}
// Only add to menu categories if not a module
if !def.Spec.Dashboard.Module {
cat := safeCategory(def) // falls back to "Resources" if empty
// Label: prefer dashboard.Plural if provided
label := titleFromKindPlural(kind, plural)
if def.Spec.Dashboard.Plural != "" {
label = def.Spec.Dashboard.Plural
}
// Weight (default 0)
weight := def.Spec.Dashboard.Weight
link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural)
categories[cat] = append(categories[cat], item{
Key: plural,
Label: label,
Link: link,
Weight: weight,
})
}
}
// Add modules to keysAndTags if we have any module sidebars
if len(moduleSidebars) > 0 {
keysAndTags["modules"] = moduleSidebars
}
// Add sidebars for built-in Kubernetes resources
keysAndTags["services"] = []any{"service-sidebar"}
keysAndTags["secrets"] = []any{"secret-sidebar"}
keysAndTags["ingresses"] = []any{"ingress-sidebar"}
// 3) Sort items within each category by Weight (desc), then Label (A→Z)
for cat := range categories {
sort.Slice(categories[cat], func(i, j int) bool {
if categories[cat][i].Weight != categories[cat][j].Weight {
return categories[cat][i].Weight < categories[cat][j].Weight // lower weight first
}
return strings.ToLower(categories[cat][i].Label) < strings.ToLower(categories[cat][j].Label)
})
}
// 4) Order categories strictly:
// Marketplace (hardcoded), IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration
orderedCats := orderCategoryLabels(categories)
// 5) Build menuItems (hardcode "Marketplace"; then dynamic categories; then hardcode "Administration")
menuItems := []any{
map[string]any{
"key": "marketplace",
"label": "Marketplace",
"children": []any{
map[string]any{
"key": "marketplace",
"label": "Marketplace",
"link": "/openapi-ui/{clusterName}/{namespace}/factory/marketplace",
},
},
},
}
for _, cat := range orderedCats {
// Skip "Marketplace" and "Administration" here since they're hardcoded
if strings.EqualFold(cat, "Marketplace") || strings.EqualFold(cat, "Administration") {
continue
}
children := []any{}
for _, it := range categories[cat] {
children = append(children, map[string]any{
"key": it.Key,
"label": it.Label,
"link": it.Link,
})
}
if len(children) > 0 {
menuItems = append(menuItems, map[string]any{
"key": slugify(cat),
"label": cat,
"children": children,
})
}
}
// Add hardcoded Administration section
menuItems = append(menuItems, map[string]any{
"key": "administration",
"label": "Administration",
"children": []any{
map[string]any{
"key": "info",
"label": "Info",
"link": "/openapi-ui/{clusterName}/{namespace}/factory/info-details/info",
},
map[string]any{
"key": "modules",
"label": "Modules",
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules",
},
map[string]any{
"key": "tenants",
"label": "Tenants",
"link": "/openapi-ui/{clusterName}/{namespace}/api-table/apps.cozystack.io/v1alpha1/tenants",
},
},
})
// 6) Prepare the list of Sidebar IDs to upsert with the SAME content
// Create sidebars for ALL CRDs with dashboard config
targetIDs := []string{
// stock-instance sidebars
"stock-instance-api-form",
"stock-instance-api-table",
"stock-instance-builtin-form",
"stock-instance-builtin-table",
// stock-project sidebars
"stock-project-factory-marketplace",
"stock-project-factory-workloadmonitor-details",
"stock-project-factory-kube-service-details",
"stock-project-factory-kube-secret-details",
"stock-project-factory-kube-ingress-details",
"stock-project-api-form",
"stock-project-api-table",
"stock-project-builtin-form",
"stock-project-builtin-table",
"stock-project-crd-form",
"stock-project-crd-table",
}
// Add details sidebars for all CRDs with dashboard config
for i := range all {
def := &all[i]
if def.Spec.Dashboard == nil {
continue
}
_, _, kind := pickGVK(def)
lowerKind := strings.ToLower(kind)
detailsID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
targetIDs = append(targetIDs, detailsID)
}
// 7) Upsert all target sidebars with identical menuItems and keysAndTags
return m.upsertMultipleSidebars(ctx, crd, targetIDs, keysAndTags, menuItems)
}
// upsertMultipleSidebars creates/updates several Sidebar resources with the same menu spec.
func (m *Manager) upsertMultipleSidebars(
ctx context.Context,
crd *cozyv1alpha1.CozystackResourceDefinition,
ids []string,
keysAndTags map[string]any,
menuItems []any,
) error {
for _, id := range ids {
spec := map[string]any{
"id": id,
"keysAndTags": keysAndTags,
"menuItems": menuItems,
}
obj := &dashv1alpha1.Sidebar{}
obj.SetName(id)
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") {
// This is a dynamic sidebar, set owner reference only if it matches the current CRD
_, _, kind := pickGVK(crd)
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 {
return err
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(obj, crd, ResourceTypeDynamic)
} else {
// This is a different CRD's sidebar, don't modify owner references or labels
// Just update the spec
}
} else {
// This is a static sidebar, don't set owner references
// Add static labels
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
labels[LabelManagedBy] = ManagedByValue
labels[LabelResourceType] = ResourceTypeStatic
obj.SetLabels(labels)
}
b, err := json.Marshal(spec)
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}}
if !compareArbitrarySpecs(obj.Spec, newSpec) {
obj.Spec = newSpec
}
return nil
}); err != nil {
return err
}
}
return nil
}
// orderCategoryLabels returns category labels ordered strictly as:
//
// Marketplace, IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration.
//
// It only returns labels that exist in `cats` (except "Marketplace" which is hardcoded by caller).
func orderCategoryLabels[T any](cats map[string][]T) []string {
if len(cats) == 0 {
return []string{"Marketplace", "IaaS", "PaaS", "NaaS", "Resources", "Administration"}
}
head := []string{"Marketplace", "IaaS", "PaaS", "NaaS"}
tail := []string{"Resources", "Administration"}
present := make(map[string]struct{}, len(cats))
for k := range cats {
present[k] = struct{}{}
}
var result []string
// Add head anchors (keep "Marketplace" in the order signature for the caller)
for _, h := range head {
result = append(result, h)
delete(present, h)
}
// Collect "others": exclude tail
var others []string
for k := range present {
if k == "Resources" || k == "Administration" {
continue
}
others = append(others, k)
}
sort.Slice(others, func(i, j int) bool { return strings.ToLower(others[i]) < strings.ToLower(others[j]) })
// Append others, then tail (always in fixed order)
result = append(result, others...)
result = append(result, tail...)
return result
}
// safeCategory returns spec.dashboard.category or "Resources" if not set.
func safeCategory(def *cozyv1alpha1.CozystackResourceDefinition) string {
if def == nil || def.Spec.Dashboard == nil {
return "Resources"
}
if def.Spec.Dashboard.Category != "" {
return def.Spec.Dashboard.Category
}
return "Resources"
}
// slugify converts a category label to a key-friendly identifier.
// "User Management" -> "usermanagement", "PaaS" -> "paas".
func slugify(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
out := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') {
out = append(out, c)
}
}
return string(out)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
package dashboard
import (
"context"
dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ensureStaticResources ensures all static dashboard resources are created
func (m *Manager) ensureStaticResources(ctx context.Context) error {
// Use refactored resources from static_refactored.go
// This replaces the old static variables with dynamic creation using helper functions
staticResources := CreateAllStaticResources()
// Create or update each static resource
for _, resource := range staticResources {
if err := m.ensureStaticResource(ctx, resource); err != nil {
return err
}
}
return nil
}
// ensureStaticResource creates or updates a single static resource
func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) error {
// Create a copy to avoid modifying the original
resource := obj.DeepCopyObject().(client.Object)
// Add dashboard labels to static resources
m.addDashboardLabels(resource, nil, ResourceTypeStatic)
_, 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
switch o := obj.(type) {
case *dashv1alpha1.CustomColumnsOverride:
resource.(*dashv1alpha1.CustomColumnsOverride).Spec = o.Spec
case *dashv1alpha1.Breadcrumb:
resource.(*dashv1alpha1.Breadcrumb).Spec = o.Spec
case *dashv1alpha1.CustomFormsOverride:
resource.(*dashv1alpha1.CustomFormsOverride).Spec = o.Spec
case *dashv1alpha1.Factory:
resource.(*dashv1alpha1.Factory).Spec = o.Spec
case *dashv1alpha1.Navigation:
resource.(*dashv1alpha1.Navigation).Spec = o.Spec
case *dashv1alpha1.TableUriMapping:
resource.(*dashv1alpha1.TableUriMapping).Spec = o.Spec
}
// Ensure labels are always set
m.addDashboardLabels(resource, nil, ResourceTypeStatic)
return nil
})
return err
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
package dashboard
import (
"context"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
)
// ensureTableUriMapping creates or updates a TableUriMapping resource for the given CRD
func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
// Links are fully managed by the CustomColumnsOverride.
return nil
}

View File

@@ -0,0 +1,209 @@
package dashboard
import "strings"
// ---------------- UI helpers (use float64 for numeric fields) ----------------
func contentCard(id string, style map[string]any, children []any) map[string]any {
return contentCardWithTitle(id, "", style, children)
}
func contentCardWithTitle(id any, title string, style map[string]any, children []any) map[string]any {
data := map[string]any{
"id": id,
"style": style,
}
if title != "" {
data["title"] = title
}
return map[string]any{
"type": "ContentCard",
"data": data,
"children": children,
}
}
func antdText(id string, strong bool, text string, style map[string]any) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateTextID("auto", "antd")
}
data := map[string]any{
"id": id,
"text": text,
"strong": strong,
}
if style != nil {
data["style"] = style
}
return map[string]any{"type": "antdText", "data": data}
}
func parsedText(id, text string, style map[string]any) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateTextID("auto", "parsed")
}
data := map[string]any{
"id": id,
"text": text,
}
if style != nil {
data["style"] = style
}
return map[string]any{"type": "parsedText", "data": data}
}
func parsedTextWithFormatter(id, text, formatter string) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateTextID("auto", "formatted")
}
return map[string]any{
"type": "parsedText",
"data": map[string]any{
"id": id,
"text": text,
"formatter": formatter,
},
}
}
func spacer(id string, space float64) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateContainerID("auto", "spacer")
}
return map[string]any{
"type": "Spacer",
"data": map[string]any{
"id": id,
"$space": space,
},
}
}
func antdFlex(id string, gap float64, children []any) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateContainerID("auto", "flex")
}
return map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": id,
"align": "center",
"gap": gap,
},
"children": children,
}
}
func antdFlexVertical(id string, gap float64, children []any) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateContainerID("auto", "flex-vertical")
}
return map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": id,
"vertical": true,
"gap": gap,
},
"children": children,
}
}
func antdRow(id string, gutter []any, children []any) map[string]any {
// Auto-generate ID if not provided
if id == "" {
id = generateContainerID("auto", "row")
}
return map[string]any{
"type": "antdRow",
"data": map[string]any{
"id": id,
"gutter": gutter,
},
"children": children,
}
}
func antdCol(id string, span float64, children []any) map[string]any {
return map[string]any{
"type": "antdCol",
"data": map[string]any{
"id": id,
"span": span,
},
"children": children,
}
}
func antdColWithStyle(id string, style map[string]any, children []any) map[string]any {
return map[string]any{
"type": "antdCol",
"data": map[string]any{
"id": id,
"style": style,
},
"children": children,
}
}
func antdLink(id, text, href string) map[string]any {
return map[string]any{
"type": "antdLink",
"data": map[string]any{
"id": id,
"text": text,
"href": href,
},
}
}
// ---------------- Badge helpers ----------------
// createBadge creates a badge element with the given text, color, and title
func createBadge(id, text, color, title string) map[string]any {
return map[string]any{
"type": "antdText",
"data": map[string]any{
"id": id,
"text": text,
"title": title,
"style": map[string]any{
"whiteSpace": "nowrap",
"backgroundColor": color,
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"textAlign": "center",
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"padding": "0 9px",
},
},
}
}
// 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)
}
// 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)
}

View File

@@ -0,0 +1,407 @@
package dashboard
import (
"crypto/sha1"
"fmt"
"strings"
)
// ---------------- Unified ID generation helpers ----------------
// generateID creates a unique ID based on the provided components
func generateID(components ...string) string {
if len(components) == 0 {
return ""
}
// Join components with hyphens and convert to lowercase
id := strings.ToLower(strings.Join(components, "-"))
// Remove any special characters that might cause issues
id = strings.ReplaceAll(id, ".", "-")
id = strings.ReplaceAll(id, "/", "-")
id = strings.ReplaceAll(id, " ", "-")
// Remove multiple consecutive hyphens
for strings.Contains(id, "--") {
id = strings.ReplaceAll(id, "--", "-")
}
// Remove leading/trailing hyphens
id = strings.Trim(id, "-")
return id
}
// generateSpecID creates a spec.id from metadata.name and other components
func generateSpecID(metadataName string, components ...string) string {
allComponents := append([]string{metadataName}, components...)
return generateID(allComponents...)
}
// generateMetadataName creates metadata.name from spec.id
func generateMetadataName(specID string) string {
// Convert ID format to metadata.name format
// Replace / with . for metadata.name
name := strings.ReplaceAll(specID, "/", ".")
// Clean up the name to be RFC 1123 compliant
// Remove any leading/trailing dots and ensure it starts/ends with alphanumeric
name = strings.Trim(name, ".")
// Replace multiple consecutive dots with single dot
for strings.Contains(name, "..") {
name = strings.ReplaceAll(name, "..", ".")
}
// Replace any remaining problematic patterns
// Handle cases like "stock-namespace-.v1" -> "stock-namespace-v1"
name = strings.ReplaceAll(name, "-.", "-")
name = strings.ReplaceAll(name, ".-", "-")
// Ensure it starts with alphanumeric character
if len(name) > 0 && !isAlphanumeric(name[0]) {
name = "a" + name
}
// Ensure it ends with alphanumeric character
if len(name) > 0 && !isAlphanumeric(name[len(name)-1]) {
name = name + "a"
}
return name
}
// isAlphanumeric checks if a character is alphanumeric
func isAlphanumeric(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
}
// ---------------- Unified badge generation helpers ----------------
// BadgeConfig holds configuration for badge generation
type BadgeConfig struct {
Text string
Color string
Title string
Size BadgeSize
}
// 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
func createUnifiedBadge(id string, config BadgeConfig) map[string]any {
fontSize := "15px"
if config.Size == BadgeSizeLarge {
fontSize = "20px"
} else if config.Size == BadgeSizeSmall {
fontSize = "12px"
}
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",
},
},
}
}
// 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,
}
return createUnifiedBadge(id, config)
}
// ---------------- Resource creation helpers with unified approach ----------------
// ResourceConfig holds configuration for resource creation
type ResourceConfig struct {
SpecID string
MetadataName string
Kind string
Title string
BadgeConfig BadgeConfig
}
// createResourceConfig creates a ResourceConfig from components
func createResourceConfig(components []string, kind, title string) ResourceConfig {
// Generate spec.id from components
specID := generateID(components...)
// Generate metadata.name from spec.id
metadataName := generateMetadataName(specID)
// Generate badge config
badgeConfig := generateBadgeConfig(kind, "", "", title)
return ResourceConfig{
SpecID: specID,
MetadataName: metadataName,
Kind: kind,
Title: title,
BadgeConfig: badgeConfig,
}
}
// ---------------- 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
func generateElementID(elementType, context string, components ...string) string {
allComponents := append([]string{elementType, context}, components...)
return generateID(allComponents...)
}
// generateBadgeID creates an ID for badge elements
func generateBadgeID(context string, kind string) string {
return generateElementID("badge", context, kind)
}
// generateLinkID creates an ID for link elements
func generateLinkID(context string, linkType string) string {
return generateElementID("link", context, linkType)
}
// generateTextID creates an ID for text elements
func generateTextID(context string, textType string) string {
return generateElementID("text", context, textType)
}
// generateContainerID creates an ID for container elements
func generateContainerID(context string, containerType string) string {
return generateElementID("container", context, containerType)
}
// generateTableID creates an ID for table elements
func generateTableID(context string, tableType string) string {
return generateElementID("table", context, tableType)
}
// ---------------- Enhanced resource creation with automatic IDs ----------------
// createResourceWithAutoID creates a resource with automatically generated IDs
func createResourceWithAutoID(resourceType, name string, spec map[string]any) map[string]any {
// Generate spec.id from name
specID := generateSpecID(name)
// Add the spec.id to the spec
spec["id"] = specID
return spec
}
// ---------------- Unified resource creation helpers ----------------
// UnifiedResourceConfig holds configuration for unified resource creation
type UnifiedResourceConfig struct {
Name string
ResourceType string
Kind string
Plural string
Title string
Color string
BadgeText string
Size BadgeSize
}
// createUnifiedFactory creates a factory using unified approach
func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch []any) map[string]any {
// Generate spec.id from name
specID := generateSpecID(config.Name)
// Create header with unified badge
badgeConfig := BadgeConfig{
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)
nameText := parsedText(generateTextID("header", "name"), "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": float64(20),
"lineHeight": "24px",
})
header := antdFlex(generateContainerID("header", "row"), float64(6), []any{
badge,
nameText,
})
// Add marginBottom style to header
if headerData, ok := header["data"].(map[string]any); ok {
if headerData["style"] == nil {
headerData["style"] = map[string]any{}
}
if style, ok := headerData["style"].(map[string]any); ok {
style["marginBottom"] = float64(24)
}
}
return map[string]any{
"key": config.Name,
"id": specID,
"sidebarTags": []any{fmt.Sprintf("%s-sidebar", strings.ToLower(config.Kind))},
"withScrollableMainContentCard": true,
"urlsToFetch": urlsToFetch,
"data": []any{
header,
map[string]any{
"type": "antdTabs",
"data": map[string]any{
"id": generateContainerID("tabs", strings.ToLower(config.Kind)),
"defaultActiveKey": "details",
"items": tabs,
},
},
},
}
}
// createUnifiedCustomColumn creates a custom column using unified approach
func createUnifiedCustomColumn(name, jsonPath, kind, title, href string) map[string]any {
badgeConfig := generateBadgeConfig(kind, "", "", title)
badge := createUnifiedBadge(generateBadgeID("column", kind), badgeConfig)
linkID := generateLinkID("column", "name")
if jsonPath == ".metadata.namespace" {
linkID = generateLinkID("column", "namespace")
}
link := antdLink(linkID, "{reqsJsonPath[0]['"+jsonPath+"']['-']}", href)
return map[string]any{
"name": name,
"type": "factory",
"jsonPath": jsonPath,
"customProps": map[string]any{
"disableEventBubbling": true,
"items": []any{
map[string]any{
"type": "antdFlex",
"data": map[string]any{
"id": generateContainerID("column", "header"),
"align": "center",
"gap": float64(6),
},
"children": []any{badge, link},
},
},
},
}
}
// ---------------- Utility functions ----------------
// hashString creates a short hash from a string for ID generation
func hashString(s string) string {
hash := sha1.Sum([]byte(s))
return fmt.Sprintf("%x", hash[:4])
}
// sanitizeForID removes characters that shouldn't be in IDs
func sanitizeForID(s string) string {
// Replace problematic characters
s = strings.ReplaceAll(s, ".", "-")
s = strings.ReplaceAll(s, "/", "-")
s = strings.ReplaceAll(s, " ", "-")
s = strings.ReplaceAll(s, "_", "-")
// Remove multiple consecutive hyphens
for strings.Contains(s, "--") {
s = strings.ReplaceAll(s, "--", "-")
}
// Remove leading/trailing hyphens
s = strings.Trim(s, "-")
return strings.ToLower(s)
}

View File

@@ -1,367 +0,0 @@
package lineagelabeler
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/shared/crdmem"
"github.com/cozystack/cozystack/pkg/lineage"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var ErrNoAncestors = errors.New("no ancestors")
type LineageLabelerReconciler struct {
client.Client
Scheme *runtime.Scheme
WatchResourceCSV string
dynClient dynamic.Interface
mapper meta.RESTMapper
appMap atomic.Value
once sync.Once
mem *crdmem.Memory
}
type chartRef struct{ repo, chart string }
type appRef struct{ groupVersion, kind, prefix string }
func (r *LineageLabelerReconciler) initMapping() {
r.once.Do(func() {
r.appMap.Store(make(map[chartRef]appRef))
})
}
func (r *LineageLabelerReconciler) currentMap() map[chartRef]appRef {
val := r.appMap.Load()
if val == nil {
return map[chartRef]appRef{}
}
return val.(map[chartRef]appRef)
}
func (r *LineageLabelerReconciler) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg := r.currentMap()
s := hr.Spec.Chart.Spec
key := chartRef{s.SourceRef.Name, s.Chart}
if v, ok := cfg[key]; ok {
return v.groupVersion, v.kind, v.prefix, nil
}
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
func parseGVKList(csv string) ([]schema.GroupVersionKind, error) {
csv = strings.TrimSpace(csv)
if csv == "" {
return nil, fmt.Errorf("watch resource list is empty")
}
parts := strings.Split(csv, ",")
out := make([]schema.GroupVersionKind, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
s := strings.Split(p, "/")
if len(s) == 2 {
out = append(out, schema.GroupVersionKind{Group: "", Version: s[0], Kind: s[1]})
continue
}
if len(s) == 3 {
out = append(out, schema.GroupVersionKind{Group: s[0], Version: s[1], Kind: s[2]})
continue
}
return nil, fmt.Errorf("invalid resource token %q, expected 'group/version/Kind' or 'v1/Kind'", p)
}
return out, nil
}
func (r *LineageLabelerReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.initMapping()
cfg := rest.CopyConfig(mgr.GetConfig())
dc, err := dynamic.NewForConfig(cfg)
if err != nil {
return err
}
disco, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return err
}
cached := memory.NewMemCacheClient(disco)
r.dynClient = dc
r.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cached)
if r.mem == nil {
r.mem = crdmem.Global()
}
if err := r.mem.EnsurePrimingWithManager(mgr); err != nil {
return err
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return err
}
if len(gvks) == 0 {
return fmt.Errorf("no resources to watch")
}
b := ctrl.NewControllerManagedBy(mgr).Named("lineage-labeler")
nsPred := predicate.NewPredicateFuncs(func(obj client.Object) bool {
ns := obj.GetNamespace()
return ns != "" && strings.HasPrefix(ns, "tenant-")
})
primary := gvks[0]
primaryObj := &unstructured.Unstructured{}
primaryObj.SetGroupVersionKind(primary)
b = b.For(primaryObj,
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
for _, gvk := range gvks[1:] {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
b = b.Watches(u,
&handler.EnqueueRequestForObject{},
builder.WithPredicates(
predicate.And(
nsPred,
predicate.Or(
predicate.GenerationChangedPredicate{},
predicate.ResourceVersionChangedPredicate{},
),
),
),
)
}
b = b.Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
_ = r.refreshAppMap(ctx)
return nil
}),
)
_ = r.refreshAppMap(context.Background())
return b.Complete(r)
}
func (r *LineageLabelerReconciler) refreshAppMap(ctx context.Context) error {
var items []cozyv1alpha1.CozystackResourceDefinition
var err error
if r.mem != nil {
items, err = r.mem.ListFromCacheOrAPI(ctx, r.Client)
} else {
var list cozyv1alpha1.CozystackResourceDefinitionList
err = r.Client.List(ctx, &list)
items = list.Items
}
if err != nil {
return err
}
newMap := make(map[chartRef]appRef, len(items))
for _, crd := range items {
k := chartRef{
repo: crd.Spec.Release.Chart.SourceRef.Name,
chart: crd.Spec.Release.Chart.Name,
}
v := appRef{
groupVersion: "apps.cozystack.io/v1alpha1",
kind: crd.Spec.Application.Kind,
prefix: crd.Spec.Release.Prefix,
}
if _, exists := newMap[k]; exists {
continue
}
newMap[k] = v
}
r.appMap.Store(newMap)
return nil
}
func (r *LineageLabelerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
if req.Namespace == "" || !strings.HasPrefix(req.Namespace, "tenant-") {
return ctrl.Result{}, nil
}
if len(r.currentMap()) == 0 {
_ = r.refreshAppMap(ctx)
if len(r.currentMap()) == 0 {
return ctrl.Result{RequeueAfter: 2 * time.Second}, nil
}
}
gvks, err := parseGVKList(r.WatchResourceCSV)
if err != nil {
return ctrl.Result{}, err
}
var obj *unstructured.Unstructured
found := false
for _, gvk := range gvks {
mapping, mErr := r.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if mErr != nil {
continue
}
ns := req.Namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
ns = ""
}
res, gErr := r.dynClient.Resource(mapping.Resource).Namespace(ns).Get(ctx, req.Name, metav1.GetOptions{})
if gErr != nil {
if apierrors.IsNotFound(gErr) {
continue
}
continue
}
obj = res
found = true
break
}
if !found || obj == nil {
return ctrl.Result{}, nil
}
existing := obj.GetLabels()
if existing == nil {
existing = map[string]string{}
}
keys := []string{
"apps.cozystack.io/application.group",
"apps.cozystack.io/application.kind",
"apps.cozystack.io/application.name",
}
allPresent := true
for _, k := range keys {
if _, ok := existing[k]; !ok {
allPresent = false
break
}
}
if allPresent {
return ctrl.Result{}, nil
}
labels, warn, err := r.computeLabels(ctx, obj)
if err != nil {
if errors.Is(err, ErrNoAncestors) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if warn != "" {
l.V(1).Info("lineage ambiguous; using first ancestor", "name", req.NamespacedName)
}
for k, v := range labels {
existing[k] = v
}
obj.SetLabels(existing)
// Server-Side Apply: claim ownership of our label keys
gvk := obj.GroupVersionKind()
patch := &unstructured.Unstructured{}
patch.SetGroupVersionKind(gvk)
patch.SetNamespace(obj.GetNamespace())
patch.SetName(obj.GetName())
patch.SetLabels(map[string]string{
"apps.cozystack.io/application.group": existing["apps.cozystack.io/application.group"],
"apps.cozystack.io/application.kind": existing["apps.cozystack.io/application.kind"],
"apps.cozystack.io/application.name": existing["apps.cozystack.io/application.name"],
})
// Use controller-runtime client with Apply patch type and field owner
if err := r.Patch(ctx, patch,
client.Apply,
client.FieldOwner("cozystack/lineage"),
client.ForceOwnership(false),
); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{RequeueAfter: 500 * time.Millisecond}, nil
}
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *LineageLabelerReconciler) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, string, error) {
owners := lineage.WalkOwnershipGraph(ctx, r.dynClient, r.mapper, r, o)
if len(owners) == 0 {
return nil, "", ErrNoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, r.dynClient, r.mapper)
if err != nil {
return nil, "", err
}
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion())
if err != nil {
return nil, "", fmt.Errorf("invalid APIVersion %s: %w", obj.GetAPIVersion(), err)
}
var warn string
if len(owners) > 1 {
warn = "ambiguous"
}
group := gv.Group
if len(group) > 63 {
group = trimDNSLabel(group[:63])
}
return map[string]string{
"apps.cozystack.io/application.group": group,
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
}, warn, nil
}
func trimDNSLabel(s string) string {
for len(s) > 0 {
b := s[len(s)-1]
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') {
return s
}
s = s[:len(s)-1]
}
return s
}

View File

@@ -0,0 +1,47 @@
package lineagecontrollerwebhook
import (
"fmt"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
)
type chartRef struct {
repo string
chart string
}
type appRef struct {
group string
kind string
}
type runtimeConfig struct {
chartAppMap map[chartRef]*cozyv1alpha1.CozystackResourceDefinition
appCRDMap map[appRef]*cozyv1alpha1.CozystackResourceDefinition
}
func (l *LineageControllerWebhook) initConfig() {
l.initOnce.Do(func() {
if l.config.Load() == nil {
l.config.Store(&runtimeConfig{
chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
})
}
})
}
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg, ok := l.config.Load().(*runtimeConfig)
if !ok {
return "", "", "", fmt.Errorf("failed to load chart-app mapping from config")
}
s := hr.Spec.Chart.Spec
val, ok := cfg.chartAppMap[chartRef{s.SourceRef.Name, s.Chart}]
if !ok {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
return "apps.cozystack.io/v1alpha1", val.Spec.Application.Kind, val.Spec.Release.Prefix, nil
}

View File

@@ -0,0 +1,54 @@
package lineagecontrollerwebhook
import (
"context"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch;get
func (c *LineageControllerWebhook) SetupWithManagerAsController(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(c)
}
func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crds := &cozyv1alpha1.CozystackResourceDefinitionList{}
if err := c.List(ctx, crds); err != nil {
l.Error(err, "failed reading CozystackResourceDefinitions")
return ctrl.Result{}, err
}
cfg := &runtimeConfig{
chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
}
for _, crd := range crds.Items {
chRef := chartRef{
crd.Spec.Release.Chart.SourceRef.Name,
crd.Spec.Release.Chart.Name,
}
appRef := appRef{
"apps.cozystack.io",
crd.Spec.Application.Kind,
}
newRef := crd
if _, exists := cfg.chartAppMap[chRef]; exists {
l.Info("duplicate chart mapping detected; ignoring subsequent entry", "key", chRef)
} else {
cfg.chartAppMap[chRef] = &newRef
}
if _, exists := cfg.appCRDMap[appRef]; exists {
l.Info("duplicate app mapping detected; ignoring subsequent entry", "key", appRef)
} else {
cfg.appCRDMap[appRef] = &newRef
}
}
c.config.Store(cfg)
return ctrl.Result{}, nil
}

View File

@@ -0,0 +1,73 @@
package lineagecontrollerwebhook
import (
"bytes"
"context"
"text/template"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// matchName checks if the provided name matches any of the resource names in the array.
// Each entry in resourceNames is treated as a Go template that gets rendered using the passed context.
// A nil resourceNames array matches any string.
func matchName(ctx context.Context, name string, templateContext map[string]string, resourceNames []string) bool {
if resourceNames == nil {
return true
}
logger := log.FromContext(ctx)
for _, templateStr := range resourceNames {
tmpl, err := template.New("resourceName").Parse(templateStr)
if err != nil {
logger.Error(err, "failed to parse resource name template", "template", templateStr)
continue
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, templateContext)
if err != nil {
logger.Error(err, "failed to execute resource name template", "template", templateStr, "context", templateContext)
continue
}
if buf.String() == name {
return true
}
}
return false
}
func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
sel, err := metav1.LabelSelectorAsSelector(&s.LabelSelector)
if err != nil {
log.FromContext(ctx).Error(err, "failed to convert label selector to selector")
return false
}
labelMatches := sel.Matches(labels.Set(l))
nameMatches := matchName(ctx, name, templateContext, s.ResourceNames)
return labelMatches && nameMatches
}
func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
for _, s := range ss {
if matchResourceToSelector(ctx, name, templateContext, l, s) {
return true
}
}
return false
}
func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, resources *cozyv1alpha1.CozystackResourceDefinitionResources) bool {
if resources == nil {
return false
}
if matchResourceToSelectorArray(ctx, name, templateContext, l, resources.Exclude) {
return false
}
return matchResourceToSelectorArray(ctx, name, templateContext, l, resources.Include)
}

View File

@@ -0,0 +1,23 @@
package lineagecontrollerwebhook
import (
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// +kubebuilder:webhook:path=/mutate-lineage,mutating=true,failurePolicy=Fail,sideEffects=None,groups="",resources=pods,secrets,services,persistentvolumeclaims,verbs=create;update,versions=v1,name=mlineage.cozystack.io,admissionReviewVersions={v1}
type LineageControllerWebhook struct {
client.Client
Scheme *runtime.Scheme
decoder admission.Decoder
dynClient dynamic.Interface
mapper meta.RESTMapper
config atomic.Value
initOnce sync.Once
}

View File

@@ -0,0 +1,200 @@
package lineagecontrollerwebhook
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/cozystack/cozystack/pkg/lineage"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1"
)
var (
NoAncestors = fmt.Errorf("no managed apps found in lineage")
AncestryAmbiguous = fmt.Errorf("object ancestry is ambiguous")
)
// getResourceSelectors returns the appropriate CozystackResourceDefinitionResources for a given GroupKind
func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.CozystackResourceDefinition) *cozyv1alpha1.CozystackResourceDefinitionResources {
switch {
case gk.Group == "" && gk.Kind == "Secret":
return &crd.Spec.Secrets
case gk.Group == "" && gk.Kind == "Service":
return &crd.Spec.Services
case gk.Group == "networking.k8s.io" && gk.Kind == "Ingress":
return &crd.Spec.Ingresses
default:
return nil
}
}
// SetupWithManager registers the handler with the webhook server.
func (h *LineageControllerWebhook) SetupWithManagerAsWebhook(mgr ctrl.Manager) error {
cfg := rest.CopyConfig(mgr.GetConfig())
var err error
h.dynClient, err = dynamic.NewForConfig(cfg)
if err != nil {
return err
}
httpClient, err := rest.HTTPClientFor(cfg)
if err != nil {
return err
}
h.mapper, err = apiutil.NewDynamicRESTMapper(cfg, httpClient)
if err != nil {
return err
}
h.initConfig()
// Register HTTP path -> handler.
mgr.GetWebhookServer().Register("/mutate-lineage", &admission.Webhook{Handler: h})
return nil
}
// InjectDecoder lets controller-runtime give us a decoder for AdmissionReview requests.
func (h *LineageControllerWebhook) InjectDecoder(d admission.Decoder) error {
h.decoder = d
return nil
}
// Handle is called for each AdmissionReview that matches the webhook config.
func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
logger := log.FromContext(ctx).WithValues(
"gvk", req.Kind.String(),
"namespace", req.Namespace,
"name", req.Name,
"operation", req.Operation,
)
warn := make(admission.Warnings, 0)
obj := &unstructured.Unstructured{}
if err := h.decodeUnstructured(req, obj); err != nil {
return admission.Errored(400, fmt.Errorf("decode object: %w", err))
}
labels, err := h.computeLabels(ctx, obj)
for {
if err != nil && errors.Is(err, NoAncestors) {
return admission.Allowed("object not managed by app")
}
if err != nil && errors.Is(err, AncestryAmbiguous) {
warn = append(warn, "object ancestry ambiguous, using first ancestor found")
break
}
if err != nil {
logger.Error(err, "error computing lineage labels")
return admission.Errored(500, fmt.Errorf("error computing lineage labels: %w", err))
}
if err == nil {
break
}
}
h.applyLabels(obj, labels)
mutated, err := json.Marshal(obj)
if err != nil {
return admission.Errored(500, fmt.Errorf("marshal mutated pod: %w", err))
}
logger.V(1).Info("mutated pod", "namespace", obj.GetNamespace(), "name", obj.GetName())
return admission.PatchResponseFromRaw(req.Object.Raw, mutated).WithWarnings(warn...)
}
func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, error) {
owners := lineage.WalkOwnershipGraph(ctx, h.dynClient, h.mapper, h, o)
if len(owners) == 0 {
return nil, NoAncestors
}
obj, err := owners[0].GetUnstructured(ctx, h.dynClient, h.mapper)
if err != nil {
return nil, err
}
gv, err := schema.ParseGroupVersion(obj.GetAPIVersion())
if err != nil {
// should never happen, we got an APIVersion right from the API
return nil, fmt.Errorf("could not parse APIVersion %s to a group and version: %w", obj.GetAPIVersion(), err)
}
if len(owners) > 1 {
err = AncestryAmbiguous
}
labels := map[string]string{
// truncate apigroup to first 63 chars
"apps.cozystack.io/application.group": func(s string) string {
if len(s) < 63 {
return s
}
s = s[:63]
for b := s[62]; !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')); s = s[:len(s)-1] {
b = s[len(s)-1]
}
return s
}(gv.Group),
"apps.cozystack.io/application.kind": obj.GetKind(),
"apps.cozystack.io/application.name": obj.GetName(),
}
templateLabels := map[string]string{
"kind": strings.ToLower(obj.GetKind()),
"name": obj.GetName(),
"namespace": o.GetNamespace(),
}
cfg := h.config.Load().(*runtimeConfig)
crd := cfg.appCRDMap[appRef{gv.Group, obj.GetKind()}]
resourceSelectors := h.getResourceSelectors(o.GroupVersionKind().GroupKind(), crd)
labels[corev1alpha1.TenantResourceLabelKey] = func(b bool) string {
if b {
return corev1alpha1.TenantResourceLabelValue
}
return "false"
}(matchResourceToExcludeInclude(ctx, o.GetName(), templateLabels, o.GetLabels(), resourceSelectors))
return labels, err
}
func (h *LineageControllerWebhook) applyLabels(o *unstructured.Unstructured, labels map[string]string) {
existing := o.GetLabels()
if existing == nil {
existing = make(map[string]string)
}
for k, v := range labels {
existing[k] = v
}
o.SetLabels(existing)
}
func (h *LineageControllerWebhook) decodeUnstructured(req admission.Request, out *unstructured.Unstructured) error {
if h.decoder != nil {
if err := h.decoder.Decode(req, out); err == nil {
return nil
}
if req.Kind.Group != "" || req.Kind.Kind != "" || req.Kind.Version != "" {
out.SetGroupVersionKind(schema.GroupVersionKind{
Group: req.Kind.Group,
Version: req.Kind.Version,
Kind: req.Kind.Kind,
})
if err := h.decoder.Decode(req, out); err == nil {
return nil
}
}
}
if len(req.Object.Raw) == 0 {
return errors.New("empty admission object")
}
return json.Unmarshal(req.Object.Raw, &out.Object)
}

View File

@@ -1,14 +1,12 @@
OUT=../_out/repos/apps
TMP := $(shell mktemp -d)
OUT=../../_out/repos/apps
CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}')
include ../../scripts/common-envs.mk
repo:
cd .. && ../hack/package_chart.sh apps $(OUT) $(TMP) library
rm -rf "$(OUT)"
helm package -d "$(OUT)" $(CHARTS) --version $(COZYSTACK_VERSION)
helm repo index "$(OUT)"
fix-chartnames:
find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}' | while read i; do sed -i "s/^name: .*/name: $$i/" "$$i/Chart.yaml"; done
gen-versions-map: fix-chartnames
../../hack/gen_versions_map.sh
check-version-map: gen-versions-map
git diff --exit-code -- versions_map
fix-charts:
find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}' | while read i; do sed -i -e "s/^name: .*/name: $$i/" -e "s/^version: .*/version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process/g" "$$i/Chart.yaml"; done

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: bucket
description: S3 compatible storage
icon: /logos/bucket.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "0.2.0"

View File

@@ -2,4 +2,5 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
yq -o json -i '.properties = {}' values.schema.json
yq -o json -i '.properties = {}' values.schema.json
../../../hack/update-crd.sh

View File

@@ -12,7 +12,14 @@ spec:
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
interval: 1m0s
timeout: 5m0s
interval: 5m
timeout: 10m
install:
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:
bucketName: {{ .Release.Name }}

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: clickhouse
description: Managed ClickHouse service
icon: /logos/clickhouse.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.13.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "24.9.2"

View File

@@ -5,6 +5,7 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh
image:
docker buildx build images/clickhouse-backup \

View File

@@ -27,7 +27,7 @@ For more details, read [Restic: Effective Backup from Stdin](https://blog.aenix.
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of Clickhouse replicas | `int` | `2` |
| `shards` | Number of Clickhouse shards | `int` | `1` |
| `resources` | Explicit CPU and memory configuration for each Clickhouse replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources` | Explicit CPU and memory configuration for each Clickhouse replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `small` |
@@ -65,7 +65,7 @@ For more details, read [Restic: Effective Backup from Stdin](https://blog.aenix.
| Name | Description | Type | Value |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `clickhouseKeeper` | Clickhouse Keeper configuration | `*object` | `{}` |
| `clickhouseKeeper` | Clickhouse Keeper configuration | `*object` | `null` |
| `clickhouseKeeper.enabled` | Deploy ClickHouse Keeper for cluster coordination | `*bool` | `true` |
| `clickhouseKeeper.size` | Persistent Volume Claim size, available for application data | `*quantity` | `1Gi` |
| `clickhouseKeeper.resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/clickhouse-backup:0.13.0@sha256:3faf7a4cebf390b9053763107482de175aa0fdb88c1e77424fd81100b1c3a205
ghcr.io/cozystack/cozystack/clickhouse-backup:0.0.0@sha256:3faf7a4cebf390b9053763107482de175aa0fdb88c1e77424fd81100b1c3a205

View File

@@ -5,16 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {
"cleanupStrategy": "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m",
"enabled": false,
"resticPassword": "\u003cpassword\u003e",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3Bucket": "s3.example.org/clickhouse-backups",
"s3Region": "us-east-1",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * *"
},
"default": {},
"required": [
"cleanupStrategy",
"enabled",
@@ -71,12 +62,7 @@
"clickhouseKeeper": {
"description": "Clickhouse Keeper configuration",
"type": "object",
"default": {
"enabled": true,
"replicas": 3,
"resourcesPreset": "micro",
"size": "1Gi"
},
"default": {},
"required": [
"resourcesPreset"
],

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: ferretdb
description: Managed FerretDB service
icon: /logos/ferretdb.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: 2.4.0

View File

@@ -2,6 +2,7 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh
update:
tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/FerretDB/FerretDB | awk -F'[/^]' '{sub("^v", "", $$3)} END{print $$3}') && \

View File

@@ -11,7 +11,7 @@ Internally, FerretDB service is backed by Postgres.
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `micro` |

View File

@@ -5,15 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {
"destinationPath": "s3://bucket/path/to/folder/",
"enabled": false,
"endpointURL": "http://minio-gateway-service:9000",
"retentionPolicy": "30d",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * * *"
},
"default": {},
"required": [
"destinationPath",
"enabled",
@@ -64,11 +56,7 @@
"bootstrap": {
"description": "Bootstrap (recovery) configuration",
"type": "object",
"default": {
"enabled": false,
"oldName": "",
"recoveryTime": ""
},
"default": {},
"properties": {
"enabled": {
"description": "Restore database cluster from a backup",
@@ -93,10 +81,7 @@
"quorum": {
"description": "Configuration for the quorum-based synchronous replication",
"type": "object",
"default": {
"maxSyncReplicas": 0,
"minSyncReplicas": 0
},
"default": {},
"required": [
"maxSyncReplicas",
"minSyncReplicas"

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: http-cache
description: Layer7 load balancer and caching service
icon: /logos/nginx.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.7.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "1.25.3"

View File

@@ -18,6 +18,7 @@ image-nginx:
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh
update:
tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/chrislim2888/IP2Location-C-Library | awk -F'[/^]' 'END{print $$3}') && \

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af

View File

@@ -18,11 +18,7 @@
"haproxy": {
"description": "HAProxy configuration",
"type": "object",
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "nano"
},
"default": {},
"required": [
"replicas",
"resources",
@@ -86,11 +82,7 @@
"nginx": {
"description": "Nginx configuration",
"type": "object",
"default": {
"replicas": 2,
"resources": {},
"resourcesPreset": "nano"
},
"default": {},
"required": [
"replicas",
"resourcesPreset"

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: kafka
description: Managed Kafka service
icon: /logos/kafka.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.8.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "3.7.0"

View File

@@ -3,3 +3,4 @@ PRESET_ENUM := ["nano","micro","small","medium","large","xlarge","2xlarge"]
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh

View File

@@ -10,13 +10,7 @@
"kafka": {
"description": "Kafka configuration",
"type": "object",
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "10Gi",
"storageClass": ""
},
"default": {},
"required": [
"replicas",
"resourcesPreset",
@@ -132,13 +126,7 @@
"zookeeper": {
"description": "Zookeeper configuration",
"type": "object",
"default": {
"replicas": 3,
"resources": {},
"resourcesPreset": "small",
"size": "5Gi",
"storageClass": ""
},
"default": {},
"required": [
"replicas",
"resourcesPreset",

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: kubernetes
description: Managed Kubernetes service
icon: /logos/kubernetes.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.29.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: 1.32.6

View File

@@ -1,4 +1,4 @@
KUBERNETES_VERSION = v1.32
KUBERNETES_VERSION = v1.33
KUBERNETES_PKG_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml)
include ../../../scripts/common-envs.mk
@@ -7,6 +7,7 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
yq -o=json -i '.properties.version.enum = (load("files/versions.yaml") | keys)' values.schema.json
../../../hack/update-crd.sh
image: image-ubuntu-container-disk image-kubevirt-cloud-provider image-kubevirt-csi-driver image-cluster-autoscaler

View File

@@ -91,21 +91,21 @@ See the reference for components utilized in this service:
### Application-specific parameters
| Name | Description | Type | Value |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | ------- |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.33` |
| `host` | Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty. | `string` | `""` |
| `nodeGroups` | Worker nodes configuration | `map[string]object` | `{...}` |
| `nodeGroups[name].minReplicas` | Minimum amount of replicas | `int` | `0` |
| `nodeGroups[name].maxReplicas` | Maximum amount of replicas | `int` | `0` |
| `nodeGroups[name].instanceType` | Virtual machine instance type | `string` | `""` |
| `nodeGroups[name].ephemeralStorage` | Ephemeral storage size | `quantity` | `""` |
| `nodeGroups[name].roles` | List of node's roles | `[]string` | `[]` |
| `nodeGroups[name].resources` | Resources available to each worker node | `object` | `{}` |
| `nodeGroups[name].resources.cpu` | CPU available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].resources.memory` | Memory (RAM) available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].gpus` | List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM) | `[]object` | `[]` |
| `nodeGroups[name].gpus.name` | Name of GPU, such as "nvidia.com/AD102GL_L40S" | `string` | `""` |
| Name | Description | Type | Value |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | ----------- |
| `version` | Kubernetes version given as vMAJOR.MINOR. Available are versions from 1.28 to 1.33. | `string` | `v1.33` |
| `host` | Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty. | `string` | `""` |
| `nodeGroups` | Worker nodes configuration | `map[string]object` | `{...}` |
| `nodeGroups[name].minReplicas` | Minimum amount of replicas | `int` | `0` |
| `nodeGroups[name].maxReplicas` | Maximum amount of replicas | `int` | `10` |
| `nodeGroups[name].instanceType` | Virtual machine instance type | `string` | `u1.medium` |
| `nodeGroups[name].ephemeralStorage` | Ephemeral storage size | `quantity` | `20Gi` |
| `nodeGroups[name].roles` | List of node's roles | `[]string` | `[]` |
| `nodeGroups[name].resources` | Resources available to each worker node | `object` | `{}` |
| `nodeGroups[name].resources.cpu` | CPU available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].resources.memory` | Memory (RAM) available to each worker node | `*quantity` | `null` |
| `nodeGroups[name].gpus` | List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM) | `[]object` | `{}` |
| `nodeGroups[name].gpus[i].name` | Name of GPU, such as "nvidia.com/AD102GL_L40S" | `string` | `""` |
### Cluster Addons

View File

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

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.0.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.29.0@sha256:3a3bc912f70ccba1e9f92a0754179dbdc4c01f24073467b6d1406c77da794863
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c8b08084a86251cdd18e237de89b695bca0e4f7eb1f1f6ddc2b903b4d74ea5ff

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.32@sha256:e53f2394c7aa76ad10818ffb945e40006cd77406999e47e036d41b8b0bf094cc
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:a09724a7f95283f9130b3da2a89d81c4c6051c6edf0392a81b6fc90f404b76b6

View File

@@ -266,6 +266,10 @@ metadata:
{{- end }}
spec:
clusterName: {{ $.Release.Name }}
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: {{ $.Release.Name }}
cluster.x-k8s.io/deployment-name: {{ $.Release.Name }}-{{ $groupName }}
template:
metadata:
labels:

View File

@@ -205,6 +205,29 @@ spec:
- mountPath: /etc/kubernetes/kubeconfig
name: kubeconfig
readOnly: true
- name: csi-resizer
image: registry.k8s.io/sig-storage/csi-resizer:v1.13.1
args:
- "-csi-address=/csi/csi.sock"
- "-kubeconfig=/etc/kubernetes/kubeconfig/super-admin.svc"
- "-v=5"
- "-timeout=3m"
- '-handle-volume-inuse-error=false'
volumeMounts:
- mountPath: /csi
name: socket-dir
- mountPath: /etc/kubernetes/kubeconfig
name: kubeconfig
readOnly: true
resources:
requests:
cpu: 10m
memory: 20Mi
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumes:
- name: socket-dir
emptyDir: {}

View File

@@ -36,3 +36,25 @@ roleRef:
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-kcsi
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ .Release.Name }}-kcsi
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ .Release.Name }}-kcsi-binding
roleRef:
kind: ClusterRole
name: {{ .Release.Name }}-kcsi
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-kcsi
namespace: {{ .Release.Namespace }}

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: cert-manager-crds
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-cert-manager-crds
storageNamespace: cozy-cert-manager-crds
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
{{- if .Values.addons.certManager.valuesOverride }}

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: cert-manager
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-cert-manager
storageNamespace: cozy-cert-manager
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
{{- with .Values.addons.certManager.valuesOverride }}

View File

@@ -21,7 +21,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: cilium
chart:
spec:
@@ -38,11 +37,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-cilium
storageNamespace: cozy-cilium
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:

View File

@@ -12,7 +12,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: coredns
chart:
spec:
@@ -29,11 +28,13 @@ spec:
key: super-admin.svc
targetNamespace: kube-system
storageNamespace: kube-system
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: fluxcd-operator
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-fluxcd
storageNamespace: cozy-fluxcd
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: gateway-api-crds
chart:
spec:
@@ -24,11 +23,13 @@ spec:
key: super-admin.svc
targetNamespace: kube-system
storageNamespace: kube-system
interval: 5m
timeout: 10m
install:
createNamespace: false
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
dependsOn:

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: gpu-operator
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-gpu-operator
storageNamespace: cozy-gpu-operator
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
{{- with .Values.addons.gpuOperator.valuesOverride }}

View File

@@ -26,7 +26,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: ingress-nginx
chart:
spec:
@@ -43,11 +42,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-ingress-nginx
storageNamespace: cozy-ingress-nginx
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:

View File

@@ -9,7 +9,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: cozy-monitoring-agents
chart:
spec:
@@ -26,12 +25,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-monitoring
storageNamespace: cozy-monitoring
interval: 5m
timeout: 10m
install:
createNamespace: true
timeout: "300s"
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
dependsOn:

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: velero
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-velero
storageNamespace: cozy-velero
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
{{- with .Values.addons.velero.valuesOverride }}

View File

@@ -35,7 +35,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: vertical-pod-autoscaler
chart:
spec:
@@ -52,11 +51,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-vertical-pod-autoscaler
storageNamespace: cozy-vertical-pod-autoscaler
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:

View File

@@ -7,7 +7,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: cozy-victoria-metrics-operator
chart:
spec:
@@ -24,11 +23,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-victoria-metrics-operator
storageNamespace: cozy-victoria-metrics-operator
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
dependsOn:

View File

@@ -6,7 +6,6 @@ metadata:
cozystack.io/repository: system
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
interval: 5m
releaseName: vsnap-crd
chart:
spec:
@@ -23,11 +22,14 @@ spec:
key: super-admin.svc
targetNamespace: cozy-vsnap-crd
storageNamespace: cozy-vsnap-crd
interval: 5m
timeout: 10m
install:
createNamespace: true
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
dependsOn:

View File

@@ -5,46 +5,7 @@
"addons": {
"description": "Cluster addons configuration",
"type": "object",
"default": {
"certManager": {
"enabled": false,
"valuesOverride": {}
},
"cilium": {
"valuesOverride": {}
},
"coredns": {
"valuesOverride": {}
},
"fluxcd": {
"enabled": false,
"valuesOverride": {}
},
"gatewayAPI": {
"enabled": false
},
"gpuOperator": {
"enabled": false,
"valuesOverride": {}
},
"ingressNginx": {
"enabled": false,
"exposeMethod": "Proxied",
"hosts": {},
"valuesOverride": {}
},
"monitoringAgents": {
"enabled": false,
"valuesOverride": {}
},
"velero": {
"enabled": false,
"valuesOverride": {}
},
"verticalPodAutoscaler": {
"valuesOverride": {}
}
},
"default": {},
"required": [
"certManager",
"cilium",
@@ -61,10 +22,7 @@
"certManager": {
"description": "Cert-manager: automatically creates and manages SSL/TLS certificate",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -86,9 +44,7 @@
"cilium": {
"description": "Cilium CNI plugin",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -104,9 +60,7 @@
"coredns": {
"description": "Coredns",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -122,10 +76,7 @@
"fluxcd": {
"description": "Flux CD",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -147,9 +98,7 @@
"gatewayAPI": {
"description": "Gateway API",
"type": "object",
"default": {
"enabled": false
},
"default": {},
"required": [
"enabled"
],
@@ -164,10 +113,7 @@
"gpuOperator": {
"description": "GPU-operator: NVIDIA GPU Operator",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -189,12 +135,7 @@
"ingressNginx": {
"description": "Ingress-NGINX Controller",
"type": "object",
"default": {
"enabled": false,
"exposeMethod": "Proxied",
"hosts": {},
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"exposeMethod",
@@ -234,10 +175,7 @@
"monitoringAgents": {
"description": "MonitoringAgents",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -259,10 +197,7 @@
"velero": {
"description": "Velero",
"type": "object",
"default": {
"enabled": false,
"valuesOverride": {}
},
"default": {},
"required": [
"enabled",
"valuesOverride"
@@ -284,9 +219,7 @@
"verticalPodAutoscaler": {
"description": "VerticalPodAutoscaler",
"type": "object",
"default": {
"valuesOverride": {}
},
"default": {},
"required": [
"valuesOverride"
],
@@ -304,27 +237,7 @@
"controlPlane": {
"description": "Control Plane Configuration",
"type": "object",
"default": {
"apiServer": {
"resources": {},
"resourcesPreset": "medium"
},
"controllerManager": {
"resources": {},
"resourcesPreset": "micro"
},
"konnectivity": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"replicas": 2,
"scheduler": {
"resources": {},
"resourcesPreset": "micro"
}
},
"default": {},
"required": [
"apiServer",
"controllerManager",
@@ -336,10 +249,7 @@
"apiServer": {
"description": "Control plane API server configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "medium"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -397,10 +307,7 @@
"controllerManager": {
"description": "Controller Manager configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -458,12 +365,7 @@
"konnectivity": {
"description": "Konnectivity configuration.",
"type": "object",
"default": {
"server": {
"resources": {},
"resourcesPreset": "micro"
}
},
"default": {},
"required": [
"server"
],
@@ -471,10 +373,7 @@
"server": {
"description": "Konnectivity server configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -539,10 +438,7 @@
"scheduler": {
"description": "Scheduler configuration.",
"type": "object",
"default": {
"resources": {},
"resourcesPreset": "micro"
},
"default": {},
"required": [
"resources",
"resourcesPreset"
@@ -609,7 +505,7 @@
"default": {
"md0": {
"ephemeralStorage": "20Gi",
"gpus": {},
"gpus": [],
"instanceType": "u1.medium",
"maxReplicas": 10,
"minReplicas": 0,
@@ -631,6 +527,7 @@
"properties": {
"ephemeralStorage": {
"description": "Ephemeral storage size",
"default": "20Gi",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"anyOf": [
{
@@ -645,6 +542,7 @@
"gpus": {
"description": "List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
@@ -660,19 +558,23 @@
},
"instanceType": {
"description": "Virtual machine instance type",
"type": "string"
"type": "string",
"default": "u1.medium"
},
"maxReplicas": {
"description": "Maximum amount of replicas",
"type": "integer"
"type": "integer",
"default": 10
},
"minReplicas": {
"description": "Minimum amount of replicas",
"type": "integer"
"type": "integer",
"default": 0
},
"resources": {
"description": "Resources available to each worker node",
"type": "object",
"default": {},
"properties": {
"cpu": {
"description": "CPU available to each worker node",

View File

@@ -9,17 +9,17 @@ version: "v1.33"
## @param host {string} Hostname used to access the Kubernetes cluster externally. Defaults to `<cluster-name>.<tenant-host>` when empty.
host: ""
## @param nodeGroups {map[string]node} Worker nodes configuration
## @field node {node} Node configuration
## @field node.minReplicas {int} Minimum amount of replicas
## @field node.maxReplicas {int} Maximum amount of replicas
## @field node.instanceType {string} Virtual machine instance type
## @field node.ephemeralStorage {quantity} Ephemeral storage size
## @field node.roles {[]string} List of node's roles
## @field node.resources {resources} Resources available to each worker node
## @param nodeGroups {map[string]nodeGroup} Worker nodes configuration
## @field nodeGroup {nodeGroup} Node configuration
## @field nodeGroup.minReplicas {int default=0} Minimum amount of replicas
## @field nodeGroup.maxReplicas {int default=10} Maximum amount of replicas
## @field nodeGroup.instanceType {string default="u1.medium"} Virtual machine instance type
## @field nodeGroup.ephemeralStorage {quantity default="20Gi"} Ephemeral storage size
## @field nodeGroup.roles {[]string default=[]} List of node's roles
## @field nodeGroup.resources {resources default={}} Resources available to each worker node
## @field resources.cpu {*quantity} CPU available to each worker node
## @field resources.memory {*quantity} Memory (RAM) available to each worker node
## @field node.gpus {[]gpu} List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)
## @field nodeGroup.gpus {[]gpu default={}} List of GPUs to attach (WARN: NVIDIA driver requires at least 4 GiB of RAM)
## @field gpu.name {string} Name of GPU, such as "nvidia.com/AD102GL_L40S"
##
nodeGroups:

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: mysql
description: Managed MariaDB service
icon: /logos/mariadb.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.10.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "11.0.2"

View File

@@ -5,6 +5,7 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh
image:
docker buildx build images/mariadb-backup \

View File

@@ -72,7 +72,7 @@ more details:
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of MariaDB replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each MariaDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources` | Explicit CPU and memory configuration for each MariaDB replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/mariadb-backup:0.10.0@sha256:a3789db9e9e065ff60cbac70771b4a8aa1460db3194307cf5ca5d4fe1b412b6b
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:1c0beb1b23a109b0e13727b4c73d2c74830e11cede92858ab20101b66f45a858

View File

@@ -5,16 +5,7 @@
"backup": {
"description": "Backup configuration",
"type": "object",
"default": {
"cleanupStrategy": "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m",
"enabled": false,
"resticPassword": "\u003cpassword\u003e",
"s3AccessKey": "\u003cyour-access-key\u003e",
"s3Bucket": "s3.example.org/mysql-backups",
"s3Region": "us-east-1",
"s3SecretKey": "\u003cyour-secret-key\u003e",
"schedule": "0 2 * * *"
},
"default": {},
"required": [
"cleanupStrategy",
"enabled",

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: nats
description: Managed NATS service
icon: /logos/nats.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.9.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "1.4.1"

View File

@@ -2,3 +2,4 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
../../../hack/update-crd.sh

View File

@@ -10,7 +10,7 @@ It provides a data layer for cloud native applications, IoT messaging, and micro
| Name | Description | Type | Value |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
| `replicas` | Number of replicas | `int` | `2` |
| `resources` | Explicit CPU and memory configuration for each NATS replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `{}` |
| `resources` | Explicit CPU and memory configuration for each NATS replica. When left empty, the preset defined in `resourcesPreset` is applied. | `*object` | `null` |
| `resources.cpu` | CPU available to each replica | `*quantity` | `null` |
| `resources.memory` | Memory (RAM) available to each replica | `*quantity` | `null` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. Allowed values: `nano`, `micro`, `small`, `medium`, `large`, `xlarge`, `2xlarge`. | `string` | `nano` |

View File

@@ -36,8 +36,15 @@ spec:
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
interval: 1m0s
timeout: 5m0s
interval: 5m
timeout: 10m
install:
remediation:
retries: -1
upgrade:
force: true
remediation:
retries: -1
values:
nats:
podTemplate:

View File

@@ -5,10 +5,7 @@
"config": {
"description": "NATS configuration",
"type": "object",
"default": {
"merge": {},
"resolver": {}
},
"default": {},
"properties": {
"merge": {
"description": "Additional configuration to merge into NATS config (see example)",
@@ -32,10 +29,7 @@
"jetstream": {
"description": "Jetstream configuration",
"type": "object",
"default": {
"enabled": true,
"size": "10Gi"
},
"default": {},
"required": [
"enabled",
"size"

View File

@@ -2,24 +2,6 @@ apiVersion: v2
name: postgres
description: Managed PostgreSQL service
icon: /logos/postgres.svg
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.18.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
appVersion: "16.2"

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