This commit is contained in:
Morten Olsen
2025-10-29 23:31:30 +01:00
parent 2281dcafb4
commit 772d078649
48 changed files with 2755 additions and 30 deletions

View File

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

View File

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

View File

@@ -0,0 +1 @@
export { BootstrapService } from './bootstrap.js';

View File

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

View File

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

View File

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

View File

@@ -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();

View File

@@ -0,0 +1,3 @@
const NAMESPACE = 'homelab';
export { NAMESPACE };

View File

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

View File

@@ -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<string, unknown>;
chart: {
name: string;
namespace?: string;
};
};
class HelmRelease extends Resource<KubernetesObject & K8SHelmReleaseV2> {
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 };

View File

@@ -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 `<algo>:<checksum>`.
*/
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 `<algo>:<checksum>`.
*/
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;
};
}

View File

@@ -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<KubernetesObject & K8SHelmRepositoryV1> {
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 };

View File

@@ -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 '<algorithm>:<checksum>'.
*/
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;
};
}

View File

@@ -0,0 +1,2 @@
export { HelmRelease } from './flux.helm-release.js';
export { HelmRepo } from './flux.helm-repo.js';

View File

@@ -50,28 +50,23 @@ class CustomResource<TSpec extends ZodType> 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<TSpec extends ZodType> extends Resource<
cronTime: '*/2 * * * *',
onTick: this.queueReconcile,
start: true,
runOnInit: true,
});
this.on('changed', this.#handleUpdate);
setTimeout(this.#handleUpdate, 0);
}
public get reconcileTime() {

View File

@@ -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<T extends KubernetesObject> extends EventEmitter<ResourceEvents<T
return manifest.status as ExplicitAny;
}
public removeFinalizer = async () => {
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;

View File

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

4
packages/resource-backup/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

@@ -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/*"
}
}

View File

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/box-configs/tsconfig.json"
}

View File

@@ -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,
},
};
});

View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

@@ -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/*"
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/box-configs/tsconfig.json"
}

View File

@@ -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,
},
};
});

4
packages/resource-ingress/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

@@ -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/*"
}
}

View File

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/box-configs/tsconfig.json"
}

View File

@@ -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,
},
};
});

View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

@@ -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/*"
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/box-configs/tsconfig.json"
}

View File

@@ -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,
},
};
});

4
packages/resource-storage/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules/
/dist/
/coverage/
/.env

View File

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

View File

View File

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

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"extends": "@morten-olsen/box-configs/tsconfig.json"
}

View File

@@ -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,
},
};
});