From 42e48034a25a0585109c9ed8acd8cffc9a2116bd Mon Sep 17 00:00:00 2001 From: Alp Celik Date: Mon, 16 Dec 2024 14:18:11 +0100 Subject: [PATCH] feat: Extend reconcileMode option for other resources --- Makefile | 4 + docs/crds/reconciliation-modes.md | 11 + examples/virtualmachineclone.yaml | 14 +- examples/virtualmachineset.yaml | 4 +- grafana/controller-runtime-metrics.json | 256 +++- .../custom-metrics-dashboard.json | 1218 +++++++++++++++++ .../proxmox/container_controller.go | 138 +- .../proxmox/customcertificate_controller.go | 71 +- .../managedvirtualmachine_controller.go | 105 +- .../proxmox/storagedownloadurl_controller.go | 70 +- .../proxmox/virtualmachine_controller.go | 22 +- .../proxmox/virtualmachineset_controller.go | 88 +- .../virtualmachinetemplate_controller.go | 68 +- mkdocs.yml | 1 + pkg/kubernetes/certificate.go | 18 + .../v1alpha1 => pkg/kubernetes}/constants.go | 4 +- pkg/kubernetes/kubernetes.go | 12 + pkg/proxmox/watcher.go | 8 +- service-monitor.yaml | 3 +- 19 files changed, 1860 insertions(+), 255 deletions(-) create mode 100644 docs/crds/reconciliation-modes.md rename {api/proxmox/v1alpha1 => pkg/kubernetes}/constants.go (88%) diff --git a/Makefile b/Makefile index 0d28809..5941da3 100644 --- a/Makefile +++ b/Makefile @@ -161,3 +161,7 @@ $(CONTROLLER_GEN): $(LOCALBIN) envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + +.PHONY: custom-dashboard +custom-dashboard: ## Run the custom dashboard generator. For more information check out https://kubebuilder.io/plugins/available/grafana-v1-alpha + kubebuilder edit --plugins grafana.kubebuilder.io/v1-alpha \ No newline at end of file diff --git a/docs/crds/reconciliation-modes.md b/docs/crds/reconciliation-modes.md new file mode 100644 index 0000000..a1765e4 --- /dev/null +++ b/docs/crds/reconciliation-modes.md @@ -0,0 +1,11 @@ +# Reconciliation Mode + +Kubemox is also supports different type of reconciliation modes to manage your `Kubemox` resources. You can define the annotation `proxmox.alperen.cloud/reconciliation-mode` in the `kubemox` objects to set the reconciliation mode. The default value is `Reconcile` which means the operator will reconcile the VirtualMachine object and handle the operations on Proxmox VE. You can find the all reconciliation modes in the table below. + +| Reconciliation Mode | Description | +|---------------------|-------------| +| Reconcile | The operator will reconcile the VirtualMachine object and handle the operations on Proxmox VE. | +| WatchOnly | The operator will only watch the VirtualMachine object and do not handle the operations on Proxmox VE. | +| EnsureExists | The operator will ensure the VirtualMachine exists on Proxmox VE. If the VirtualMachine does not exist, the operator will create it and do nothing further. | +| Disable | The operator will not reconcile the VirtualMachine object and do not handle the operations on Proxmox VE. This is useful if you would like to debug something or you would like to disable the operator for the specific VirtualMachine object for a while. | +| DryRun (Experimental) | The operator will not handle the operations on Proxmox VE. This is useful if you would like to test the VirtualMachine object without affecting the Proxmox VE. | diff --git a/examples/virtualmachineclone.yaml b/examples/virtualmachineclone.yaml index bc7c441..33b35fd 100644 --- a/examples/virtualmachineclone.yaml +++ b/examples/virtualmachineclone.yaml @@ -32,13 +32,13 @@ spec: network: - model: virtio bridge: vmbr0 - pciDevices: - - device: "c0-p0-o0-if0" - type: "mapped" - - device: "0000:03:00.2" - type: "raw" - primaryGPU: true - pcie: true +# pciDevices: +# - deviceID: "c0-p0-o0-if0" +# type: "mapped" +# - deviceID: "0000:03:00.2" +# type: "raw" +# primaryGPU: true +# pcie: true # Optional field to add additional features to the VM that doesn't included in the spec additionalConfig: balloon: "0" diff --git a/examples/virtualmachineset.yaml b/examples/virtualmachineset.yaml index 9814518..b3da1b1 100644 --- a/examples/virtualmachineset.yaml +++ b/examples/virtualmachineset.yaml @@ -25,9 +25,9 @@ spec: memory: 4096 # As MB # Disk used by the VM disk: - - storage: nvme + - storage: local-lvm size: 50 # As GB - device: scsi + device: scsi0 # Network interfaces used by the VM network: - model: virtio diff --git a/grafana/controller-runtime-metrics.json b/grafana/controller-runtime-metrics.json index 70023a4..c8eea4c 100644 --- a/grafana/controller-runtime-metrics.json +++ b/grafana/controller-runtime-metrics.json @@ -58,6 +58,62 @@ "title": "Reconciliation Metrics", "type": "row" }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 1 + }, + "id": 24, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.5.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "controller_runtime_active_workers{job=\"$job\", namespace=\"$namespace\"}", + "interval": "", + "legendFormat": "{{controller}} {{instance}}", + "refId": "A" + } + ], + "title": "Number of workers in use", + "type": "gauge" + }, { "datasource": "${DS_PROMETHEUS}", "description": "Total number of reconciliations per controller", @@ -67,6 +123,8 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -113,17 +171,18 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 0, + "h": 8, + "w": 11, + "x": 3, "y": 1 }, "id": 7, "options": { "legend": { "calcs": [], - "displayMode": "list", - "placement": "bottom" + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -154,6 +213,8 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -200,17 +261,18 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 12, + "h": 8, + "w": 10, + "x": 14, "y": 1 }, "id": 6, "options": { "legend": { "calcs": [], - "displayMode": "list", - "placement": "bottom" + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -238,13 +300,69 @@ "h": 1, "w": 24, "x": 0, - "y": 8 + "y": 9 }, "id": 11, "panels": [], "title": "Work Queue Metrics", "type": "row" }, + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 3, + "x": 0, + "y": 10 + }, + "id": 22, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.5.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "workqueue_depth{job=\"$job\", namespace=\"$namespace\"}", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "WorkQueue Depth", + "type": "gauge" + }, { "datasource": "${DS_PROMETHEUS}", "description": "How long in seconds an item stays in workqueue before being requested", @@ -254,6 +372,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -300,10 +420,10 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 9 + "h": 8, + "w": 11, + "x": 3, + "y": 10 }, "id": 13, "options": { @@ -312,8 +432,9 @@ "max", "mean" ], - "displayMode": "list", - "placement": "right" + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -359,6 +480,8 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -405,17 +528,18 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 9 + "h": 8, + "w": 10, + "x": 14, + "y": 10 }, "id": 15, "options": { "legend": { "calcs": [], - "displayMode": "list", - "placement": "bottom" + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -436,6 +560,64 @@ "title": "Work Queue Add Rate", "type": "timeseries" }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "How many seconds of work has done that is in progress and hasn't been observed by work_duration.\nLarge values indicate stuck threads.\nOne can deduce the number of stuck threads by observing the rate at which this increases.", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 3, + "x": 0, + "y": 18 + }, + "id": 23, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.5.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "rate(workqueue_unfinished_work_seconds{job=\"$job\", namespace=\"$namespace\"}[5m])", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Unfinished Seconds", + "type": "gauge" + }, { "datasource": "${DS_PROMETHEUS}", "description": "How long in seconds processing an item from workqueue takes.", @@ -445,6 +627,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -491,10 +675,10 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 16 + "h": 9, + "w": 11, + "x": 3, + "y": 18 }, "id": 19, "options": { @@ -504,7 +688,8 @@ "mean" ], "displayMode": "table", - "placement": "right" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -551,6 +736,8 @@ "mode": "continuous-GrYlRd" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -597,17 +784,18 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 16 + "h": 9, + "w": 10, + "x": 14, + "y": 18 }, "id": 17, "options": { "legend": { "calcs": [], - "displayMode": "list", - "placement": "bottom" + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", diff --git a/grafana/custom-metrics/custom-metrics-dashboard.json b/grafana/custom-metrics/custom-metrics-dashboard.json index 304f5ea..8bfbe78 100644 --- a/grafana/custom-metrics/custom-metrics-dashboard.json +++ b/grafana/custom-metrics/custom-metrics-dashboard.json @@ -562,6 +562,1224 @@ ], "title": "managed_virtualmachine_memory (gauge)", "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_container_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_container_cpu_cores)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "megabytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_container_memory)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_template_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_template_cpu_cores)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "megabytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_template_memory)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_set_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_set_cpu_cores)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "megabytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_set_memory)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_set_replicas)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_snapshot_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_virtual_machine_snapshot_policy_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_storage_download_url_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" + }, + + { + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24 + }, + "interval": "1m", + "links": [], + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.4.3", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": true, + "expr": "sum by (name, namespace) (kubemox_custom_certificate_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 10 + } + ], + "title": " (gauge)", + "type": "timeseries" } ], "refresh": "", diff --git a/internal/controller/proxmox/container_controller.go b/internal/controller/proxmox/container_controller.go index 8e12779..c09ee62 100644 --- a/internal/controller/proxmox/container_controller.go +++ b/internal/controller/proxmox/container_controller.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" + "github.com/alperencelik/kubemox/pkg/kubernetes" "github.com/alperencelik/kubemox/pkg/proxmox" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -78,14 +79,37 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( if err != nil { return ctrl.Result{}, r.handleResourceNotFound(ctx, err) } - logger.Info(fmt.Sprintf("Reconciling Container %s", container.Name)) + reconcileMode := kubernetes.GetReconcileMode(container) + + switch reconcileMode { + case kubernetes.ReconcileModeWatchOnly: + logger.Info(fmt.Sprintf("Reconciling Container %s in WatchOnly mode", container.Name)) + r.handleWatcher(ctx, req, container) + return ctrl.Result{}, nil + case kubernetes.ReconcileModeEnsureExists: + logger.Info(fmt.Sprintf("Reconciling Container %s in EnsureExists mode", container.Name)) + containerExists := proxmox.ContainerExists(container.Spec.Name, container.Spec.NodeName) + if !containerExists { + err = r.handleCloneContainer(ctx, container) + if err != nil { + logger.Error(err, "Failed to clone Container") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + return ctrl.Result{}, nil + case kubernetes.ReconcileModeDisable: + // Disable the reconciliation + logger.Info(fmt.Sprintf("Reconciling Container %s in Disable mode", container.Name)) + return ctrl.Result{}, nil + default: + // Continue with the normal reconciliation + break + } + logger.Info(fmt.Sprintf("Reconciling Container %s", container.Name)) // Handle the external watcher for the Container r.handleWatcher(ctx, req, container) - containerName := container.Spec.Name - nodeName := container.Spec.NodeName - // Check if the Container instance is marked to be deleted, which is indicated by the deletion timestamp being set. if container.ObjectMeta.DeletionTimestamp.IsZero() { // The object is not being deleted, so if it does not have our finalizer, then lets add the finalizer and update the object. @@ -98,50 +122,23 @@ func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // The object is being deleted if controllerutil.ContainsFinalizer(container, containerFinalizerName) { // Delete the Container - logger.Info("Deleting Container", "name", containerName) - - if !meta.IsStatusConditionPresentAndEqual(container.Status.Conditions, typeDeletingContainer, metav1.ConditionTrue) { - meta.SetStatusCondition(&container.Status.Conditions, metav1.Condition{ - Type: typeDeletingContainer, - Status: metav1.ConditionTrue, - Reason: "Deleting", - Message: "Deleting Container", - }) - if err = r.Status().Update(ctx, container); err != nil { - logger.Error(err, "Error updating Container status") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - // Stop the watcher if resource is being deleted - if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { - close(stopChan) - delete(r.Watchers.Watchers, req.Name) - } - // Handle deletion of the Container - r.handleContainerDeletion(ctx, container) - // Remove finalizer - controllerutil.RemoveFinalizer(container, containerFinalizerName) - if err = r.Update(ctx, container); err != nil { - logger.Error(err, "Error updating Container") + res, delErr := r.handleDelete(ctx, req, container) + if delErr != nil { + logger.Error(delErr, "Failed to delete Container") + return res, client.IgnoreNotFound(delErr) } } // Stop reconciliation as the item is being deleted return ctrl.Result{}, client.IgnoreNotFound(err) } - containerExists := proxmox.ContainerExists(containerName, nodeName) - if containerExists { - err = r.StartOrUpdateContainer(ctx, container) - if err != nil { - logger.Error(err, "Failed to start or update Container") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - } else { - err = r.handleCloneContainer(ctx, container) - if err != nil { - logger.Error(err, "Failed to clone Container") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } + + result, err := r.handleContainerOperations(ctx, container) + if err != nil { + logger.Error(err, "Failed to handle Container operations") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + if result.Requeue { + return result, nil } return ctrl.Result{}, client.IgnoreNotFound(err) @@ -271,6 +268,61 @@ func (r *ContainerReconciler) handleCloneContainer(ctx context.Context, containe return nil } +func (r *ContainerReconciler) handleDelete(ctx context.Context, req ctrl.Request, container *proxmoxv1alpha1.Container) ( + ctrl.Result, error) { + logger := log.FromContext(ctx) + var err error + logger.Info("Deleting Container", "name", container.Spec.Name) + + if !meta.IsStatusConditionPresentAndEqual(container.Status.Conditions, typeDeletingContainer, metav1.ConditionTrue) { + meta.SetStatusCondition(&container.Status.Conditions, metav1.Condition{ + Type: typeDeletingContainer, + Status: metav1.ConditionTrue, + Reason: "Deleting", + Message: "Deleting Container", + }) + if err = r.Status().Update(ctx, container); err != nil { + logger.Error(err, "Error updating Container status") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + // Stop the watcher if resource is being deleted + if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { + close(stopChan) + delete(r.Watchers.Watchers, req.Name) + } + // Handle deletion of the Container + r.handleContainerDeletion(ctx, container) + // Remove finalizer + logger.Info("Removing finalizer from Container", "name", container.Spec.Name) + controllerutil.RemoveFinalizer(container, containerFinalizerName) + if err = r.Update(ctx, container); err != nil { + logger.Error(err, "Error updating Container") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + return ctrl.Result{}, nil +} + +func (r *ContainerReconciler) handleContainerOperations(ctx context.Context, container *proxmoxv1alpha1.Container) (ctrl.Result, error) { + logger := log.FromContext(ctx) + containerExists := proxmox.ContainerExists(container.Spec.Name, container.Spec.NodeName) + if containerExists { + err := r.StartOrUpdateContainer(ctx, container) + if err != nil { + logger.Error(err, "Failed to start or update Container") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } else { + err := r.handleCloneContainer(ctx, container) + if err != nil { + logger.Error(err, "Failed to clone Container") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + return ctrl.Result{}, nil +} + func (r *ContainerReconciler) handleAutoStart(ctx context.Context, container *proxmoxv1alpha1.Container) (ctrl.Result, error) { logger := log.FromContext(ctx) diff --git a/internal/controller/proxmox/customcertificate_controller.go b/internal/controller/proxmox/customcertificate_controller.go index d3b863d..c5f0e51 100644 --- a/internal/controller/proxmox/customcertificate_controller.go +++ b/internal/controller/proxmox/customcertificate_controller.go @@ -69,6 +69,10 @@ const ( // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile + +// CustomCertifaceController implements two main features: +// 1. It creates a certificate using cert-manager and stores it in a secret +// 2. It updates the Proxmox node with the new certificate func (r *CustomCertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) @@ -91,40 +95,15 @@ func (r *CustomCertificateReconciler) Reconcile(ctx context.Context, req ctrl.Re // The object is being deleted if controllerutil.ContainsFinalizer(customCert, customCertificateFinalizerName) { // Delete the custom certificate - logger.Info("Deleting the CustomCertificate") - - // Update the condition for the CustomCertificate if it is being deleted - if !meta.IsStatusConditionPresentAndEqual(customCert.Status.Conditions, typeDeletingCustomCertificate, metav1.ConditionTrue) { - meta.SetStatusCondition(&customCert.Status.Conditions, metav1.Condition{ - Type: typeDeletingCustomCertificate, - Status: metav1.ConditionTrue, - Reason: "Deleting", - Message: "Deleting CustomCertificate", - }) - if err = r.Status().Update(ctx, customCert); err != nil { - logger.Error(err, "Error updating CustomCertificate status") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - } else { - return ctrl.Result{}, nil - } - // Delete the custom certificate from Proxmox - proxmox.DeleteCustomCertificate(customCert.Spec.NodeName) - // Remove the finalizer - logger.Info("Removing finalizer from CustomCertificate") - - controllerutil.RemoveFinalizer(customCert, customCertificateFinalizerName) - if err = r.Update(ctx, customCert); err != nil { - log.Log.Error(err, "Error updating CustomCertificate") - return ctrl.Result{}, client.IgnoreNotFound(err) + res, delErr := r.handleDelete(ctx, customCert) + if delErr != nil { + logger.Error(delErr, "unable to delete CustomCertificate") + return res, delErr } } // Stop reconciliation as the item is being deleted return ctrl.Result{}, client.IgnoreNotFound(err) } - // This controller implements two main features: - // 1. It creates a certificate using cert-manager and stores it in a secret - // 2. It updates the Proxmox node with the new certificate // Create a certificate using cert-manager // 1. Create a Certificate resource @@ -200,3 +179,37 @@ func (r *CustomCertificateReconciler) handleResourceNotFound(ctx context.Context logger.Error(err, "Failed to get CustomCertificate") return err } + +func (r *CustomCertificateReconciler) handleDelete(ctx context.Context, + customCert *proxmoxv1alpha1.CustomCertificate) (ctrl.Result, error) { + logger := log.FromContext(ctx) + var err error + logger.Info("Deleting the CustomCertificate") + + // Update the condition for the CustomCertificate if it is being deleted + if !meta.IsStatusConditionPresentAndEqual(customCert.Status.Conditions, typeDeletingCustomCertificate, metav1.ConditionTrue) { + meta.SetStatusCondition(&customCert.Status.Conditions, metav1.Condition{ + Type: typeDeletingCustomCertificate, + Status: metav1.ConditionTrue, + Reason: "Deleting", + Message: "Deleting CustomCertificate", + }) + if err = r.Status().Update(ctx, customCert); err != nil { + logger.Error(err, "Error updating CustomCertificate status") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } else { + return ctrl.Result{}, nil + } + // Delete the custom certificate from Proxmox + proxmox.DeleteCustomCertificate(customCert.Spec.NodeName) + // Remove the finalizer + logger.Info("Removing finalizer from CustomCertificate") + + controllerutil.RemoveFinalizer(customCert, customCertificateFinalizerName) + if err = r.Update(ctx, customCert); err != nil { + log.Log.Error(err, "Error updating CustomCertificate") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + return ctrl.Result{}, nil +} diff --git a/internal/controller/proxmox/managedvirtualmachine_controller.go b/internal/controller/proxmox/managedvirtualmachine_controller.go index c4893f9..2ac8e91 100644 --- a/internal/controller/proxmox/managedvirtualmachine_controller.go +++ b/internal/controller/proxmox/managedvirtualmachine_controller.go @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" proxmoxv1alpha1 "github.com/alperencelik/kubemox/api/proxmox/v1alpha1" + "github.com/alperencelik/kubemox/pkg/kubernetes" "github.com/alperencelik/kubemox/pkg/proxmox" ) @@ -82,6 +83,23 @@ func (r *ManagedVirtualMachineReconciler) Reconcile(ctx context.Context, req ctr if err != nil { return ctrl.Result{}, r.handleResourceNotFound(ctx, err) } + + reconcileMode := kubernetes.GetReconcileMode(managedVM) + + switch reconcileMode { + case kubernetes.ReconcileModeWatchOnly: + logger.Info(fmt.Sprintf("Reconciling ManagedVirtualMachine %s in WatchOnly mode", managedVM.Name)) + r.handleWatcher(ctx, req, managedVM) + return ctrl.Result{}, nil + case kubernetes.ReconcileModeDisable: + // Disable the reconciliation + logger.Info(fmt.Sprintf("Reconciliation is disabled for VirtualMachine %s", managedVM.Name)) + return ctrl.Result{}, nil + default: + // Normal mode + break + } + logger.Info(fmt.Sprintf("Reconciling ManagedVirtualMachine %s", managedVM.Name)) // Handle the external watcher for the ManagedVirtualMachine @@ -99,44 +117,14 @@ func (r *ManagedVirtualMachineReconciler) Reconcile(ctx context.Context, req ctr // The object is being deleted if controllerutil.ContainsFinalizer(managedVM, managedvirtualMachineFinalizerName) { logger.Info(fmt.Sprintf("Deleting ManagedVirtualMachine %s", managedVM.Name)) - - if !meta.IsStatusConditionPresentAndEqual(managedVM.Status.Conditions, typeDeletingManagedVirtualMachine, metav1.ConditionTrue) { - meta.SetStatusCondition(&managedVM.Status.Conditions, metav1.Condition{ - Type: typeDeletingManagedVirtualMachine, - Status: metav1.ConditionTrue, - Reason: "Deleting", - Message: "Deleting ManagedVirtualMachine", - }) - if err = r.Status().Update(ctx, managedVM); err != nil { - logger.Error(err, "Error updating ManagedVirtualMachine status") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } + // Delete ManagedVM + res, delErr := r.handleDelete(ctx, req, managedVM) + if delErr != nil { + logger.Error(delErr, "unable to delete ManagedVirtualMachine") + return res, delErr } - // Stop the watcher if resource is being deleted - if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { - close(stopChan) - delete(r.Watchers.Watchers, req.Name) - } - - // Delete the VM - r.Recorder.Event(managedVM, "Normal", "Deleting", fmt.Sprintf("Deleting ManagedVirtualMachine %s", managedVM.Name)) - if !managedVM.Spec.DeletionProtection { - proxmox.DeleteVM(managedVM.Name, managedVM.Spec.NodeName) - } else { - // Remove managedVirtualMachineTag from Managed Virtual Machine to not manage it anymore - err = proxmox.RemoveVirtualMachineTag(managedVM.Name, managedVM.Spec.NodeName, proxmox.ManagedVirtualMachineTag) - if err != nil { - logger.Error(err, "Error removing managedVirtualMachineTag from Managed Virtual Machine") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - } - } - logger.Info("Removing finalizer from ManagedVirtualMachine", "name", managedVM.Name) - // Remove finalizer - controllerutil.RemoveFinalizer(managedVM, managedvirtualMachineFinalizerName) - if err = r.Update(ctx, managedVM); err != nil { - logger.Info(fmt.Sprintf("Error updating ManagedVirtualMachine %s", managedVM.Name)) } + // Stop reconciliation as the item is being deleted return ctrl.Result{}, nil } // If EnableAutoStart is true, start the VM if it's stopped @@ -293,3 +281,48 @@ func (r *ManagedVirtualMachineReconciler) UpdateManagedVirtualMachineStatus(ctx func (r *ManagedVirtualMachineReconciler) IsResourceReady(obj proxmox.Resource) (bool, error) { return proxmox.IsVirtualMachineReady(obj.(*proxmoxv1alpha1.ManagedVirtualMachine)) } + +func (r *ManagedVirtualMachineReconciler) handleDelete(ctx context.Context, req ctrl.Request, + managedVM *proxmoxv1alpha1.ManagedVirtualMachine) ( + ctrl.Result, error) { + logger := log.FromContext(ctx) + var err error + + if !meta.IsStatusConditionPresentAndEqual(managedVM.Status.Conditions, typeDeletingManagedVirtualMachine, metav1.ConditionTrue) { + meta.SetStatusCondition(&managedVM.Status.Conditions, metav1.Condition{ + Type: typeDeletingManagedVirtualMachine, + Status: metav1.ConditionTrue, + Reason: "Deleting", + Message: "Deleting ManagedVirtualMachine", + }) + if err = r.Status().Update(ctx, managedVM); err != nil { + logger.Error(err, "Error updating ManagedVirtualMachine status") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + // Stop the watcher if resource is being deleted + if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { + close(stopChan) + delete(r.Watchers.Watchers, req.Name) + } + + // Delete the VM + r.Recorder.Event(managedVM, "Normal", "Deleting", fmt.Sprintf("Deleting ManagedVirtualMachine %s", managedVM.Name)) + if !managedVM.Spec.DeletionProtection { + proxmox.DeleteVM(managedVM.Name, managedVM.Spec.NodeName) + } else { + // Remove managedVirtualMachineTag from Managed Virtual Machine to not manage it anymore + err = proxmox.RemoveVirtualMachineTag(managedVM.Name, managedVM.Spec.NodeName, proxmox.ManagedVirtualMachineTag) + if err != nil { + logger.Error(err, "Error removing managedVirtualMachineTag from Managed Virtual Machine") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + logger.Info("Removing finalizer from ManagedVirtualMachine", "name", managedVM.Name) + // Remove finalizer + controllerutil.RemoveFinalizer(managedVM, managedvirtualMachineFinalizerName) + if err = r.Update(ctx, managedVM); err != nil { + logger.Info(fmt.Sprintf("Error updating ManagedVirtualMachine %s", managedVM.Name)) + } + return ctrl.Result{}, nil +} diff --git a/internal/controller/proxmox/storagedownloadurl_controller.go b/internal/controller/proxmox/storagedownloadurl_controller.go index 73ec27f..690e98a 100644 --- a/internal/controller/proxmox/storagedownloadurl_controller.go +++ b/internal/controller/proxmox/storagedownloadurl_controller.go @@ -34,6 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" proxmoxv1alpha1 "github.com/alperencelik/kubemox/api/proxmox/v1alpha1" + "github.com/alperencelik/kubemox/pkg/kubernetes" "github.com/alperencelik/kubemox/pkg/proxmox" ) @@ -82,6 +83,15 @@ func (r *StorageDownloadURLReconciler) Reconcile(ctx context.Context, req ctrl.R if err != nil { return ctrl.Result{}, r.handleResourceNotFound(ctx, err) } + reconcileMode := kubernetes.GetReconcileMode(storageDownloadURL) + + switch reconcileMode { + case kubernetes.ReconcileModeDisable: + logger.Info("Reconciliation is disabled for the StorageDownloadURL") + return ctrl.Result{}, nil + default: + break + } logger.Info(fmt.Sprintf("Reconciling StorageDownloadURL %s", storageDownloadURL.Name)) @@ -99,29 +109,15 @@ func (r *StorageDownloadURLReconciler) Reconcile(ctx context.Context, req ctrl.R } } else { if controllerutil.ContainsFinalizer(storageDownloadURL, storageDownloadURLFinalizerName) { - if !meta.IsStatusConditionPresentAndEqual(storageDownloadURL.Status.Conditions, typeDeletingStorageDownloadURL, metav1.ConditionTrue) { - meta.SetStatusCondition(&storageDownloadURL.Status.Conditions, metav1.Condition{ - Type: typeDeletingStorageDownloadURL, - Status: metav1.ConditionTrue, - Reason: "Deleting", - Message: "Deleting StorageDownloadURL", - }) - if err = r.Status().Update(ctx, storageDownloadURL); err != nil { - logger.Error(err, "unable to update StorageDownloadURL status") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } + // Delete the storage download URL + res, delErr := r.handleDelete(ctx, storageDownloadURL) + if delErr != nil { + logger.Error(delErr, "unable to delete StorageDownloadURL") + return res, delErr + } + if res.Requeue { + return res, nil } - } - // Delete the file from the storage - err = proxmox.DeleteStorageContent(storage, &storageDownloadURL.Spec) - if err != nil { - logger.Error(err, "unable to delete the file") - return ctrl.Result{}, client.IgnoreNotFound(err) - } - controllerutil.RemoveFinalizer(storageDownloadURL, storageDownloadURLFinalizerName) - if err = r.Update(ctx, storageDownloadURL); err != nil { - logger.Error(err, "unable to update StorageDownloadURL") - return ctrl.Result{}, client.IgnoreNotFound(err) } // Stop reconciliation as the object is being deleted return ctrl.Result{}, nil @@ -238,3 +234,33 @@ func (r *StorageDownloadURLReconciler) handleDownloadURL(ctx context.Context, } return nil } + +func (r *StorageDownloadURLReconciler) handleDelete(ctx context.Context, + storageDownloadURL *proxmoxv1alpha1.StorageDownloadURL) (ctrl.Result, error) { + logger := log.FromContext(ctx) + var err error + if !meta.IsStatusConditionPresentAndEqual(storageDownloadURL.Status.Conditions, typeDeletingStorageDownloadURL, metav1.ConditionTrue) { + meta.SetStatusCondition(&storageDownloadURL.Status.Conditions, metav1.Condition{ + Type: typeDeletingStorageDownloadURL, + Status: metav1.ConditionTrue, + Reason: "Deleting", + Message: "Deleting StorageDownloadURL", + }) + if err = r.Status().Update(ctx, storageDownloadURL); err != nil { + logger.Error(err, "unable to update StorageDownloadURL status") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + // Delete the file from the storage + err = proxmox.DeleteStorageContent(storageDownloadURL.Spec.Storage, &storageDownloadURL.Spec) + if err != nil { + logger.Error(err, "unable to delete the file") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + controllerutil.RemoveFinalizer(storageDownloadURL, storageDownloadURLFinalizerName) + if err = r.Update(ctx, storageDownloadURL); err != nil { + logger.Error(err, "unable to update StorageDownloadURL") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + return ctrl.Result{}, nil +} diff --git a/internal/controller/proxmox/virtualmachine_controller.go b/internal/controller/proxmox/virtualmachine_controller.go index 06a7f22..f239024 100644 --- a/internal/controller/proxmox/virtualmachine_controller.go +++ b/internal/controller/proxmox/virtualmachine_controller.go @@ -80,14 +80,14 @@ func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque if err != nil { return ctrl.Result{}, r.handleResourceNotFound(ctx, err) } - reconcileMode := r.getReconcileMode(vm) + reconcileMode := kubernetes.GetReconcileMode(vm) switch reconcileMode { - case proxmoxv1alpha1.ReconcileModeWatchOnly: + case kubernetes.ReconcileModeWatchOnly: logger.Info(fmt.Sprintf("Reconciliation is watch only for VirtualMachine %s", vm.Name)) r.handleWatcher(ctx, req, vm) return ctrl.Result{}, nil - case proxmoxv1alpha1.ReconcileModeEnsureExists: + case kubernetes.ReconcileModeEnsureExists: logger.Info(fmt.Sprintf("Reconciliation is ensure exists for VirtualMachine %s", vm.Name)) vmExists := proxmox.CheckVM(vm.Spec.Name, vm.Spec.NodeName) if !vmExists { @@ -99,7 +99,7 @@ func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque } } return ctrl.Result{}, nil - case proxmoxv1alpha1.ReconcileModeDisable: + case kubernetes.ReconcileModeDisable: // Disable the reconciliation logger.Info(fmt.Sprintf("Reconciliation is disabled for VirtualMachine %s", vm.Name)) return ctrl.Result{}, nil @@ -126,7 +126,7 @@ func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Delete the VM res, delErr := r.handleDelete(ctx, req, vm) if delErr != nil { - logger.Error(err, "Error handling VirtualMachine deletion") + logger.Error(delErr, "Error handling VirtualMachine deletion") return res, client.IgnoreNotFound(delErr) } } @@ -452,18 +452,6 @@ func (r *VirtualMachineReconciler) handleAdditionalConfig(ctx context.Context, v return nil } -func (r *VirtualMachineReconciler) getReconcileMode(vm *proxmoxv1alpha1.VirtualMachine) string { - // Get the annotations and find out the reconcile mode - annotations := vm.GetAnnotations() - if annotations == nil { - return "Normal" - } - if mode, ok := annotations[proxmoxv1alpha1.ReconcileModeAnnotation]; ok { - return mode - } - return "Normal" -} - func (r *VirtualMachineReconciler) handleWatcher(ctx context.Context, req ctrl.Request, vm *proxmoxv1alpha1.VirtualMachine) { r.Watchers.HandleWatcher(ctx, req, func(ctx context.Context, stopChan chan struct{}) (ctrl.Result, error) { return proxmox.StartWatcher(ctx, vm, stopChan, r.fetchResource, r.updateStatus, diff --git a/internal/controller/proxmox/virtualmachineset_controller.go b/internal/controller/proxmox/virtualmachineset_controller.go index 760d74d..3d3102e 100644 --- a/internal/controller/proxmox/virtualmachineset_controller.go +++ b/internal/controller/proxmox/virtualmachineset_controller.go @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" proxmoxv1alpha1 "github.com/alperencelik/kubemox/api/proxmox/v1alpha1" + "github.com/alperencelik/kubemox/pkg/kubernetes" "github.com/alperencelik/kubemox/pkg/proxmox" ) @@ -80,6 +81,18 @@ func (r *VirtualMachineSetReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, r.handleResourceNotFound(ctx, err) } + reconcileMode := kubernetes.GetReconcileMode(vmSet) + + switch reconcileMode { + case kubernetes.ReconcileModeDisable: + // Disable the reconciliation + logger.Info(fmt.Sprintf("Reconciliation is disabled for VirtualMachineSet %s", vmSet.Name)) + return ctrl.Result{}, nil + default: + // Normal mode + break + } + logger.Info(fmt.Sprintf("Reconciling VirtualMachineSet %s", vmSet.Name)) vmList := &proxmoxv1alpha1.VirtualMachineList{} @@ -105,37 +118,13 @@ func (r *VirtualMachineSetReconciler) Reconcile(ctx context.Context, req ctrl.Re if controllerutil.ContainsFinalizer(vmSet, virtualMachineSetFinalizerName) { // Ensure that the pre-delete logic is idempotent. logger.Info(fmt.Sprintf("Deleting VirtualMachineSet %s", vmSet.Name)) - - if !meta.IsStatusConditionPresentAndEqual(vmSet.Status.Conditions, typeDeletingVirtualMachineSet, metav1.ConditionUnknown) { - meta.SetStatusCondition(&vmSet.Status.Conditions, metav1.Condition{ - Type: "Deleting", - Status: metav1.ConditionUnknown, - Reason: "Deleting", - Message: "Deleting VirtualMachineSet", - }) - if err = r.Status().Update(ctx, vmSet); err != nil { - logger.Info("Error updating VirtualMachineSet status") - return ctrl.Result{}, client.IgnoreNotFound(err) - } - } - // Get VM list and delete them - for i := range vmList.Items { - vm := &vmList.Items[i] - if err = r.Delete(ctx, vm); err != nil { - logger.Error(err, "unable to delete VirtualMachine") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } + res, delErr := r.handleDelete(ctx, vmSet, vmList) + if delErr != nil { + logger.Error(delErr, "unable to delete VirtualMachineSet") + return res, delErr } - - if len(vmList.Items) == 0 { - // Remove finalizer - if ok := controllerutil.RemoveFinalizer(vmSet, virtualMachineSetFinalizerName); !ok { - logger.Error(err, "Error removing finalizer from VirtualMachineSet") - } - if err = r.Update(ctx, vmSet); err != nil { - logger.Error(err, "Error updating VirtualMachineSet") - } - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + if res.Requeue { + return res, nil } } // Requeue the request until the vmSet has no VirtualMachines @@ -342,3 +331,42 @@ func (r *VirtualMachineSetReconciler) handleFinalizer(ctx context.Context, vmSet } return nil } + +func (r *VirtualMachineSetReconciler) handleDelete(ctx context.Context, vmSet *proxmoxv1alpha1.VirtualMachineSet, + vmList *proxmoxv1alpha1.VirtualMachineList) (ctrl.Result, error) { + logger := log.FromContext(ctx) + var err error + + if !meta.IsStatusConditionPresentAndEqual(vmSet.Status.Conditions, typeDeletingVirtualMachineSet, metav1.ConditionUnknown) { + meta.SetStatusCondition(&vmSet.Status.Conditions, metav1.Condition{ + Type: "Deleting", + Status: metav1.ConditionUnknown, + Reason: "Deleting", + Message: "Deleting VirtualMachineSet", + }) + if err = r.Status().Update(ctx, vmSet); err != nil { + logger.Info("Error updating VirtualMachineSet status") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + } + // Get VM list and delete them + for i := range vmList.Items { + vm := &vmList.Items[i] + if err = r.Delete(ctx, vm); err != nil { + logger.Error(err, "unable to delete VirtualMachine") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } + + if len(vmList.Items) == 0 { + // Remove finalizer + if ok := controllerutil.RemoveFinalizer(vmSet, virtualMachineSetFinalizerName); !ok { + logger.Error(err, "Error removing finalizer from VirtualMachineSet") + } + if err = r.Update(ctx, vmSet); err != nil { + logger.Error(err, "Error updating VirtualMachineSet") + } + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + return ctrl.Result{}, nil +} diff --git a/internal/controller/proxmox/virtualmachinetemplate_controller.go b/internal/controller/proxmox/virtualmachinetemplate_controller.go index 8fff92d..563c81d 100644 --- a/internal/controller/proxmox/virtualmachinetemplate_controller.go +++ b/internal/controller/proxmox/virtualmachinetemplate_controller.go @@ -103,34 +103,13 @@ func (r *VirtualMachineTemplateReconciler) Reconcile(ctx context.Context, req ct // The object is being deleted if controllerutil.ContainsFinalizer(vmTemplate, virtualMachineTemplateFinalizerName) { // Update the condition for the VirtualMachineTemplate if it's not already deleting - if !meta.IsStatusConditionPresentAndEqual(vmTemplate.Status.Conditions, typeDeletingVirtualMachineTemplate, metav1.ConditionUnknown) { - meta.SetStatusCondition(&vmTemplate.Status.Conditions, metav1.Condition{ - Type: typeDeletingVirtualMachineTemplate, - Status: metav1.ConditionUnknown, - Reason: "Deleting", - Message: "VirtualMachineTemplate is being deleted", - }) - if err := r.Status().Update(ctx, vmTemplate); err != nil { - logger.Error(err, "Failed to update VirtualMachineTemplate status") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) - } - } else { - return ctrl.Result{}, nil + res, delErr := r.handleDelete(ctx, req, vmTemplate) + if delErr != nil { + logger.Error(delErr, "Failed to delete VirtualMachineTemplate") + return res, delErr } - // TODO: Stop the watcher if resource is being deleted - // Stop the watcher if resource is being deleted - if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { - close(stopChan) - delete(r.Watchers.Watchers, req.Name) - } - // Delete the VirtualMachineTemplate - r.deleteVirtualMachineTemplate(ctx, vmTemplate) - // Remove the finalizer - logger.Info("Removing finalizer from VirtualMachineTemplate", "name", vmTemplate.Name) - controllerutil.RemoveFinalizer(vmTemplate, virtualMachineTemplateFinalizerName) - if err := r.Update(ctx, vmTemplate); err != nil { - logger.Error(err, "Failed to remove VirtualMachineTemplate finalizer") - return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + if res.Requeue { + return res, nil } } // Stop the reconciliation as the object is being deleted @@ -460,3 +439,38 @@ func (r *VirtualMachineTemplateReconciler) handleReconcileFunc(ctx context.Conte func (r *VirtualMachineTemplateReconciler) IsResourceReady(obj proxmox.Resource) (bool, error) { return proxmox.IsVirtualMachineReady(obj.(*proxmoxv1alpha1.VirtualMachineTemplate)) } + +func (r *VirtualMachineTemplateReconciler) handleDelete(ctx context.Context, + req ctrl.Request, vmTemplate *proxmoxv1alpha1.VirtualMachineTemplate) (ctrl.Result, error) { + logger := log.FromContext(ctx) + if !meta.IsStatusConditionPresentAndEqual(vmTemplate.Status.Conditions, typeDeletingVirtualMachineTemplate, metav1.ConditionUnknown) { + meta.SetStatusCondition(&vmTemplate.Status.Conditions, metav1.Condition{ + Type: typeDeletingVirtualMachineTemplate, + Status: metav1.ConditionUnknown, + Reason: "Deleting", + Message: "VirtualMachineTemplate is being deleted", + }) + if err := r.Status().Update(ctx, vmTemplate); err != nil { + logger.Error(err, "Failed to update VirtualMachineTemplate status") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + } else { + return ctrl.Result{}, nil + } + // TODO: Stop the watcher if resource is being deleted + // Stop the watcher if resource is being deleted + if stopChan, exists := r.Watchers.Watchers[req.Name]; exists { + close(stopChan) + delete(r.Watchers.Watchers, req.Name) + } + // Delete the VirtualMachineTemplate + r.deleteVirtualMachineTemplate(ctx, vmTemplate) + // Remove the finalizer + logger.Info("Removing finalizer from VirtualMachineTemplate", "name", vmTemplate.Name) + controllerutil.RemoveFinalizer(vmTemplate, virtualMachineTemplateFinalizerName) + if err := r.Update(ctx, vmTemplate); err != nil { + logger.Error(err, "Failed to remove VirtualMachineTemplate finalizer") + return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err) + } + return ctrl.Result{}, nil +} diff --git a/mkdocs.yml b/mkdocs.yml index 76300c1..d419a0d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,6 +20,7 @@ nav: - Certificates: - CustomCertificate: crds/customcertificate.md - metrics.md +- reconciliation-modes.md - contributing.md - roadmap.md - Releases ⧉: https://github.com/alperencelik/kubemox/releases diff --git a/pkg/kubernetes/certificate.go b/pkg/kubernetes/certificate.go index 69a6858..06bad57 100644 --- a/pkg/kubernetes/certificate.go +++ b/pkg/kubernetes/certificate.go @@ -34,6 +34,11 @@ func CreateCertificate(customCert *proxmoxv1alpha1.CustomCertificate) (*unstruct secretName := certManagerSpec.SecretName usages := certManagerSpec.Usages + // Check if secret ref exists + if !CheckSecretExists(secretName, customCert.ObjectMeta.Namespace) { + return nil, fmt.Errorf("secret %s does not exist in namespace %s", secretName, customCert.ObjectMeta.Namespace) + } + certManagerCertificate := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "cert-manager.io/v1", @@ -176,3 +181,16 @@ func findCertManagerNamespace() string { } return "" } + +func CheckSecretExists(secretName, namespace string) bool { + // Check if secret exists + _, err := Clientset.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) + switch { + case errors.IsNotFound(err): + return false + case err != nil: + panic(err) + default: + return true + } +} diff --git a/api/proxmox/v1alpha1/constants.go b/pkg/kubernetes/constants.go similarity index 88% rename from api/proxmox/v1alpha1/constants.go rename to pkg/kubernetes/constants.go index 8505a8c..c187358 100644 --- a/api/proxmox/v1alpha1/constants.go +++ b/pkg/kubernetes/constants.go @@ -1,11 +1,11 @@ -package v1alpha1 +package kubernetes const ( // ReconcileModeAnnotation is the annotation key for the reconcile mode // ReconcileMode is the mode of the reconciliation it could be Normal, WatchOnly, EnsureExists, Disable, DryRun (to be implemented) ReconcileModeAnnotation = "proxmox.alperen.cloud/reconcile-mode" - ReconcileModeNormal = "Normal" + ReconcileModeNormal = "Reconcile" ReconcileModeWatchOnly = "WatchOnly" ReconcileModeEnsureExists = "EnsureExists" ReconcileModeDisable = "Disable" diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index ce996ec..5bdf7a3 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -9,6 +9,7 @@ import ( apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -67,3 +68,14 @@ func GetSecretData(namespace string, selector *corev1.SecretKeySelector) (string } return string(value), nil } + +func GetReconcileMode(resource client.Object) string { + annotations := resource.GetAnnotations() + if annotations == nil { + return ReconcileModeNormal + } + if mode, ok := annotations[ReconcileModeAnnotation]; ok { + return mode + } + return ReconcileModeNormal +} diff --git a/pkg/proxmox/watcher.go b/pkg/proxmox/watcher.go index ddd69fc..c101643 100644 --- a/pkg/proxmox/watcher.go +++ b/pkg/proxmox/watcher.go @@ -6,7 +6,7 @@ import ( "sync" "time" - proxmoxv1alpha1 "github.com/alperencelik/kubemox/api/proxmox/v1alpha1" + "github.com/alperencelik/kubemox/pkg/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -111,9 +111,9 @@ func StartWatcher(ctx context.Context, resource Resource, logger.Error(err, "Error updating resource status") return ctrl.Result{}, err } - // If the reconcileMode is WatchOnly then we don't need to check for configuration drift - val := resource.GetAnnotations()[proxmoxv1alpha1.ReconcileModeAnnotation] - if val == proxmoxv1alpha1.ReconcileModeWatchOnly || val == proxmoxv1alpha1.ReconcileModeEnsureExists { + // If the reconcileMode is WatchOnly or EnsureExists then we don't need to check for configuration drift + val := resource.GetAnnotations()[kubernetes.ReconcileModeAnnotation] + if val == kubernetes.ReconcileModeWatchOnly || val == kubernetes.ReconcileModeEnsureExists { continue } triggerReconcile, err := checkDelta(resource) diff --git a/service-monitor.yaml b/service-monitor.yaml index 904d36c..bbe1c00 100644 --- a/service-monitor.yaml +++ b/service-monitor.yaml @@ -3,8 +3,7 @@ kind: ServiceMonitor metadata: name: kubemox-monitor labels: - prometheus: kube-prom-stack - release: kube-prom-stack + release: kube-prometheus-stack spec: selector: matchLabels: