From 772d078649a096de595c5c99bf51cc33a579b91c Mon Sep 17 00:00:00 2001 From: Morten Olsen Date: Wed, 29 Oct 2025 23:31:30 +0100 Subject: [PATCH] stuff --- .u8.json | 60 ++ Makefile | 16 + colima.yaml | 259 +++++ packages/bootstrap/package.json | 16 +- packages/bootstrap/src/bootstrap.ts | 35 + packages/bootstrap/src/exports.ts | 1 + .../bootstrap/src/namespaces/namespaces.ts | 45 + packages/bootstrap/src/releases/releases.ts | 210 ++++ packages/bootstrap/src/repos/repos.ts | 104 ++ packages/bootstrap/src/start.ts | 7 + packages/bootstrap/src/utils/consts.ts | 3 + packages/k8s/src/core/core.ts | 1 + .../src/core/custom/flux/flux.helm-release.ts | 43 + .../custom/flux/flux.helm-release.type.ts | 987 ++++++++++++++++++ .../src/core/custom/flux/flux.helm-repo.ts | 25 + .../core/custom/flux/flux.helm-repo.types.ts | 240 +++++ packages/k8s/src/core/custom/flux/flux.ts | 2 + .../src/resources/resource/resource.custom.ts | 35 +- .../k8s/src/resources/resource/resource.ts | 36 + packages/k8s/tsconfig.tsbuildinfo | 2 +- packages/resource-backup/.gitignore | 4 + packages/resource-backup/package.json | 29 + packages/resource-backup/src/exports.ts | 0 packages/resource-backup/tsconfig.json | 9 + packages/resource-backup/vitest.config.ts | 12 + packages/resource-environment/.gitignore | 4 + packages/resource-environment/package.json | 29 + packages/resource-environment/src/exports.ts | 0 packages/resource-environment/tsconfig.json | 9 + .../resource-environment/vitest.config.ts | 12 + packages/resource-ingress/.gitignore | 4 + packages/resource-ingress/package.json | 29 + packages/resource-ingress/src/exports.ts | 0 packages/resource-ingress/tsconfig.json | 9 + packages/resource-ingress/vitest.config.ts | 12 + packages/resource-secret-generator/.gitignore | 4 + .../resource-secret-generator/package.json | 29 + .../resource-secret-generator/src/exports.ts | 0 .../resource-secret-generator/tsconfig.json | 9 + .../vitest.config.ts | 12 + packages/resource-storage/.gitignore | 4 + packages/resource-storage/package.json | 29 + packages/resource-storage/src/exports.ts | 0 .../src/resources/storage.schemas.ts | 15 + .../resource-storage/src/resources/storage.ts | 0 packages/resource-storage/tsconfig.json | 9 + packages/resource-storage/vitest.config.ts | 12 + pnpm-lock.yaml | 373 ++++++- 48 files changed, 2755 insertions(+), 30 deletions(-) create mode 100644 Makefile create mode 100644 colima.yaml create mode 100644 packages/bootstrap/src/bootstrap.ts create mode 100644 packages/bootstrap/src/namespaces/namespaces.ts create mode 100644 packages/bootstrap/src/releases/releases.ts create mode 100644 packages/bootstrap/src/repos/repos.ts create mode 100644 packages/bootstrap/src/start.ts create mode 100644 packages/bootstrap/src/utils/consts.ts create mode 100644 packages/k8s/src/core/custom/flux/flux.helm-release.ts create mode 100644 packages/k8s/src/core/custom/flux/flux.helm-release.type.ts create mode 100644 packages/k8s/src/core/custom/flux/flux.helm-repo.ts create mode 100644 packages/k8s/src/core/custom/flux/flux.helm-repo.types.ts create mode 100644 packages/k8s/src/core/custom/flux/flux.ts create mode 100644 packages/resource-backup/.gitignore create mode 100644 packages/resource-backup/package.json create mode 100644 packages/resource-backup/src/exports.ts create mode 100644 packages/resource-backup/tsconfig.json create mode 100644 packages/resource-backup/vitest.config.ts create mode 100644 packages/resource-environment/.gitignore create mode 100644 packages/resource-environment/package.json create mode 100644 packages/resource-environment/src/exports.ts create mode 100644 packages/resource-environment/tsconfig.json create mode 100644 packages/resource-environment/vitest.config.ts create mode 100644 packages/resource-ingress/.gitignore create mode 100644 packages/resource-ingress/package.json create mode 100644 packages/resource-ingress/src/exports.ts create mode 100644 packages/resource-ingress/tsconfig.json create mode 100644 packages/resource-ingress/vitest.config.ts create mode 100644 packages/resource-secret-generator/.gitignore create mode 100644 packages/resource-secret-generator/package.json create mode 100644 packages/resource-secret-generator/src/exports.ts create mode 100644 packages/resource-secret-generator/tsconfig.json create mode 100644 packages/resource-secret-generator/vitest.config.ts create mode 100644 packages/resource-storage/.gitignore create mode 100644 packages/resource-storage/package.json create mode 100644 packages/resource-storage/src/exports.ts create mode 100644 packages/resource-storage/src/resources/storage.schemas.ts create mode 100644 packages/resource-storage/src/resources/storage.ts create mode 100644 packages/resource-storage/tsconfig.json create mode 100644 packages/resource-storage/vitest.config.ts diff --git a/.u8.json b/.u8.json index 9c905b1..ceb6abe 100644 --- a/.u8.json +++ b/.u8.json @@ -106,6 +106,66 @@ "packageVersion": "1.0.0", "packageName": "resource-cloudflare" } + }, + { + "timestamp": "2025-10-25T21:36:15.179Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "storage-provisioner" + } + }, + { + "timestamp": "2025-10-25T21:38:51.518Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "resource-secret-generator" + } + }, + { + "timestamp": "2025-10-25T21:39:26.176Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "resource-backup" + } + }, + { + "timestamp": "2025-10-25T21:40:26.879Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "resource-environment" + } + }, + { + "timestamp": "2025-10-25T21:41:03.051Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "resource-ingress" + } + }, + { + "timestamp": "2025-10-29T18:39:49.880Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/box-", + "packageVersion": "1.0.0", + "packageName": "resource-storage" + } } ] } \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9148b50 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: dev-recreate + +dev-destroy: + colima delete --force + rm -rf ~/.colima/homelab/ + +dev-recreate: dev-destroy + colima start \ + --profile homelab \ + --kubernetes \ + --memory 4 \ + --k3s-arg='"--disable=traefik,local-storage"' \ + --k3s-arg='"--disable-helm-controller"' + #--k3s-arg='"--flannel-backend=none"' + flux install --components="source-controller,helm-controller" + diff --git a/colima.yaml b/colima.yaml new file mode 100644 index 0000000..1adabf2 --- /dev/null +++ b/colima.yaml @@ -0,0 +1,259 @@ +# ============================================================================================ # +# To abort, delete the contents of this file including the comments and save as an empty file +# ============================================================================================ # + +# Number of CPUs to be allocated to the virtual machine. +# Default: 2 +cpu: 2 + +# Size of the disk in GiB to be allocated to the virtual machine for container data. +# NOTE: value can only be increased after virtual machine has been created. +# +# Default: 100 +disk: 100 + +# Size of the memory in GiB to be allocated to the virtual machine. +# Default: 2 +memory: 4 + +# Architecture of the virtual machine (x86_64, aarch64, host). +# +# NOTE: value cannot be changed after virtual machine is created. +# Default: host +arch: aarch64 + +# Container runtime to be used (docker, containerd). +# +# NOTE: value cannot be changed after virtual machine is created. +# Default: docker +runtime: docker + +# Set custom hostname for the virtual machine. +# Default: colima +# colima-profile_name for other profiles +hostname: colima + +# Kubernetes configuration for the virtual machine. +kubernetes: + # Enable kubernetes. + # Default: false + enabled: true + + # Kubernetes version to use. + # This needs to exactly match a k3s version https://github.com/k3s-io/k3s/releases + # Default: latest stable release + version: v1.33.4+k3s1 + + # Additional args to pass to k3s https://docs.k3s.io/cli/server + # Default: traefik is disabled + k3sArgs: + - --disable=servicelb,traefik,local-storage + - --flannel-backend=none + + # Kubernetes port to listen on + # A common port is 6443, though left unbound to ensure no port conflicts + # Default: pick random unbound port + port: 0 + +# Auto-activate on the Host for client access. +# Setting to true does the following on startup +# - sets as active Docker context (for Docker runtime). +# - sets as active Kubernetes context (if Kubernetes is enabled). +# - sets as active Incus remote (for Incus runtime). +# Default: true +autoActivate: true + +# Network configurations for the virtual machine. +network: + # Assign reachable IP address to the virtual machine. + # NOTE: this is currently macOS only and ignored on Linux. + # Default: false + address: false + + # Network mode for the virtual machine (shared, bridged). + # NOTE: this is currently macOS only and ignored on Linux. + # Default: shared + mode: shared + + # Network interface to use for bridged mode. + # This is only used when mode is set to bridged. + # NOTE: this is currently macOS only and ignored on Linux. + # Default: en0 + interface: en0 + + # Use the assigned IP address as the preferred route for the VM. + # Note: this only has an effect when `address` is set to true. + # Default: false + preferredRoute: false + + # Custom DNS resolvers for the virtual machine. + # + # EXAMPLE + # dns: [8.8.8.8, 1.1.1.1] + # + # Default: [] + dns: [] + + # DNS hostnames to resolve to custom targets using the internal resolver. + # This setting has no effect if a custom DNS resolver list is supplied above. + # It does not configure the /etc/hosts files of any machine or container. + # The value can be an IP address or another host. + # + # EXAMPLE + # dnsHosts: + # example.com: 1.2.3.4 + dnsHosts: {} + + # Replicate host IP addresses in the VM. This enables port forwarding to specific + # host IP addresses. + # e.g. `docker run --port 10.0.1.2:8080:8080 alpine` would only forward to the + # specified IP address. + # + # Default: false + hostAddresses: false + +# ===================================================================== # +# ADVANCED CONFIGURATION +# ===================================================================== # + +# Forward the host's SSH agent to the virtual machine. +# Default: false +forwardAgent: false + +# Docker daemon configuration that maps directly to daemon.json. +# https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file. +# NOTE: some settings may affect Colima's ability to start docker. e.g. `hosts`. +# +# EXAMPLE - disable buildkit +# docker: +# features: +# buildkit: false +# +# EXAMPLE - add insecure registries +# docker: +# insecure-registries: +# - myregistry.com:5000 +# - host.docker.internal:5000 +# +# Colima default behaviour: buildkit enabled +# Default: {} +docker: {} + +# Virtual Machine type (qemu, vz) +# NOTE: this is macOS 13 only. For Linux and macOS <13.0, qemu is always used. +# +# vz is macOS virtualization framework and requires macOS 13 +# +# NOTE: value cannot be changed after virtual machine is created. +# Default: qemu +vmType: vz + +# Port forwarder for the virtual machine (ssh, grpc). +# ssh is more stable but supports only TCP. +# grpc supports both TCP and UDP, but is experimental. +# +# Default: ssh +portForwarder: ssh + +# Utilise rosetta for amd64 emulation (requires m1 mac and vmType `vz`) +# Default: false +rosetta: false + +# Enable foreign architecture emulation via binfmt (e.g. amd64 on arm64, arm64 on amd64) +# Default: true +binfmt: true + +# Enable nested virtualization for the virtual machine (requires m3 mac and vmType `vz`) +# Default: false +nestedVirtualization: false + +# Volume mount driver for the virtual machine (virtiofs, 9p, sshfs). +# +# virtiofs is limited to macOS and vmType `vz`. It is the fastest of the options. +# +# 9p is the recommended and the most stable option for vmType `qemu`. +# +# sshfs is faster than 9p but the least reliable of the options (when there are lots +# of concurrent reads or writes). +# +# NOTE: value cannot be changed after virtual machine is created. +# Default: virtiofs (for vz), sshfs (for qemu) +mountType: virtiofs + +# Propagate inotify file events to the VM. +# NOTE: this is experimental. +mountInotify: true + +# The CPU type for the virtual machine (requires vmType `qemu`). +# Options available for host emulation can be checked with: `qemu-system-$(arch) -cpu help`. +# Instructions are also supported by appending to the cpu type e.g. "qemu64,+ssse3". +# Default: host +cpuType: "" + +# Custom provision scripts for the virtual machine. +# Provisioning scripts are executed on startup and therefore needs to be idempotent. +# +# EXAMPLE - script executed as root +# provision: +# - mode: system +# script: apt-get install htop vim +# +# EXAMPLE - script executed as user +# provision: +# - mode: user +# script: | +# [ -f ~/.provision ] && exit 0; +# echo provisioning as $USER... +# touch ~/.provision +# +# Default: [] +provision: [] + +# Modify ~/.ssh/config automatically to include a SSH config for the virtual machine. +# SSH config will still be generated in $COLIMA_HOME/ssh_config regardless. +# Default: true +sshConfig: true + +# The port number for the SSH server for the virtual machine. +# When set to 0, a random available port is used. +# +# Default: 0 +sshPort: 0 + +# Configure volume mounts for the virtual machine. +# Colima mounts user's home directory by default to provide a familiar +# user experience. +# +# EXAMPLE +# mounts: +# - location: ~/secrets +# writable: false +# - location: ~/projects +# writable: true +# +# Colima default behaviour: $HOME is mounted as writable. +# Default: [] +mounts: [] + +# Specify a custom disk image for the virtual machine. +# When not specified, Colima downloads an appropriate disk image from Github at +# https://github.com/abiosoft/colima-core/releases. +# The file path to a custom disk image can be specified to override the behaviour. +# +# Default: "" +diskImage: "" + +# Size of the disk in GiB for the root filesystem of the virtual machine. +# This value is ignored if no runtime is in use. i.e. `none` runtime. +# Default: 20 +rootDisk: 20 + +# Environment variables for the virtual machine. +# +# EXAMPLE +# env: +# KEY: value +# ANOTHER_KEY: another value +# +# Default: {} +env: {} diff --git a/packages/bootstrap/package.json b/packages/bootstrap/package.json index a39f635..7120d8f 100644 --- a/packages/bootstrap/package.json +++ b/packages/bootstrap/package.json @@ -14,16 +14,18 @@ ".": "./dist/exports.js" }, "devDependencies": { + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*", "@types/node": "catalog:", "@vitest/coverage-v8": "catalog:", + "tsx": "^4.20.6", "typescript": "catalog:", - "vitest": "catalog:", - "@morten-olsen/box-configs": "workspace:*", - "@morten-olsen/box-tests": "workspace:*" + "vitest": "catalog:" + }, + "dependencies": { + "@morten-olsen/box-k8s": "workspace:*", + "@morten-olsen/box-utils": "workspace:*" }, "name": "@morten-olsen/box-bootstrap", - "version": "1.0.0", - "imports": { - "#root/*": "./src/*" - } + "version": "1.0.0" } diff --git a/packages/bootstrap/src/bootstrap.ts b/packages/bootstrap/src/bootstrap.ts new file mode 100644 index 0000000..6f3ebe6 --- /dev/null +++ b/packages/bootstrap/src/bootstrap.ts @@ -0,0 +1,35 @@ +import type { Services } from '@morten-olsen/box-utils/services'; +import { HelmRelease, HelmRepo, Namespace, ResourceService } from '@morten-olsen/box-k8s'; + +import { NamespaceService } from './namespaces/namespaces.js'; +import { ReleaseService } from './releases/releases.js'; +import { RepoService } from './repos/repos.js'; + +class BootstrapService { + #services: Services; + + constructor(services: Services) { + this.#services = services; + } + public get namespaces() { + return this.#services.get(NamespaceService); + } + + public get repos() { + return this.#services.get(RepoService); + } + + public get releases() { + return this.#services.get(ReleaseService); + } + + public ensure = async () => { + const resourceService = this.#services.get(ResourceService); + await resourceService.register(Namespace, HelmRepo, HelmRelease); + await this.namespaces.ensure(); + await this.repos.ensure(); + await this.releases.ensure(); + }; +} + +export { BootstrapService }; diff --git a/packages/bootstrap/src/exports.ts b/packages/bootstrap/src/exports.ts index e69de29..d1fc8f2 100644 --- a/packages/bootstrap/src/exports.ts +++ b/packages/bootstrap/src/exports.ts @@ -0,0 +1 @@ +export { BootstrapService } from './bootstrap.js'; diff --git a/packages/bootstrap/src/namespaces/namespaces.ts b/packages/bootstrap/src/namespaces/namespaces.ts new file mode 100644 index 0000000..a1307eb --- /dev/null +++ b/packages/bootstrap/src/namespaces/namespaces.ts @@ -0,0 +1,45 @@ +import { Namespace, ResourceService } from '@morten-olsen/box-k8s'; +import type { Services } from '@morten-olsen/box-utils/services'; + +import { NAMESPACE } from '../utils/consts.js'; + +class NamespaceService { + #homelab: Namespace; + #istioSystem: Namespace; + #certManager: Namespace; + + constructor(services: Services) { + const resourceService = services.get(ResourceService); + this.#homelab = resourceService.get(Namespace, NAMESPACE); + this.#istioSystem = resourceService.get(Namespace, 'istio-system'); + this.#certManager = resourceService.get(Namespace, 'cert-manager'); + + this.#homelab.on('changed', this.ensure); + this.#istioSystem.on('changed', this.ensure); + this.#certManager.on('changed', this.ensure); + } + + public get homelab() { + return this.#homelab; + } + public get istioSystem() { + return this.#istioSystem; + } + public get certManager() { + return this.#certManager; + } + + public ensure = async () => { + await this.#homelab.ensure({ + metadata: { + labels: { + 'istio-injection': 'enabled', + }, + }, + }); + await this.#istioSystem.ensure({}); + await this.#certManager.ensure({}); + }; +} + +export { NamespaceService }; diff --git a/packages/bootstrap/src/releases/releases.ts b/packages/bootstrap/src/releases/releases.ts new file mode 100644 index 0000000..4778133 --- /dev/null +++ b/packages/bootstrap/src/releases/releases.ts @@ -0,0 +1,210 @@ +import type { Services } from '@morten-olsen/box-utils/services'; +import { HelmRelease, ResourceService } from '@morten-olsen/box-k8s'; + +import { NamespaceService } from '../namespaces/namespaces.js'; +import { RepoService } from '../repos/repos.js'; +import { NAMESPACE } from '../utils/consts.js'; + +class ReleaseService { + #services: Services; + #certManager: HelmRelease; + #istioBase: HelmRelease; + #istiod: HelmRelease; + #istioGateway: HelmRelease; + #trivy: HelmRelease; + #kyverno: HelmRelease; + #cloudnativepg: HelmRelease; + + constructor(services: Services) { + this.#services = services; + const resourceService = services.get(ResourceService); + this.#certManager = resourceService.get(HelmRelease, 'cert-manager', NAMESPACE); + this.#istioBase = resourceService.get(HelmRelease, 'istio-base', NAMESPACE); + this.#istiod = resourceService.get(HelmRelease, 'istiod', NAMESPACE); + this.#istioGateway = resourceService.get(HelmRelease, 'istio-gateway', NAMESPACE); + this.#trivy = resourceService.get(HelmRelease, 'trivy', NAMESPACE); + this.#kyverno = resourceService.get(HelmRelease, 'kyverno', NAMESPACE); + this.#cloudnativepg = resourceService.get(HelmRelease, 'cloudnative-pg', NAMESPACE); + + this.#certManager.on('changed', this.ensure); + this.#istioBase.on('changed', this.ensure); + this.#istiod.on('changed', this.ensure); + this.#istioGateway.on('changed', this.ensure); + this.#trivy.on('changed', this.ensure); + this.#kyverno.on('changed', this.ensure); + this.#cloudnativepg.on('changed', this.ensure); + } + + public get certManager() { + return this.#certManager; + } + + public get istioBase() { + return this.#istioBase; + } + + public get istiod() { + return this.#istiod; + } + + public get trivy() { + return this.#trivy; + } + + public get kyverno() { + return this.#kyverno; + } + + public get cloudnativepg() { + return this.#cloudnativepg; + } + + public ensure = async () => { + const namespaceService = this.#services.get(NamespaceService); + const repoService = this.#services.get(RepoService); + await this.#certManager.ensure({ + spec: { + targetNamespace: namespaceService.certManager.name, + interval: '1h', + values: { + installCRDs: true, + }, + chart: { + spec: { + chart: 'cert-manager', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.jetstack.name, + namespace: repoService.jetstack.namespace, + }, + }, + }, + }, + }); + await this.#istioBase.ensure({ + spec: { + targetNamespace: namespaceService.istioSystem.name, + interval: '1h', + values: { + defaultRevision: 'default', + profile: 'ambient', + }, + chart: { + spec: { + chart: 'base', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.istio.name, + namespace: repoService.istio.namespace, + }, + }, + }, + }, + }); + await this.#istiod.ensure({ + spec: { + targetNamespace: namespaceService.istioSystem.name, + interval: '1h', + dependsOn: [ + { + name: this.#istioBase.name, + namespace: this.#istioBase.namespace, + }, + ], + chart: { + spec: { + chart: 'istiod', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.istio.name, + namespace: repoService.istio.namespace, + }, + }, + }, + }, + }); + await this.#istioGateway.ensure({ + spec: { + targetNamespace: NAMESPACE, + interval: '1h', + dependsOn: [ + { + name: this.#istioBase.name, + namespace: this.#istioBase.namespace, + }, + { + name: this.#istiod.name, + namespace: this.#istiod.namespace, + }, + ], + chart: { + spec: { + chart: 'gateway', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.istio.name, + namespace: repoService.istio.namespace, + }, + }, + }, + }, + }); + await this.#trivy.ensure({ + spec: { + targetNamespace: NAMESPACE, + interval: '1h', + chart: { + spec: { + chart: 'trivy-operator', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.aqua.name, + namespace: repoService.aqua.namespace, + }, + }, + }, + }, + }); + await this.#kyverno.ensure({ + spec: { + targetNamespace: NAMESPACE, + interval: '1h', + chart: { + spec: { + chart: 'kyverno', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.kyverno.name, + namespace: repoService.kyverno.namespace, + }, + }, + }, + }, + }); + await this.#cloudnativepg.ensure({ + spec: { + targetNamespace: NAMESPACE, + interval: '1h', + chart: { + spec: { + chart: 'cloudnative-pg', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: repoService.cloudnativepg.name, + namespace: repoService.cloudnativepg.namespace, + }, + }, + }, + }, + }); + }; +} + +export { ReleaseService }; diff --git a/packages/bootstrap/src/repos/repos.ts b/packages/bootstrap/src/repos/repos.ts new file mode 100644 index 0000000..c4a557e --- /dev/null +++ b/packages/bootstrap/src/repos/repos.ts @@ -0,0 +1,104 @@ +import type { Services } from '@morten-olsen/box-utils/services'; +import { HelmRepo, ResourceService } from '@morten-olsen/box-k8s'; + +import { NAMESPACE } from '../utils/consts.js'; + +class RepoService { + #jetstack: HelmRepo; + #istio: HelmRepo; + #authentik: HelmRepo; + #cloudflare: HelmRepo; + #argo: HelmRepo; + #aqua: HelmRepo; + #kyverno: HelmRepo; + #cloudnativepg: HelmRepo; + + constructor(services: Services) { + const resourceService = services.get(ResourceService); + this.#jetstack = resourceService.get(HelmRepo, 'jetstack', NAMESPACE); + this.#istio = resourceService.get(HelmRepo, 'istio', NAMESPACE); + this.#authentik = resourceService.get(HelmRepo, 'authentik', NAMESPACE); + this.#cloudflare = resourceService.get(HelmRepo, 'cloudflare', NAMESPACE); + this.#argo = resourceService.get(HelmRepo, 'argo', NAMESPACE); + this.#aqua = resourceService.get(HelmRepo, 'aqua', NAMESPACE); + this.#kyverno = resourceService.get(HelmRepo, 'kyverno', NAMESPACE); + this.#cloudnativepg = resourceService.get(HelmRepo, 'cloudnative-pg', NAMESPACE); + + this.#jetstack.on('changed', this.ensure); + this.#istio.on('changed', this.ensure); + this.#authentik.on('changed', this.ensure); + this.#cloudflare.on('changed', this.ensure); + this.#argo.on('changed', this.ensure); + this.#aqua.on('changed', this.ensure); + this.#kyverno.on('changed', this.ensure); + this.#cloudnativepg.on('changed', this.ensure); + } + + public get jetstack() { + return this.#jetstack; + } + + public get istio() { + return this.#istio; + } + + public get authentik() { + return this.#authentik; + } + + public get cloudflare() { + return this.#cloudflare; + } + + public get argo() { + return this.#argo; + } + + public get aqua() { + return this.#aqua; + } + + public get kyverno() { + return this.#kyverno; + } + + public get cloudnativepg() { + return this.#cloudnativepg; + } + + public ensure = async () => { + await this.#jetstack.set({ + url: 'https://charts.jetstack.io', + }); + + await this.#istio.set({ + url: 'https://istio-release.storage.googleapis.com/charts', + }); + + await this.#authentik.set({ + url: 'https://charts.goauthentik.io', + }); + + await this.#cloudflare.set({ + url: 'https://cloudflare.github.io/helm-charts', + }); + + await this.#argo.set({ + url: 'https://argoproj.github.io/argo-helm', + }); + + await this.#aqua.set({ + url: 'https://aquasecurity.github.io/helm-charts/', + }); + + await this.#kyverno.set({ + url: 'https://kyverno.github.io/kyverno/', + }); + + await this.#cloudnativepg.set({ + url: 'https://cloudnative-pg.github.io/charts', + }); + }; +} + +export { RepoService }; diff --git a/packages/bootstrap/src/start.ts b/packages/bootstrap/src/start.ts new file mode 100644 index 0000000..f8f010b --- /dev/null +++ b/packages/bootstrap/src/start.ts @@ -0,0 +1,7 @@ +import { Services } from '@morten-olsen/box-utils/services'; + +import { BootstrapService } from './bootstrap.js'; + +const services = new Services(); +const bootstrap = services.get(BootstrapService); +await bootstrap.ensure(); diff --git a/packages/bootstrap/src/utils/consts.ts b/packages/bootstrap/src/utils/consts.ts new file mode 100644 index 0000000..8a8ffbc --- /dev/null +++ b/packages/bootstrap/src/utils/consts.ts @@ -0,0 +1,3 @@ +const NAMESPACE = 'homelab'; + +export { NAMESPACE }; diff --git a/packages/k8s/src/core/core.ts b/packages/k8s/src/core/core.ts index 277a1b2..00a7d60 100644 --- a/packages/k8s/src/core/core.ts +++ b/packages/k8s/src/core/core.ts @@ -8,3 +8,4 @@ import { StatefulSet } from './core.stateful-set.js'; import { StorageClass } from './core.storage-class.js'; export { CRD, Deployment, Namespace, PersistentVolume, Secret, Service, StatefulSet, StorageClass }; +export * from './custom/flux/flux.js'; diff --git a/packages/k8s/src/core/custom/flux/flux.helm-release.ts b/packages/k8s/src/core/custom/flux/flux.helm-release.ts new file mode 100644 index 0000000..29dde7d --- /dev/null +++ b/packages/k8s/src/core/custom/flux/flux.helm-release.ts @@ -0,0 +1,43 @@ +import type { KubernetesObject } from '@kubernetes/client-node'; + +import { Resource } from '../../../resources/resources.js'; + +import type { K8SHelmReleaseV2 } from './flux.helm-release.type.js'; + +type SetOptions = { + namespace?: string; + values?: Record; + chart: { + name: string; + namespace?: string; + }; +}; + +class HelmRelease extends Resource { + public static readonly apiVersion = 'helm.toolkit.fluxcd.io/v2'; + public static readonly kind = 'HelmRelease'; + + public set = async (options: SetOptions) => { + return await this.ensure({ + spec: { + targetNamespace: options.namespace, + interval: '1h', + values: options.values, + chart: { + spec: { + chart: 'cert-manager', + version: 'v1.18.2', + sourceRef: { + apiVersion: 'source.toolkit.fluxcd.io/v1', + kind: 'HelmRepository', + name: options.chart.name, + namespace: options.chart.namespace, + }, + }, + }, + }, + }); + }; +} + +export { HelmRelease }; diff --git a/packages/k8s/src/core/custom/flux/flux.helm-release.type.ts b/packages/k8s/src/core/custom/flux/flux.helm-release.type.ts new file mode 100644 index 0000000..73dc858 --- /dev/null +++ b/packages/k8s/src/core/custom/flux/flux.helm-release.type.ts @@ -0,0 +1,987 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * HelmRelease is the Schema for the helmreleases API + */ +export interface K8SHelmReleaseV2 { + /** + * APIVersion defines the versioned schema of this representation of an object. + * Servers should convert recognized schemas to the latest internal value, and + * may reject unrecognized values. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + */ + apiVersion?: string; + /** + * Kind is a string value representing the REST resource this object represents. + * Servers may infer this from the endpoint the client submits requests to. + * Cannot be updated. + * In CamelCase. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind?: string; + metadata?: {}; + /** + * HelmReleaseSpec defines the desired state of a Helm release. + */ + spec?: { + /** + * Chart defines the template of the v1.HelmChart that should be created + * for this HelmRelease. + */ + chart?: { + /** + * ObjectMeta holds the template for metadata like labels and annotations. + */ + metadata?: { + /** + * Annotations is an unstructured key value map stored with a resource that may be + * set by external tools to store and retrieve arbitrary metadata. They are not + * queryable and should be preserved when modifying objects. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + */ + annotations?: { + [k: string]: string; + }; + /** + * Map of string keys and values that can be used to organize and categorize + * (scope and select) objects. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + */ + labels?: { + [k: string]: string; + }; + }; + /** + * Spec holds the template for the v1.HelmChartSpec for this HelmRelease. + */ + spec: { + /** + * The name or path the Helm chart is available at in the SourceRef. + */ + chart: string; + /** + * IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing. + */ + ignoreMissingValuesFiles?: boolean; + /** + * Interval at which to check the v1.Source for updates. Defaults to + * 'HelmReleaseSpec.Interval'. + */ + interval?: string; + /** + * Determines what enables the creation of a new artifact. Valid values are + * ('ChartVersion', 'Revision'). + * See the documentation of the values for an explanation on their behavior. + * Defaults to ChartVersion when omitted. + */ + reconcileStrategy?: string; + /** + * The name and namespace of the v1.Source the chart is available at. + */ + sourceRef: { + /** + * APIVersion of the referent. + */ + apiVersion?: string; + /** + * Kind of the referent. + */ + kind: string; + /** + * Name of the referent. + */ + name: string; + /** + * Namespace of the referent. + */ + namespace?: string; + }; + /** + * Alternative list of values files to use as the chart values (values.yaml + * is not included by default), expected to be a relative path in the SourceRef. + * Values files are merged in the order of this list with the last file overriding + * the first. Ignored when omitted. + */ + valuesFiles?: string[]; + /** + * Verify contains the secret name containing the trusted public keys + * used to verify the signature and specifies which provider to use to check + * whether OCI image is authentic. + * This field is only supported for OCI sources. + * Chart dependencies, which are not bundled in the umbrella chart artifact, + * are not verified. + */ + verify?: { + /** + * Provider specifies the technology used to sign the OCI Helm chart. + */ + provider: string; + /** + * SecretRef specifies the Kubernetes Secret containing the + * trusted public keys. + */ + secretRef?: { + /** + * Name of the referent. + */ + name: string; + }; + }; + /** + * Version semver expression, ignored for charts from v1.GitRepository and + * v1beta2.Bucket sources. Defaults to latest when omitted. + */ + version?: string; + }; + }; + /** + * ChartRef holds a reference to a source controller resource containing the + * Helm chart artifact. + */ + chartRef?: { + /** + * APIVersion of the referent. + */ + apiVersion?: string; + /** + * Kind of the referent. + */ + kind: string; + /** + * Name of the referent. + */ + name: string; + /** + * Namespace of the referent, defaults to the namespace of the Kubernetes + * resource object that contains the reference. + */ + namespace?: string; + }; + /** + * DependsOn may contain a meta.NamespacedObjectReference slice with + * references to HelmRelease resources that must be ready before this HelmRelease + * can be reconciled. + */ + dependsOn?: { + /** + * Name of the referent. + */ + name: string; + /** + * Namespace of the referent, when not specified it acts as LocalObjectReference. + */ + namespace?: string; + }[]; + /** + * DriftDetection holds the configuration for detecting and handling + * differences between the manifest in the Helm storage and the resources + * currently existing in the cluster. + */ + driftDetection?: { + /** + * Ignore contains a list of rules for specifying which changes to ignore + * during diffing. + */ + ignore?: { + /** + * Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from + * consideration in a Kubernetes object. + */ + paths: string[]; + /** + * Target is a selector for specifying Kubernetes objects to which this + * rule applies. + * If Target is not set, the Paths will be ignored for all Kubernetes + * objects within the manifest of the Helm release. + */ + target?: { + /** + * AnnotationSelector is a string that follows the label selection expression + * https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + * It matches with the resource annotations. + */ + annotationSelector?: string; + /** + * Group is the API group to select resources from. + * Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + group?: string; + /** + * Kind of the API Group to select resources from. + * Together with Group and Version it is capable of unambiguously + * identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + kind?: string; + /** + * LabelSelector is a string that follows the label selection expression + * https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + * It matches with the resource labels. + */ + labelSelector?: string; + /** + * Name to match resources with. + */ + name?: string; + /** + * Namespace to select resources from. + */ + namespace?: string; + /** + * Version of the API Group to select resources from. + * Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + version?: string; + }; + }[]; + /** + * Mode defines how differences should be handled between the Helm manifest + * and the manifest currently applied to the cluster. + * If not explicitly set, it defaults to DiffModeDisabled. + */ + mode?: string; + }; + /** + * Install holds the configuration for Helm install actions for this HelmRelease. + */ + install?: { + /** + * CRDs upgrade CRDs from the Helm Chart's crds directory according + * to the CRD upgrade policy provided here. Valid values are `Skip`, + * `Create` or `CreateReplace`. Default is `Create` and if omitted + * CRDs are installed but not updated. + * + * Skip: do neither install nor replace (update) any CRDs. + * + * Create: new CRDs are created, existing CRDs are neither updated nor deleted. + * + * CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + * but not deleted. + * + * By default, CRDs are applied (installed) during Helm install action. + * With this option users can opt in to CRD replace existing CRDs on Helm + * install actions, which is not (yet) natively supported by Helm. + * https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + */ + crds?: string; + /** + * CreateNamespace tells the Helm install action to create the + * HelmReleaseSpec.TargetNamespace if it does not exist yet. + * On uninstall, the namespace will not be garbage collected. + */ + createNamespace?: boolean; + /** + * DisableHooks prevents hooks from running during the Helm install action. + */ + disableHooks?: boolean; + /** + * DisableOpenAPIValidation prevents the Helm install action from validating + * rendered templates against the Kubernetes OpenAPI Schema. + */ + disableOpenAPIValidation?: boolean; + /** + * DisableSchemaValidation prevents the Helm install action from validating + * the values against the JSON Schema. + */ + disableSchemaValidation?: boolean; + /** + * DisableTakeOwnership disables taking ownership of existing resources + * during the Helm install action. Defaults to false. + */ + disableTakeOwnership?: boolean; + /** + * DisableWait disables the waiting for resources to be ready after a Helm + * install has been performed. + */ + disableWait?: boolean; + /** + * DisableWaitForJobs disables waiting for jobs to complete after a Helm + * install has been performed. + */ + disableWaitForJobs?: boolean; + /** + * Remediation holds the remediation configuration for when the Helm install + * action for the HelmRelease fails. The default is to not perform any action. + */ + remediation?: { + /** + * IgnoreTestFailures tells the controller to skip remediation when the Helm + * tests are run after an install action but fail. Defaults to + * 'Test.IgnoreFailures'. + */ + ignoreTestFailures?: boolean; + /** + * RemediateLastFailure tells the controller to remediate the last failure, when + * no retries remain. Defaults to 'false'. + */ + remediateLastFailure?: boolean; + /** + * Retries is the number of retries that should be attempted on failures before + * bailing. Remediation, using an uninstall, is performed between each attempt. + * Defaults to '0', a negative integer equals to unlimited retries. + */ + retries?: number; + }; + /** + * Replace tells the Helm install action to re-use the 'ReleaseName', but only + * if that name is a deleted release which remains in the history. + */ + replace?: boolean; + /** + * SkipCRDs tells the Helm install action to not install any CRDs. By default, + * CRDs are installed if not already present. + * + * Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. + */ + skipCRDs?: boolean; + /** + * Timeout is the time to wait for any individual Kubernetes operation (like + * Jobs for hooks) during the performance of a Helm install action. Defaults to + * 'HelmReleaseSpec.Timeout'. + */ + timeout?: string; + }; + /** + * Interval at which to reconcile the Helm release. + */ + interval: string; + /** + * KubeConfig for reconciling the HelmRelease on a remote cluster. + * When used in combination with HelmReleaseSpec.ServiceAccountName, + * forces the controller to act on behalf of that Service Account at the + * target cluster. + * If the --default-service-account flag is set, its value will be used as + * a controller level fallback for when HelmReleaseSpec.ServiceAccountName + * is empty. + */ + kubeConfig?: { + /** + * SecretRef holds the name of a secret that contains a key with + * the kubeconfig file as the value. If no key is set, the key will default + * to 'value'. + * It is recommended that the kubeconfig is self-contained, and the secret + * is regularly updated if credentials such as a cloud-access-token expire. + * Cloud specific `cmd-path` auth helpers will not function without adding + * binaries and credentials to the Pod that is responsible for reconciling + * Kubernetes resources. + */ + secretRef: { + /** + * Key in the Secret, when not specified an implementation-specific default key is used. + */ + key?: string; + /** + * Name of the Secret. + */ + name: string; + }; + }; + /** + * MaxHistory is the number of revisions saved by Helm for this HelmRelease. + * Use '0' for an unlimited number of revisions; defaults to '5'. + */ + maxHistory?: number; + /** + * PersistentClient tells the controller to use a persistent Kubernetes + * client for this release. When enabled, the client will be reused for the + * duration of the reconciliation, instead of being created and destroyed + * for each (step of a) Helm action. + * + * This can improve performance, but may cause issues with some Helm charts + * that for example do create Custom Resource Definitions during installation + * outside Helm's CRD lifecycle hooks, which are then not observed to be + * available by e.g. post-install hooks. + * + * If not set, it defaults to true. + */ + persistentClient?: boolean; + /** + * PostRenderers holds an array of Helm PostRenderers, which will be applied in order + * of their definition. + */ + postRenderers?: { + /** + * Kustomization to apply as PostRenderer. + */ + kustomize?: { + /** + * Images is a list of (image name, new name, new tag or digest) + * for changing image names, tags or digests. This can also be achieved with a + * patch, but this operator is simpler to specify. + */ + images?: { + /** + * Digest is the value used to replace the original image tag. + * If digest is present NewTag value is ignored. + */ + digest?: string; + /** + * Name is a tag-less image name. + */ + name: string; + /** + * NewName is the value used to replace the original name. + */ + newName?: string; + /** + * NewTag is the value used to replace the original tag. + */ + newTag?: string; + }[]; + /** + * Strategic merge and JSON patches, defined as inline YAML objects, + * capable of targeting objects based on kind, label and annotation selectors. + */ + patches?: { + /** + * Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with + * an array of operation objects. + */ + patch: string; + /** + * Target points to the resources that the patch document should be applied to. + */ + target?: { + /** + * AnnotationSelector is a string that follows the label selection expression + * https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + * It matches with the resource annotations. + */ + annotationSelector?: string; + /** + * Group is the API group to select resources from. + * Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + group?: string; + /** + * Kind of the API Group to select resources from. + * Together with Group and Version it is capable of unambiguously + * identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + kind?: string; + /** + * LabelSelector is a string that follows the label selection expression + * https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + * It matches with the resource labels. + */ + labelSelector?: string; + /** + * Name to match resources with. + */ + name?: string; + /** + * Namespace to select resources from. + */ + namespace?: string; + /** + * Version of the API Group to select resources from. + * Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + */ + version?: string; + }; + }[]; + }; + }[]; + /** + * ReleaseName used for the Helm release. Defaults to a composition of + * '[TargetNamespace-]Name'. + */ + releaseName?: string; + /** + * Rollback holds the configuration for Helm rollback actions for this HelmRelease. + */ + rollback?: { + /** + * CleanupOnFail allows deletion of new resources created during the Helm + * rollback action when it fails. + */ + cleanupOnFail?: boolean; + /** + * DisableHooks prevents hooks from running during the Helm rollback action. + */ + disableHooks?: boolean; + /** + * DisableWait disables the waiting for resources to be ready after a Helm + * rollback has been performed. + */ + disableWait?: boolean; + /** + * DisableWaitForJobs disables waiting for jobs to complete after a Helm + * rollback has been performed. + */ + disableWaitForJobs?: boolean; + /** + * Force forces resource updates through a replacement strategy. + */ + force?: boolean; + /** + * Recreate performs pod restarts for the resource if applicable. + */ + recreate?: boolean; + /** + * Timeout is the time to wait for any individual Kubernetes operation (like + * Jobs for hooks) during the performance of a Helm rollback action. Defaults to + * 'HelmReleaseSpec.Timeout'. + */ + timeout?: string; + }; + /** + * The name of the Kubernetes service account to impersonate + * when reconciling this HelmRelease. + */ + serviceAccountName?: string; + /** + * StorageNamespace used for the Helm storage. + * Defaults to the namespace of the HelmRelease. + */ + storageNamespace?: string; + /** + * Suspend tells the controller to suspend reconciliation for this HelmRelease, + * it does not apply to already started reconciliations. Defaults to false. + */ + suspend?: boolean; + /** + * TargetNamespace to target when performing operations for the HelmRelease. + * Defaults to the namespace of the HelmRelease. + */ + targetNamespace?: string; + /** + * Test holds the configuration for Helm test actions for this HelmRelease. + */ + test?: { + /** + * Enable enables Helm test actions for this HelmRelease after an Helm install + * or upgrade action has been performed. + */ + enable?: boolean; + /** + * Filters is a list of tests to run or exclude from running. + */ + filters?: { + /** + * Exclude specifies whether the named test should be excluded. + */ + exclude?: boolean; + /** + * Name is the name of the test. + */ + name: string; + }[]; + /** + * IgnoreFailures tells the controller to skip remediation when the Helm tests + * are run but fail. Can be overwritten for tests run after install or upgrade + * actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. + */ + ignoreFailures?: boolean; + /** + * Timeout is the time to wait for any individual Kubernetes operation during + * the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. + */ + timeout?: string; + }; + /** + * Timeout is the time to wait for any individual Kubernetes operation (like Jobs + * for hooks) during the performance of a Helm action. Defaults to '5m0s'. + */ + timeout?: string; + /** + * Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. + */ + uninstall?: { + /** + * DeletionPropagation specifies the deletion propagation policy when + * a Helm uninstall is performed. + */ + deletionPropagation?: string; + /** + * DisableHooks prevents hooks from running during the Helm rollback action. + */ + disableHooks?: boolean; + /** + * DisableWait disables waiting for all the resources to be deleted after + * a Helm uninstall is performed. + */ + disableWait?: boolean; + /** + * KeepHistory tells Helm to remove all associated resources and mark the + * release as deleted, but retain the release history. + */ + keepHistory?: boolean; + /** + * Timeout is the time to wait for any individual Kubernetes operation (like + * Jobs for hooks) during the performance of a Helm uninstall action. Defaults + * to 'HelmReleaseSpec.Timeout'. + */ + timeout?: string; + }; + /** + * Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. + */ + upgrade?: { + /** + * CleanupOnFail allows deletion of new resources created during the Helm + * upgrade action when it fails. + */ + cleanupOnFail?: boolean; + /** + * CRDs upgrade CRDs from the Helm Chart's crds directory according + * to the CRD upgrade policy provided here. Valid values are `Skip`, + * `Create` or `CreateReplace`. Default is `Skip` and if omitted + * CRDs are neither installed nor upgraded. + * + * Skip: do neither install nor replace (update) any CRDs. + * + * Create: new CRDs are created, existing CRDs are neither updated nor deleted. + * + * CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + * but not deleted. + * + * By default, CRDs are not applied during Helm upgrade action. With this + * option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. + * https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + */ + crds?: string; + /** + * DisableHooks prevents hooks from running during the Helm upgrade action. + */ + disableHooks?: boolean; + /** + * DisableOpenAPIValidation prevents the Helm upgrade action from validating + * rendered templates against the Kubernetes OpenAPI Schema. + */ + disableOpenAPIValidation?: boolean; + /** + * DisableSchemaValidation prevents the Helm upgrade action from validating + * the values against the JSON Schema. + */ + disableSchemaValidation?: boolean; + /** + * DisableTakeOwnership disables taking ownership of existing resources + * during the Helm upgrade action. Defaults to false. + */ + disableTakeOwnership?: boolean; + /** + * DisableWait disables the waiting for resources to be ready after a Helm + * upgrade has been performed. + */ + disableWait?: boolean; + /** + * DisableWaitForJobs disables waiting for jobs to complete after a Helm + * upgrade has been performed. + */ + disableWaitForJobs?: boolean; + /** + * Force forces resource updates through a replacement strategy. + */ + force?: boolean; + /** + * PreserveValues will make Helm reuse the last release's values and merge in + * overrides from 'Values'. Setting this flag makes the HelmRelease + * non-declarative. + */ + preserveValues?: boolean; + /** + * Remediation holds the remediation configuration for when the Helm upgrade + * action for the HelmRelease fails. The default is to not perform any action. + */ + remediation?: { + /** + * IgnoreTestFailures tells the controller to skip remediation when the Helm + * tests are run after an upgrade action but fail. + * Defaults to 'Test.IgnoreFailures'. + */ + ignoreTestFailures?: boolean; + /** + * RemediateLastFailure tells the controller to remediate the last failure, when + * no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. + */ + remediateLastFailure?: boolean; + /** + * Retries is the number of retries that should be attempted on failures before + * bailing. Remediation, using 'Strategy', is performed between each attempt. + * Defaults to '0', a negative integer equals to unlimited retries. + */ + retries?: number; + /** + * Strategy to use for failure remediation. Defaults to 'rollback'. + */ + strategy?: string; + }; + /** + * Timeout is the time to wait for any individual Kubernetes operation (like + * Jobs for hooks) during the performance of a Helm upgrade action. Defaults to + * 'HelmReleaseSpec.Timeout'. + */ + timeout?: string; + }; + /** + * Values holds the values for this Helm release. + */ + values?: { + [k: string]: unknown; + }; + /** + * ValuesFrom holds references to resources containing Helm values for this HelmRelease, + * and information about how they should be merged. + */ + valuesFrom?: { + /** + * Kind of the values referent, valid values are ('Secret', 'ConfigMap'). + */ + kind: "Secret" | "ConfigMap"; + /** + * Name of the values referent. Should reside in the same namespace as the + * referring resource. + */ + name: string; + /** + * Optional marks this ValuesReference as optional. When set, a not found error + * for the values reference is ignored, but any ValuesKey, TargetPath or + * transient error will still result in a reconciliation failure. + */ + optional?: boolean; + /** + * TargetPath is the YAML dot notation path the value should be merged at. When + * set, the ValuesKey is expected to be a single flat value. Defaults to 'None', + * which results in the values getting merged at the root. + */ + targetPath?: string; + /** + * ValuesKey is the data key where the values.yaml or a specific value can be + * found at. Defaults to 'values.yaml'. + */ + valuesKey?: string; + }[]; + }; + /** + * HelmReleaseStatus defines the observed state of a HelmRelease. + */ + status?: { + /** + * Conditions holds the conditions for the HelmRelease. + */ + conditions?: { + /** + * lastTransitionTime is the last time the condition transitioned from one status to another. + * This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + */ + lastTransitionTime: string; + /** + * message is a human readable message indicating details about the transition. + * This may be an empty string. + */ + message: string; + /** + * observedGeneration represents the .metadata.generation that the condition was set based upon. + * For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + * with respect to the current state of the instance. + */ + observedGeneration?: number; + /** + * reason contains a programmatic identifier indicating the reason for the condition's last transition. + * Producers of specific condition types may define expected values and meanings for this field, + * and whether the values are considered a guaranteed API. + * The value should be a CamelCase string. + * This field may not be empty. + */ + reason: string; + /** + * status of the condition, one of True, False, Unknown. + */ + status: "True" | "False" | "Unknown"; + /** + * type of condition in CamelCase or in foo.example.com/CamelCase. + */ + type: string; + }[]; + /** + * Failures is the reconciliation failure count against the latest desired + * state. It is reset after a successful reconciliation. + */ + failures?: number; + /** + * HelmChart is the namespaced name of the HelmChart resource created by + * the controller for the HelmRelease. + */ + helmChart?: string; + /** + * History holds the history of Helm releases performed for this HelmRelease + * up to the last successfully completed release. + */ + history?: { + /** + * APIVersion is the API version of the Snapshot. + * Provisional: when the calculation method of the Digest field is changed, + * this field will be used to distinguish between the old and new methods. + */ + apiVersion?: string; + /** + * AppVersion is the chart app version of the release object in storage. + */ + appVersion?: string; + /** + * ChartName is the chart name of the release object in storage. + */ + chartName: string; + /** + * ChartVersion is the chart version of the release object in + * storage. + */ + chartVersion: string; + /** + * ConfigDigest is the checksum of the config (better known as + * "values") of the release object in storage. + * It has the format of `:`. + */ + configDigest: string; + /** + * Deleted is when the release was deleted. + */ + deleted?: string; + /** + * Digest is the checksum of the release object in storage. + * It has the format of `:`. + */ + digest: string; + /** + * FirstDeployed is when the release was first deployed. + */ + firstDeployed: string; + /** + * LastDeployed is when the release was last deployed. + */ + lastDeployed: string; + /** + * Name is the name of the release. + */ + name: string; + /** + * Namespace is the namespace the release is deployed to. + */ + namespace: string; + /** + * OCIDigest is the digest of the OCI artifact associated with the release. + */ + ociDigest?: string; + /** + * Status is the current state of the release. + */ + status: string; + /** + * TestHooks is the list of test hooks for the release as observed to be + * run by the controller. + */ + testHooks?: { + /** + * TestHookStatus holds the status information for a test hook as observed + * to be run by the controller. + */ + [k: string]: { + /** + * LastCompleted is the time the test hook last completed. + */ + lastCompleted?: string; + /** + * LastStarted is the time the test hook was last started. + */ + lastStarted?: string; + /** + * Phase the test hook was observed to be in. + */ + phase?: string; + }; + }; + /** + * Version is the version of the release object in storage. + */ + version: number; + }[]; + /** + * InstallFailures is the install failure count against the latest desired + * state. It is reset after a successful reconciliation. + */ + installFailures?: number; + /** + * LastAttemptedConfigDigest is the digest for the config (better known as + * "values") of the last reconciliation attempt. + */ + lastAttemptedConfigDigest?: string; + /** + * LastAttemptedGeneration is the last generation the controller attempted + * to reconcile. + */ + lastAttemptedGeneration?: number; + /** + * LastAttemptedReleaseAction is the last release action performed for this + * HelmRelease. It is used to determine the active remediation strategy. + */ + lastAttemptedReleaseAction?: string; + /** + * LastAttemptedRevision is the Source revision of the last reconciliation + * attempt. For OCIRepository sources, the 12 first characters of the digest are + * appended to the chart version e.g. "1.2.3+1234567890ab". + */ + lastAttemptedRevision?: string; + /** + * LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. + * This is only set for OCIRepository sources. + */ + lastAttemptedRevisionDigest?: string; + /** + * LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last + * reconciliation attempt. + * Deprecated: Use LastAttemptedConfigDigest instead. + */ + lastAttemptedValuesChecksum?: string; + /** + * LastHandledForceAt holds the value of the most recent force request + * value, so a change of the annotation value can be detected. + */ + lastHandledForceAt?: string; + /** + * LastHandledReconcileAt holds the value of the most recent + * reconcile request value, so a change of the annotation value + * can be detected. + */ + lastHandledReconcileAt?: string; + /** + * LastHandledResetAt holds the value of the most recent reset request + * value, so a change of the annotation value can be detected. + */ + lastHandledResetAt?: string; + /** + * LastReleaseRevision is the revision of the last successful Helm release. + * Deprecated: Use History instead. + */ + lastReleaseRevision?: number; + /** + * ObservedGeneration is the last observed generation. + */ + observedGeneration?: number; + /** + * ObservedPostRenderersDigest is the digest for the post-renderers of + * the last successful reconciliation attempt. + */ + observedPostRenderersDigest?: string; + /** + * StorageNamespace is the namespace of the Helm release storage for the + * current release. + */ + storageNamespace?: string; + /** + * UpgradeFailures is the upgrade failure count against the latest desired + * state. It is reset after a successful reconciliation. + */ + upgradeFailures?: number; + }; +} diff --git a/packages/k8s/src/core/custom/flux/flux.helm-repo.ts b/packages/k8s/src/core/custom/flux/flux.helm-repo.ts new file mode 100644 index 0000000..1123a46 --- /dev/null +++ b/packages/k8s/src/core/custom/flux/flux.helm-repo.ts @@ -0,0 +1,25 @@ +import type { KubernetesObject } from '@kubernetes/client-node'; + +import { Resource } from '../../../resources/resources.js'; + +import type { K8SHelmRepositoryV1 } from './flux.helm-repo.types.js'; + +type SetOptions = { + url: string; +}; +class HelmRepo extends Resource { + public static readonly apiVersion = 'source.toolkit.fluxcd.io/v1'; + public static readonly kind = 'HelmRepository'; + public static readonly plural = 'helmrepositories'; + + public set = async ({ url }: SetOptions) => { + await this.ensure({ + spec: { + interval: '1h', + url, + }, + }); + }; +} + +export { HelmRepo }; diff --git a/packages/k8s/src/core/custom/flux/flux.helm-repo.types.ts b/packages/k8s/src/core/custom/flux/flux.helm-repo.types.ts new file mode 100644 index 0000000..56f4b5f --- /dev/null +++ b/packages/k8s/src/core/custom/flux/flux.helm-repo.types.ts @@ -0,0 +1,240 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * HelmRepository is the Schema for the helmrepositories API. + */ +export interface K8SHelmRepositoryV1 { + /** + * APIVersion defines the versioned schema of this representation of an object. + * Servers should convert recognized schemas to the latest internal value, and + * may reject unrecognized values. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + */ + apiVersion?: string; + /** + * Kind is a string value representing the REST resource this object represents. + * Servers may infer this from the endpoint the client submits requests to. + * Cannot be updated. + * In CamelCase. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind?: string; + metadata?: {}; + /** + * HelmRepositorySpec specifies the required configuration to produce an + * Artifact for a Helm repository index YAML. + */ + spec?: { + /** + * AccessFrom specifies an Access Control List for allowing cross-namespace + * references to this object. + * NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 + */ + accessFrom?: { + /** + * NamespaceSelectors is the list of namespace selectors to which this ACL applies. + * Items in this list are evaluated using a logical OR operation. + */ + namespaceSelectors: { + /** + * MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + * map is equivalent to an element of matchExpressions, whose key field is "key", the + * operator is "In", and the values array contains only "value". The requirements are ANDed. + */ + matchLabels?: { + [k: string]: string; + }; + }[]; + }; + /** + * CertSecretRef can be given the name of a Secret containing + * either or both of + * + * - a PEM-encoded client certificate (`tls.crt`) and private + * key (`tls.key`); + * - a PEM-encoded CA certificate (`ca.crt`) + * + * and whichever are supplied, will be used for connecting to the + * registry. The client cert and key are useful if you are + * authenticating with a certificate; the CA cert is useful if + * you are using a self-signed server certificate. The Secret must + * be of type `Opaque` or `kubernetes.io/tls`. + * + * It takes precedence over the values specified in the Secret referred + * to by `.spec.secretRef`. + */ + certSecretRef?: { + /** + * Name of the referent. + */ + name: string; + }; + /** + * Insecure allows connecting to a non-TLS HTTP container registry. + * This field is only taken into account if the .spec.type field is set to 'oci'. + */ + insecure?: boolean; + /** + * Interval at which the HelmRepository URL is checked for updates. + * This interval is approximate and may be subject to jitter to ensure + * efficient use of resources. + */ + interval?: string; + /** + * PassCredentials allows the credentials from the SecretRef to be passed + * on to a host that does not match the host as defined in URL. + * This may be required if the host of the advertised chart URLs in the + * index differ from the defined URL. + * Enabling this should be done with caution, as it can potentially result + * in credentials getting stolen in a MITM-attack. + */ + passCredentials?: boolean; + /** + * Provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. + * This field is optional, and only taken into account if the .spec.type field is set to 'oci'. + * When not specified, defaults to 'generic'. + */ + provider?: string; + /** + * SecretRef specifies the Secret containing authentication credentials + * for the HelmRepository. + * For HTTP/S basic auth the secret must contain 'username' and 'password' + * fields. + * Support for TLS auth using the 'certFile' and 'keyFile', and/or 'caFile' + * keys is deprecated. Please use `.spec.certSecretRef` instead. + */ + secretRef?: { + /** + * Name of the referent. + */ + name: string; + }; + /** + * Suspend tells the controller to suspend the reconciliation of this + * HelmRepository. + */ + suspend?: boolean; + /** + * Timeout is used for the index fetch operation for an HTTPS helm repository, + * and for remote OCI Repository operations like pulling for an OCI helm + * chart by the associated HelmChart. + * Its default value is 60s. + */ + timeout?: string; + /** + * Type of the HelmRepository. + * When this field is set to "oci", the URL field value must be prefixed with "oci://". + */ + type?: string; + /** + * URL of the Helm repository, a valid URL contains at least a protocol and + * host. + */ + url: string; + }; + /** + * HelmRepositoryStatus records the observed state of the HelmRepository. + */ + status?: { + /** + * Artifact represents the last successful HelmRepository reconciliation. + */ + artifact?: { + /** + * Digest is the digest of the file in the form of ':'. + */ + digest?: string; + /** + * LastUpdateTime is the timestamp corresponding to the last update of the + * Artifact. + */ + lastUpdateTime: string; + /** + * Metadata holds upstream information such as OCI annotations. + */ + metadata?: { + [k: string]: string; + }; + /** + * Path is the relative file path of the Artifact. It can be used to locate + * the file in the root of the Artifact storage on the local file system of + * the controller managing the Source. + */ + path: string; + /** + * Revision is a human-readable identifier traceable in the origin source + * system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. + */ + revision: string; + /** + * Size is the number of bytes in the file. + */ + size?: number; + /** + * URL is the HTTP address of the Artifact as exposed by the controller + * managing the Source. It can be used to retrieve the Artifact for + * consumption, e.g. by another controller applying the Artifact contents. + */ + url: string; + }; + /** + * Conditions holds the conditions for the HelmRepository. + */ + conditions?: { + /** + * lastTransitionTime is the last time the condition transitioned from one status to another. + * This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + */ + lastTransitionTime: string; + /** + * message is a human readable message indicating details about the transition. + * This may be an empty string. + */ + message: string; + /** + * observedGeneration represents the .metadata.generation that the condition was set based upon. + * For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + * with respect to the current state of the instance. + */ + observedGeneration?: number; + /** + * reason contains a programmatic identifier indicating the reason for the condition's last transition. + * Producers of specific condition types may define expected values and meanings for this field, + * and whether the values are considered a guaranteed API. + * The value should be a CamelCase string. + * This field may not be empty. + */ + reason: string; + /** + * status of the condition, one of True, False, Unknown. + */ + status: "True" | "False" | "Unknown"; + /** + * type of condition in CamelCase or in foo.example.com/CamelCase. + */ + type: string; + }[]; + /** + * LastHandledReconcileAt holds the value of the most recent + * reconcile request value, so a change of the annotation value + * can be detected. + */ + lastHandledReconcileAt?: string; + /** + * ObservedGeneration is the last observed generation of the HelmRepository + * object. + */ + observedGeneration?: number; + /** + * URL is the dynamic fetch link for the latest Artifact. + * It is provided on a "best effort" basis, and using the precise + * HelmRepositoryStatus.Artifact data is recommended. + */ + url?: string; + }; +} diff --git a/packages/k8s/src/core/custom/flux/flux.ts b/packages/k8s/src/core/custom/flux/flux.ts new file mode 100644 index 0000000..947168e --- /dev/null +++ b/packages/k8s/src/core/custom/flux/flux.ts @@ -0,0 +1,2 @@ +export { HelmRelease } from './flux.helm-release.js'; +export { HelmRepo } from './flux.helm-repo.js'; diff --git a/packages/k8s/src/resources/resource/resource.custom.ts b/packages/k8s/src/resources/resource/resource.custom.ts index fd8da7b..a4ff509 100644 --- a/packages/k8s/src/resources/resource/resource.custom.ts +++ b/packages/k8s/src/resources/resource/resource.custom.ts @@ -50,28 +50,23 @@ class CustomResource extends Resource< if (!this.exists) { return; } - // TODO: Read FINALIZER - // const finalizers = this.metadata?.finalizers || []; + const finalizers = this.metadata?.finalizers || [] + const hasFinalizer = finalizers.includes(FINALIZER) if (this.manifest?.metadata?.deletionTimestamp) { - await this.destroy?.(); - // if (this.metadata?.finalizers?.includes(FINALIZER)) { - // await this.patch({ - // metadata: { - // finalizers: finalizers.filter((f) => f !== FINALIZER), - // deletionTimestamp: this.metadata?.deletionTimestamp, - // }, - // } as any) - // } + if (hasFinalizer) { + await this.destroy?.(); + await this.removeFinalizer(); + } return; } - // if (this.destroy && !finalizers.includes(FINALIZER)) { - // return await this.patch({ - // metadata: { - // finalizers: [...finalizers, FINALIZER] - // }, - // spec: this.spec!, - // }); - // } + if (!hasFinalizer && this.destroy) { + return await this.patch({ + metadata: { + finalizers: [...finalizers, FINALIZER] + }, + spec: this.spec!, + }); + } await this.markSeen(); await this.reconcile?.(); await this.markReady(); @@ -91,9 +86,9 @@ class CustomResource extends Resource< cronTime: '*/2 * * * *', onTick: this.queueReconcile, start: true, - runOnInit: true, }); this.on('changed', this.#handleUpdate); + setTimeout(this.#handleUpdate, 0); } public get reconcileTime() { diff --git a/packages/k8s/src/resources/resource/resource.ts b/packages/k8s/src/resources/resource/resource.ts index 00218d2..6c6f30a 100644 --- a/packages/k8s/src/resources/resource/resource.ts +++ b/packages/k8s/src/resources/resource/resource.ts @@ -7,6 +7,7 @@ import { isDeepSubset } from '@morten-olsen/box-utils/objects'; import { ResourceService } from '../resources.service.js'; import { K8sConfig } from '../../config/config.js'; +import { FINALIZER } from '@morten-olsen/box-utils/consts'; type ResourceSelector = { apiVersion: string; @@ -140,6 +141,41 @@ class Resource extends EventEmitter { + this.#queue.add(async () => { + if (!this.metadata?.finalizers?.includes(FINALIZER) || !this.exists) { + return; + } + const { services } = this.#options; + const config = services.get(K8sConfig); + const { objectsApi } = config; + const body: KubernetesObject = { + apiVersion: this.selector.apiVersion, + kind: this.selector.kind, + metadata: { + name: this.selector.name, + namespace: this.selector.namespace, + finalizers: this.metadata?.finalizers?.filter(f => f !== FINALIZER) + }, + }; + try { + this.manifest = await objectsApi.patch( + body, + undefined, + undefined, + undefined, + undefined, + PatchStrategy.MergePatch, + ) as T; + } catch (err) { + if (err instanceof ApiException && err.code === 404) { + return; + } + throw err; + } + }); + } + public patch = (patch: T) => this.#queue.add(async () => { const { services } = this.#options; diff --git a/packages/k8s/tsconfig.tsbuildinfo b/packages/k8s/tsconfig.tsbuildinfo index ccbf7d4..6579de7 100644 --- a/packages/k8s/tsconfig.tsbuildinfo +++ b/packages/k8s/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/exports.ts","./src/global.d.ts","./src/operator.ts","./src/config/config.ts","./src/core/core.crd.ts","./src/core/core.deployment.ts","./src/core/core.namespace.ts","./src/core/core.pv.ts","./src/core/core.secret.ts","./src/core/core.service.ts","./src/core/core.stateful-set.ts","./src/core/core.storage-class.ts","./src/core/core.ts","./src/errors/errors.ts","./src/resources/resources.service.ts","./src/resources/resources.ts","./src/resources/resources.utils.ts","./src/resources/resource/resource.custom.ts","./src/resources/resource/resource.reference.ts","./src/resources/resource/resource.ts","./src/utils/utils.secrets.ts","./src/watchers/watchers.ts","./src/watchers/watcher/watcher.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/exports.ts","./src/global.d.ts","./src/operator.ts","./src/config/config.ts","./src/core/core.crd.ts","./src/core/core.deployment.ts","./src/core/core.namespace.ts","./src/core/core.pv.ts","./src/core/core.secret.ts","./src/core/core.service.ts","./src/core/core.stateful-set.ts","./src/core/core.storage-class.ts","./src/core/core.ts","./src/core/custom/flux/flux.helm-release.ts","./src/core/custom/flux/flux.helm-release.type.ts","./src/core/custom/flux/flux.helm-repo.ts","./src/core/custom/flux/flux.helm-repo.types.ts","./src/core/custom/flux/flux.ts","./src/errors/errors.ts","./src/resources/resources.service.ts","./src/resources/resources.ts","./src/resources/resources.utils.ts","./src/resources/resource/resource.custom.ts","./src/resources/resource/resource.reference.ts","./src/resources/resource/resource.ts","./src/utils/utils.secrets.ts","./src/watchers/watchers.ts","./src/watchers/watcher/watcher.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/resource-backup/.gitignore b/packages/resource-backup/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/resource-backup/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/resource-backup/package.json b/packages/resource-backup/package.json new file mode 100644 index 0000000..c56ddb5 --- /dev/null +++ b/packages/resource-backup/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@types/node": "24.9.1", + "@vitest/coverage-v8": "4.0.3", + "typescript": "5.9.3", + "vitest": "4.0.3", + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*" + }, + "name": "@morten-olsen/box-resource-backup", + "version": "1.0.0", + "imports": { + "#root/*": "./src/*" + } +} diff --git a/packages/resource-backup/src/exports.ts b/packages/resource-backup/src/exports.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-backup/tsconfig.json b/packages/resource-backup/tsconfig.json new file mode 100644 index 0000000..2a3ec7e --- /dev/null +++ b/packages/resource-backup/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/box-configs/tsconfig.json" +} diff --git a/packages/resource-backup/vitest.config.ts b/packages/resource-backup/vitest.config.ts new file mode 100644 index 0000000..0a3d383 --- /dev/null +++ b/packages/resource-backup/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/box-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/packages/resource-environment/.gitignore b/packages/resource-environment/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/resource-environment/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/resource-environment/package.json b/packages/resource-environment/package.json new file mode 100644 index 0000000..9287b7a --- /dev/null +++ b/packages/resource-environment/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@types/node": "24.9.1", + "@vitest/coverage-v8": "4.0.3", + "typescript": "5.9.3", + "vitest": "4.0.3", + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*" + }, + "name": "@morten-olsen/box-resource-environment", + "version": "1.0.0", + "imports": { + "#root/*": "./src/*" + } +} diff --git a/packages/resource-environment/src/exports.ts b/packages/resource-environment/src/exports.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-environment/tsconfig.json b/packages/resource-environment/tsconfig.json new file mode 100644 index 0000000..2a3ec7e --- /dev/null +++ b/packages/resource-environment/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/box-configs/tsconfig.json" +} diff --git a/packages/resource-environment/vitest.config.ts b/packages/resource-environment/vitest.config.ts new file mode 100644 index 0000000..0a3d383 --- /dev/null +++ b/packages/resource-environment/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/box-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/packages/resource-ingress/.gitignore b/packages/resource-ingress/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/resource-ingress/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/resource-ingress/package.json b/packages/resource-ingress/package.json new file mode 100644 index 0000000..11b38be --- /dev/null +++ b/packages/resource-ingress/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@types/node": "24.9.1", + "@vitest/coverage-v8": "4.0.3", + "typescript": "5.9.3", + "vitest": "4.0.3", + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*" + }, + "name": "@morten-olsen/box-resource-ingress", + "version": "1.0.0", + "imports": { + "#root/*": "./src/*" + } +} diff --git a/packages/resource-ingress/src/exports.ts b/packages/resource-ingress/src/exports.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-ingress/tsconfig.json b/packages/resource-ingress/tsconfig.json new file mode 100644 index 0000000..2a3ec7e --- /dev/null +++ b/packages/resource-ingress/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/box-configs/tsconfig.json" +} diff --git a/packages/resource-ingress/vitest.config.ts b/packages/resource-ingress/vitest.config.ts new file mode 100644 index 0000000..0a3d383 --- /dev/null +++ b/packages/resource-ingress/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/box-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/packages/resource-secret-generator/.gitignore b/packages/resource-secret-generator/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/resource-secret-generator/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/resource-secret-generator/package.json b/packages/resource-secret-generator/package.json new file mode 100644 index 0000000..a5bd770 --- /dev/null +++ b/packages/resource-secret-generator/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@types/node": "24.9.1", + "@vitest/coverage-v8": "4.0.3", + "typescript": "5.9.3", + "vitest": "4.0.3", + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*" + }, + "name": "@morten-olsen/box-resource-secret-generator", + "version": "1.0.0", + "imports": { + "#root/*": "./src/*" + } +} diff --git a/packages/resource-secret-generator/src/exports.ts b/packages/resource-secret-generator/src/exports.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-secret-generator/tsconfig.json b/packages/resource-secret-generator/tsconfig.json new file mode 100644 index 0000000..2a3ec7e --- /dev/null +++ b/packages/resource-secret-generator/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/box-configs/tsconfig.json" +} diff --git a/packages/resource-secret-generator/vitest.config.ts b/packages/resource-secret-generator/vitest.config.ts new file mode 100644 index 0000000..0a3d383 --- /dev/null +++ b/packages/resource-secret-generator/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/box-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/packages/resource-storage/.gitignore b/packages/resource-storage/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/resource-storage/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/resource-storage/package.json b/packages/resource-storage/package.json new file mode 100644 index 0000000..e978847 --- /dev/null +++ b/packages/resource-storage/package.json @@ -0,0 +1,29 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@types/node": "catalog:", + "@vitest/coverage-v8": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + "@morten-olsen/box-configs": "workspace:*", + "@morten-olsen/box-tests": "workspace:*" + }, + "dependencies": { + "@morten-olsen/box-k8s": "workspace:*" + }, + "name": "@morten-olsen/box-resource-storage", + "version": "1.0.0" +} diff --git a/packages/resource-storage/src/exports.ts b/packages/resource-storage/src/exports.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-storage/src/resources/storage.schemas.ts b/packages/resource-storage/src/resources/storage.schemas.ts new file mode 100644 index 0000000..94b8665 --- /dev/null +++ b/packages/resource-storage/src/resources/storage.schemas.ts @@ -0,0 +1,15 @@ +import { z } from '@morten-olsen/box-k8s'; + +const hostSpec = z.object({ + type: z.literal('host'), + path: z.string(), +}); + +const locationSpec = hostSpec; + +const storageSpecSchema = z.object({ + location: locationSpec, + backup: locationSpec.optional(), +}); + +export { storageSpecSchema }; diff --git a/packages/resource-storage/src/resources/storage.ts b/packages/resource-storage/src/resources/storage.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/resource-storage/tsconfig.json b/packages/resource-storage/tsconfig.json new file mode 100644 index 0000000..2a3ec7e --- /dev/null +++ b/packages/resource-storage/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/box-configs/tsconfig.json" +} diff --git a/packages/resource-storage/vitest.config.ts b/packages/resource-storage/vitest.config.ts new file mode 100644 index 0000000..0a3d383 --- /dev/null +++ b/packages/resource-storage/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/box-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80c817a..edc3812 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: version: 6.0.9(@pnpm/logger@5.2.0) '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6)) + version: 4.0.1(vitest@4.0.1(@types/node@24.9.2)(tsx@4.20.6)) eslint: specifier: 9.38.0 version: 9.38.0 @@ -61,9 +61,16 @@ importers: version: 8.46.2(eslint@9.38.0)(typescript@5.9.3) vitest: specifier: 'catalog:' - version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6) + version: 4.0.1(@types/node@24.9.2)(tsx@4.20.6) packages/bootstrap: + dependencies: + '@morten-olsen/box-k8s': + specifier: workspace:* + version: link:../k8s + '@morten-olsen/box-utils': + specifier: workspace:* + version: link:../utils devDependencies: '@morten-olsen/box-configs': specifier: workspace:* @@ -77,6 +84,9 @@ importers: '@vitest/coverage-v8': specifier: 'catalog:' version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6)) + tsx: + specifier: ^4.20.6 + version: 4.20.6 typescript: specifier: 'catalog:' version: 5.9.3 @@ -173,6 +183,27 @@ importers: specifier: 'catalog:' version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6) + packages/resource-backup: + devDependencies: + '@morten-olsen/box-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/box-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 24.9.1 + version: 24.9.1 + '@vitest/coverage-v8': + specifier: 4.0.3 + version: 4.0.3(vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.3 + version: 4.0.3(@types/node@24.9.1)(tsx@4.20.6) + packages/resource-cloudflare: dependencies: '@morten-olsen/box-k8s': @@ -207,6 +238,48 @@ importers: specifier: 4.0.1 version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6) + packages/resource-environment: + devDependencies: + '@morten-olsen/box-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/box-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 24.9.1 + version: 24.9.1 + '@vitest/coverage-v8': + specifier: 4.0.3 + version: 4.0.3(vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.3 + version: 4.0.3(@types/node@24.9.1)(tsx@4.20.6) + + packages/resource-ingress: + devDependencies: + '@morten-olsen/box-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/box-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 24.9.1 + version: 24.9.1 + '@vitest/coverage-v8': + specifier: 4.0.3 + version: 4.0.3(vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.3 + version: 4.0.3(@types/node@24.9.1)(tsx@4.20.6) + packages/resource-postgres: dependencies: '@morten-olsen/box-k8s': @@ -263,6 +336,52 @@ importers: specifier: 'catalog:' version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6) + packages/resource-secret-generator: + devDependencies: + '@morten-olsen/box-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/box-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 24.9.1 + version: 24.9.1 + '@vitest/coverage-v8': + specifier: 4.0.3 + version: 4.0.3(vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.3 + version: 4.0.3(@types/node@24.9.1)(tsx@4.20.6) + + packages/resource-storage: + dependencies: + '@morten-olsen/box-k8s': + specifier: workspace:* + version: link:../k8s + devDependencies: + '@morten-olsen/box-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/box-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 'catalog:' + version: 24.9.1 + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.0.1(vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6)) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.1(@types/node@24.9.1)(tsx@4.20.6) + packages/tests: dependencies: '@pnpm/find-workspace-packages': @@ -885,6 +1004,9 @@ packages: '@types/node@24.9.1': resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} + '@types/node@24.9.2': + resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==} + '@types/ssri@7.1.5': resolution: {integrity: sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw==} @@ -959,9 +1081,21 @@ packages: '@vitest/browser': optional: true + '@vitest/coverage-v8@4.0.3': + resolution: {integrity: sha512-I+MlLwyJRBjmJr1kFYSxoseINbIdpxIAeK10jmXgB0FUtIfdYsvM3lGAvBu5yk8WPyhefzdmbCHCc1idFbNRcg==} + peerDependencies: + '@vitest/browser': 4.0.3 + vitest: 4.0.3 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@4.0.1': resolution: {integrity: sha512-KtvGLN/IWoZfg68JF2q/zbDEo+UJTWnc7suYJ8RF+ZTBeBcBz4NIOJDxO4Q3bEY9GsOYhgy5cOevcVPFh4+V7g==} + '@vitest/expect@4.0.3': + resolution: {integrity: sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==} + '@vitest/mocker@4.0.1': resolution: {integrity: sha512-fwmvg8YvwSAE41Hyhul7dL4UzPhG+k2VaZCcL+aHagLx4qlNQgKYTw7coF4YdjAxSBBt0b408gQFYMX1Qeqweg==} peerDependencies: @@ -973,21 +1107,47 @@ packages: vite: optional: true + '@vitest/mocker@4.0.3': + resolution: {integrity: sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@4.0.1': resolution: {integrity: sha512-6nq3JY/zQ91+oX1vd4fajiVNyA/HMhaF9cOw5P9cQi6ML7PRi7ilVaQ77PulF+4kvUKr9bcLm9GoAtwlVFbGzw==} + '@vitest/pretty-format@4.0.3': + resolution: {integrity: sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==} + '@vitest/runner@4.0.1': resolution: {integrity: sha512-nxUoWmw7ZX2OiSNwolJeSOOzrrR/o79wRTwP7HhiW/lDFwQHtWMj9snMhrdvccFqanvI8897E81eXjgDbrRvqA==} + '@vitest/runner@4.0.3': + resolution: {integrity: sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==} + '@vitest/snapshot@4.0.1': resolution: {integrity: sha512-CvfsEWutEIN/Z9ScXYup7YwlPeK9JICrV7FN9p3pVytsyh+aCHAH0PUi//YlTiQ7T8qYxJYpUrAwZL9XqmZ5ZA==} + '@vitest/snapshot@4.0.3': + resolution: {integrity: sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==} + '@vitest/spy@4.0.1': resolution: {integrity: sha512-Hj0/TBQ2EN72wDpfKiUf63mRCkE0ZiSGXGeDDvW9T3LBKVVApItd0GyQLDBIe03kWbyK9gOTEbJVVWthcLFzCg==} + '@vitest/spy@4.0.3': + resolution: {integrity: sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==} + '@vitest/utils@4.0.1': resolution: {integrity: sha512-uRrACgpIz5sxuT87ml7xhh7EdKtW8k0N9oSFVBPl8gHB/JfLObLe9dXO6ZrsNN55FzciGIRqIEILgTQvg1eNHw==} + '@vitest/utils@4.0.3': + resolution: {integrity: sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==} + '@zkochan/which@2.0.3': resolution: {integrity: sha512-C1ReN7vt2/2O0fyTsx5xnbQuxBrmG5NMSbcIkPKCCfCTJgpZBsuRYzFXHj3nVq8vTfK7vxHUmzfCpSHgO7j4rg==} engines: {node: '>= 8'} @@ -2666,6 +2826,40 @@ packages: jsdom: optional: true + vitest@4.0.3: + resolution: {integrity: sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.3 + '@vitest/browser-preview': 4.0.3 + '@vitest/browser-webdriverio': 4.0.3 + '@vitest/ui': 4.0.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -3306,6 +3500,11 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@24.9.2': + dependencies: + undici-types: 7.16.0 + optional: true + '@types/ssri@7.1.5': dependencies: '@types/node': 24.9.1 @@ -3424,6 +3623,40 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@4.0.1(vitest@4.0.1(@types/node@24.9.2)(tsx@4.20.6))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.1 + ast-v8-to-istanbul: 0.3.7 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.3.5 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.1(@types/node@24.9.2)(tsx@4.20.6) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@4.0.3(vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.3 + ast-v8-to-istanbul: 0.3.7 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.3.5 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.3(@types/node@24.9.1)(tsx@4.20.6) + transitivePeerDependencies: + - supports-color + '@vitest/expect@4.0.1': dependencies: '@standard-schema/spec': 1.0.0 @@ -3433,6 +3666,15 @@ snapshots: chai: 6.2.0 tinyrainbow: 3.0.3 + '@vitest/expect@4.0.3': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.3 + '@vitest/utils': 4.0.3 + chai: 6.2.0 + tinyrainbow: 3.0.3 + '@vitest/mocker@4.0.1(vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6))': dependencies: '@vitest/spy': 4.0.1 @@ -3441,28 +3683,66 @@ snapshots: optionalDependencies: vite: 7.1.12(@types/node@24.9.1)(tsx@4.20.6) + '@vitest/mocker@4.0.1(vite@7.1.12(@types/node@24.9.2)(tsx@4.20.6))': + dependencies: + '@vitest/spy': 4.0.1 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 7.1.12(@types/node@24.9.2)(tsx@4.20.6) + + '@vitest/mocker@4.0.3(vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6))': + dependencies: + '@vitest/spy': 4.0.3 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 7.1.12(@types/node@24.9.1)(tsx@4.20.6) + '@vitest/pretty-format@4.0.1': dependencies: tinyrainbow: 3.0.3 + '@vitest/pretty-format@4.0.3': + dependencies: + tinyrainbow: 3.0.3 + '@vitest/runner@4.0.1': dependencies: '@vitest/utils': 4.0.1 pathe: 2.0.3 + '@vitest/runner@4.0.3': + dependencies: + '@vitest/utils': 4.0.3 + pathe: 2.0.3 + '@vitest/snapshot@4.0.1': dependencies: '@vitest/pretty-format': 4.0.1 magic-string: 0.30.19 pathe: 2.0.3 + '@vitest/snapshot@4.0.3': + dependencies: + '@vitest/pretty-format': 4.0.3 + magic-string: 0.30.19 + pathe: 2.0.3 + '@vitest/spy@4.0.1': {} + '@vitest/spy@4.0.3': {} + '@vitest/utils@4.0.1': dependencies: '@vitest/pretty-format': 4.0.1 tinyrainbow: 3.0.3 + '@vitest/utils@4.0.3': + dependencies: + '@vitest/pretty-format': 4.0.3 + tinyrainbow: 3.0.3 + '@zkochan/which@2.0.3': dependencies: isexe: 2.0.0 @@ -5275,6 +5555,19 @@ snapshots: fsevents: 2.3.3 tsx: 4.20.6 + vite@7.1.12(@types/node@24.9.2)(tsx@4.20.6): + dependencies: + esbuild: 0.25.11 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.9.2 + fsevents: 2.3.3 + tsx: 4.20.6 + vitest@4.0.1(@types/node@24.9.1)(tsx@4.20.6): dependencies: '@vitest/expect': 4.0.1 @@ -5313,6 +5606,82 @@ snapshots: - tsx - yaml + vitest@4.0.1(@types/node@24.9.2)(tsx@4.20.6): + dependencies: + '@vitest/expect': 4.0.1 + '@vitest/mocker': 4.0.1(vite@7.1.12(@types/node@24.9.2)(tsx@4.20.6)) + '@vitest/pretty-format': 4.0.1 + '@vitest/runner': 4.0.1 + '@vitest/snapshot': 4.0.1 + '@vitest/spy': 4.0.1 + '@vitest/utils': 4.0.1 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@24.9.2)(tsx@4.20.6) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.9.2 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vitest@4.0.3(@types/node@24.9.1)(tsx@4.20.6): + dependencies: + '@vitest/expect': 4.0.3 + '@vitest/mocker': 4.0.3(vite@7.1.12(@types/node@24.9.1)(tsx@4.20.6)) + '@vitest/pretty-format': 4.0.3 + '@vitest/runner': 4.0.3 + '@vitest/snapshot': 4.0.3 + '@vitest/spy': 4.0.3 + '@vitest/utils': 4.0.3 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@24.9.1)(tsx@4.20.6) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.9.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + wcwidth@1.0.1: dependencies: defaults: 1.0.4