diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index c44c51e..576ef8c 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -33,6 +33,14 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: data-volumes + mountPath: {{ .Values.storage.path }} + volumes: + - name: data-volumes + hostPath: + path: {{ .Values.storage.path }} + type: DirectoryOrCreate {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/chart/values.yaml b/chart/values.yaml index 7918b3d..2c5b80d 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -9,8 +9,11 @@ image: tag: main imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" +nameOverride: '' +fullnameOverride: '' + +storage: + path: /data/volumes serviceAccount: # Specifies whether a service account should be created @@ -19,7 +22,7 @@ serviceAccount: annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: "" + name: '' podAnnotations: {} @@ -50,4 +53,4 @@ nodeSelector: {} tolerations: [] -affinity: {} \ No newline at end of file +affinity: {} diff --git a/scripts/list-manifests.ts b/scripts/list-manifests.ts new file mode 100755 index 0000000..b19dcf6 --- /dev/null +++ b/scripts/list-manifests.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +import { K8sService } from '../src/services/k8s/k8s.ts'; +import { Services } from '../src/utils/service.ts'; + +const services = new Services(); +const k8s = services.get(K8sService); + +const manifests = await k8s.extensionsApi.listCustomResourceDefinition(); + +for (const manifest of manifests.items) { + for (const version of manifest.spec.versions) { + console.log(`group: ${manifest.spec.group}, plural: ${manifest.spec.names.plural}, version: ${version.name}`); + } +} diff --git a/src/index.ts b/src/index.ts index 29241b8..bf408ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { Services } from './utils/service.ts'; import { CustomResourceService } from './services/custom-resources/custom-resources.ts'; import { WatcherService } from './services/watchers/watchers.ts'; import { customResources } from './custom-resouces/custom-resources.ts'; +import { StorageProvider } from './storage-provider/storage-provider.ts'; process.on('uncaughtException', (error) => { console.log('UNCAUGHT EXCEPTION'); @@ -29,6 +30,8 @@ process.on('unhandledRejection', (error) => { const services = new Services(); const watcherService = services.get(WatcherService); +const storageProvider = services.get(StorageProvider); +await storageProvider.start(); await watcherService .create({ path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions', diff --git a/src/storage-provider/storage-provider.ts b/src/storage-provider/storage-provider.ts new file mode 100644 index 0000000..dd6cd2c --- /dev/null +++ b/src/storage-provider/storage-provider.ts @@ -0,0 +1,82 @@ +import { mkdir } from 'fs/promises'; + +import { V1PersistentVolume, type V1PersistentVolumeClaim } from '@kubernetes/client-node'; + +import { Watcher, WatcherService } from '../services/watchers/watchers.ts'; +import type { Services } from '../utils/service.ts'; +import { ResourceService, type Resource } from '../services/resources/resources.ts'; + +const PROVISIONER = 'reuse-local-path-provisioner'; + +class StorageProvider { + #watcher: Watcher; + #services: Services; + + constructor(services: Services) { + this.#services = services; + const watchService = this.#services.get(WatcherService); + this.#watcher = watchService.create({ + path: '/api/v1/persistentvolumeclaims', + transform: (manifest) => ({ + apiVersion: 'v1', + kind: 'PersistentVolumeClaim', + ...manifest, + }), + list: async (k8s) => { + const current = await k8s.api.listPersistentVolumeClaimForAllNamespaces(); + return current; + }, + verbs: ['add', 'update', 'delete'], + }); + this.#watcher.on('changed', this.#handleChange); + } + + #handleChange = async (pvc: Resource) => { + if (pvc.metadata?.annotations?.['volume.kubernetes.io/storage-provisioner'] !== PROVISIONER) { + return; + } + const target = `/data/volumes/${pvc.namespace}/${pvc.name}`; + try { + await mkdir(target, { recursive: true }); + } catch (err) { + console.error(err); + } + const resourceService = this.#services.get(ResourceService); + const pv = resourceService.get({ + apiVersion: 'v1', + kind: 'PersistentVolume', + name: `${pvc.namespace}-${pvc.name}`, + }); + await pv.load(); + await pv.patch({ + metadata: { + labels: { + provisioner: PROVISIONER, + }, + }, + spec: { + hostPath: { + path: target, + }, + capacity: { + storage: pvc.spec?.resources?.requests?.storage ?? '1Gi', + }, + persistentVolumeReclaimPolicy: 'Retain', + accessModes: pvc.spec?.accessModes, + claimRef: { + uid: pvc.metadata?.uid, + resourceVersion: pvc.metadata?.resourceVersion, + apiVersion: pvc.apiVersion, + name: pvc.name, + namespace: pvc.namespace, + }, + }, + }); + }; + + public start = async () => { + await this.#watcher.start(); + }; +} + +export { StorageProvider };