diff --git a/charts/kubemirror/Chart.yaml b/charts/kubemirror/Chart.yaml new file mode 100644 index 0000000..6dcc110 --- /dev/null +++ b/charts/kubemirror/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: kubemirror +description: Kubernetes controller for mirroring resources across namespaces +type: application +version: 0.2.8 +appVersion: "0.2.8" +keywords: + - kubernetes + - controller + - mirror + - secrets + - configmaps +home: https://github.com/lukaszraczylo/kubemirror +sources: + - https://github.com/lukaszraczylo/kubemirror +maintainers: + - name: Lukasz Raczylo + email: lukasz@raczylo.com diff --git a/charts/kubemirror/templates/NOTES.txt b/charts/kubemirror/templates/NOTES.txt new file mode 100644 index 0000000..5709c23 --- /dev/null +++ b/charts/kubemirror/templates/NOTES.txt @@ -0,0 +1,36 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }} + +KubeMirror is now running and will automatically mirror resources across namespaces. + +To mirror a Secret or ConfigMap, add this LABEL and ANNOTATIONS: + + # Label (required for server-side filtering): + kubemirror.raczylo.com/enabled: "true" + + # Annotations: + kubemirror.raczylo.com/sync: "true" + kubemirror.raczylo.com/target-namespaces: "namespace1,namespace2" + +Or use "all" to mirror to all namespaces with the allow-mirrors label: + + kubemirror.raczylo.com/target-namespaces: "all" + +To allow a namespace to receive mirrored resources, add this label: + + kubemirror.raczylo.com/allow-mirrors: "true" + +View controller logs: + + $ kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ include "kubemirror.name" . }} + +Check metrics: + + $ kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "kubemirror.fullname" . }}-metrics {{ .Values.service.metricsPort }}:{{ .Values.service.metricsPort }} + $ curl http://localhost:{{ .Values.service.metricsPort }}/metrics diff --git a/charts/kubemirror/templates/_helpers.tpl b/charts/kubemirror/templates/_helpers.tpl new file mode 100644 index 0000000..04d3ab0 --- /dev/null +++ b/charts/kubemirror/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "kubemirror.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "kubemirror.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "kubemirror.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "kubemirror.labels" -}} +helm.sh/chart: {{ include "kubemirror.chart" . }} +{{ include "kubemirror.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "kubemirror.selectorLabels" -}} +app.kubernetes.io/name: {{ include "kubemirror.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "kubemirror.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "kubemirror.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/kubemirror/templates/clusterrole.yaml b/charts/kubemirror/templates/clusterrole.yaml new file mode 100644 index 0000000..b8ce4f0 --- /dev/null +++ b/charts/kubemirror/templates/clusterrole.yaml @@ -0,0 +1,57 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "kubemirror.fullname" . }} + labels: + {{- include "kubemirror.labels" . | nindent 4 }} +rules: + # Discovery - read access to all API groups for resource discovery + # This is required for auto-discovering available resource types + - apiGroups: ["*"] + resources: ["*"] + verbs: + - get + - list + - watch + + # Full access to all mirrorable resources + # Required for creating, updating, and deleting mirrors across all resource types + # The controller will only mirror resources that are explicitly marked with + # kubemirror.raczylo.com/enabled label and kubemirror.raczylo.com/sync annotation + - apiGroups: ["*"] + resources: ["*"] + verbs: + - create + - update + - patch + - delete + + # Namespaces - read only (for listing and filtering) + - apiGroups: [""] + resources: + - namespaces + verbs: + - get + - list + - watch + + # Leader election - coordination.k8s.io/v1 + - apiGroups: ["coordination.k8s.io"] + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + + # Events - for creating events about mirroring operations + - apiGroups: [""] + resources: + - events + verbs: + - create + - patch diff --git a/charts/kubemirror/templates/clusterrolebinding.yaml b/charts/kubemirror/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..fe30d53 --- /dev/null +++ b/charts/kubemirror/templates/clusterrolebinding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "kubemirror.fullname" . }} + labels: + {{- include "kubemirror.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "kubemirror.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "kubemirror.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/kubemirror/templates/deployment.yaml b/charts/kubemirror/templates/deployment.yaml new file mode 100644 index 0000000..aa85ab2 --- /dev/null +++ b/charts/kubemirror/templates/deployment.yaml @@ -0,0 +1,95 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "kubemirror.fullname" . }} + labels: + {{- include "kubemirror.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "kubemirror.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- toYaml .Values.podAnnotations | nindent 8 }} + labels: + {{- include "kubemirror.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "kubemirror.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + containers: + - name: controller + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /kubemirror + args: + - --metrics-bind-address={{ .Values.controller.metricsBindAddress }} + - --health-probe-bind-address={{ .Values.controller.healthProbeBindAddress }} + {{- if .Values.controller.leaderElect }} + - --leader-elect + {{- end }} + - --leader-election-id={{ .Values.controller.leaderElectionID }} + - --max-targets={{ .Values.controller.maxTargets }} + - --worker-threads={{ .Values.controller.workerThreads }} + - --rate-limit-qps={{ .Values.controller.rateLimitQPS }} + - --rate-limit-burst={{ .Values.controller.rateLimitBurst }} + {{- if .Values.controller.excludedNamespaces }} + - --excluded-namespaces={{ .Values.controller.excludedNamespaces }} + {{- end }} + {{- if .Values.controller.includedNamespaces }} + - --included-namespaces={{ .Values.controller.includedNamespaces }} + {{- end }} + {{- if .Values.controller.resourceTypes }} + - --resource-types={{ join "," .Values.controller.resourceTypes }} + {{- end }} + - --discovery-interval={{ .Values.controller.discoveryInterval }} + ports: + - name: metrics + containerPort: 8080 + protocol: TCP + - name: health + containerPort: 8081 + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + terminationGracePeriodSeconds: 10 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/kubemirror/templates/service.yaml b/charts/kubemirror/templates/service.yaml new file mode 100644 index 0000000..0ace2cc --- /dev/null +++ b/charts/kubemirror/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "kubemirror.fullname" . }}-metrics + labels: + {{- include "kubemirror.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - name: metrics + port: {{ .Values.service.metricsPort }} + targetPort: metrics + protocol: TCP + - name: health + port: {{ .Values.service.healthPort }} + targetPort: health + protocol: TCP + selector: + {{- include "kubemirror.selectorLabels" . | nindent 4 }} diff --git a/charts/kubemirror/templates/serviceaccount.yaml b/charts/kubemirror/templates/serviceaccount.yaml new file mode 100644 index 0000000..ca61f42 --- /dev/null +++ b/charts/kubemirror/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "kubemirror.serviceAccountName" . }} + labels: + {{- include "kubemirror.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/kubemirror/values.yaml b/charts/kubemirror/values.yaml new file mode 100644 index 0000000..9502b06 --- /dev/null +++ b/charts/kubemirror/values.yaml @@ -0,0 +1,86 @@ +replicaCount: 1 + +image: + repository: ghcr.io/lukaszraczylo/kubemirror + pullPolicy: IfNotPresent + tag: "0.2.8" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/metrics" + +podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + +controller: + # Metrics and health endpoints + metricsBindAddress: ":8080" + healthProbeBindAddress: ":8081" + + # Leader election + leaderElect: true + leaderElectionID: "kubemirror-controller-leader" + + # Resource types to mirror + # Examples: ["Secret.v1", "ConfigMap.v1", "Ingress.v1.networking.k8s.io"] + # If empty, auto-discovery will find all mirrorable resources + resourceTypes: [] + + # Auto-discovery interval (only used when resourceTypes is empty) + # How often to rediscover available resources in the cluster + discoveryInterval: "5m" + + # Resource limits + maxTargets: 100 + workerThreads: 5 + + # API rate limiting + rateLimitQPS: 50.0 + rateLimitBurst: 100 + + # Namespace filtering + excludedNamespaces: "" + includedNamespaces: "" + +service: + type: ClusterIP + metricsPort: 8080 + healthPort: 8081 + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +priorityClassName: "" diff --git a/charts/packages/kubemirror-0.2.8.tgz b/charts/packages/kubemirror-0.2.8.tgz new file mode 100644 index 0000000..074b88e Binary files /dev/null and b/charts/packages/kubemirror-0.2.8.tgz differ diff --git a/index.yaml b/index.yaml index af581d9..810fd7a 100644 --- a/index.yaml +++ b/index.yaml @@ -1448,4 +1448,27 @@ entries: urls: - https://github.com/lukaszraczylo/helm-charts/releases/download/kube-images-sync-0.1.5/kube-images-sync-0.1.5.tgz version: 0.1.5 -generated: "2025-12-24T03:42:29.384457558Z" + kubemirror: + - apiVersion: v2 + appVersion: 0.2.8 + created: "2025-12-26T00:13:16.665880215Z" + description: Kubernetes controller for mirroring resources across namespaces + digest: 408fcd9733175afdaea44672e01b0927a2f9068d59b226609e207b6c163598d8 + home: https://github.com/lukaszraczylo/kubemirror + keywords: + - kubernetes + - controller + - mirror + - secrets + - configmaps + maintainers: + - email: lukasz@raczylo.com + name: Lukasz Raczylo + name: kubemirror + sources: + - https://github.com/lukaszraczylo/kubemirror + type: application + urls: + - https://github.com/lukaszraczylo/helm-charts/releases/download/kubemirror-0.2.8/kubemirror-0.2.8.tgz + version: 0.2.8 +generated: "2025-12-26T00:13:16.665356444Z"