Init commit
This commit is contained in:
commit
09f81a5899
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
28
.woodpecker/build.yaml
Normal file
28
.woodpecker/build.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Build a container image
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
image: git.badhouseplants.net/badhouseplants/badhouseplants-builder:555262114ea81f6f286010474527f419b56d33a3
|
||||||
|
name: Build helmule operator image
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
- PACKAGE_NAME=allanger/helmule
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
if [[ "${CI_COMMIT_TAG}" ]]; then
|
||||||
|
export CUSTOM_TAG="${CI_COMMIT_TAG}";
|
||||||
|
fi
|
||||||
|
- build-container
|
||||||
|
secrets:
|
||||||
|
- gitea_token
|
||||||
|
backend_options:
|
||||||
|
kubernetes:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 800Mi
|
||||||
|
cpu: 500m
|
||||||
|
limits:
|
||||||
|
memory: 1000Mi
|
||||||
|
cpu: 1000m
|
1104
Cargo.lock
generated
Normal file
1104
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "helmule"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.21.5"
|
||||||
|
chrono = "0.4.31"
|
||||||
|
clap = { version = "4.4.11", features = ["derive"] }
|
||||||
|
dircpy = "0.3.15"
|
||||||
|
env_logger = "0.10.1"
|
||||||
|
handlebars = "5.0.0"
|
||||||
|
log = "0.4.20"
|
||||||
|
regex = "1.10.2"
|
||||||
|
semver = "1.0.20"
|
||||||
|
semver_sort = "1.0.0"
|
||||||
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
|
serde_json = "1.0.110"
|
||||||
|
serde_yaml = "0.9.29"
|
||||||
|
tempfile = "3.9.0"
|
||||||
|
time = { version = "0.3.31", features = ["serde", "formatting", "parsing"]}
|
30
Containerfile
Normal file
30
Containerfile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM rust:1.70.0-alpine3.18 as builder
|
||||||
|
WORKDIR /src
|
||||||
|
RUN apk update && apk add --no-cache gcc musl-dev
|
||||||
|
COPY ./ .
|
||||||
|
RUN rustup default nightly && rustup update
|
||||||
|
RUN cargo build --release --jobs 2 -Z sparse-registry
|
||||||
|
|
||||||
|
FROM ghcr.io/allanger/dumb-downloader as dudo
|
||||||
|
RUN apt-get update -y && apt-get install tar git -y
|
||||||
|
ARG HELM_VERSION=v3.13.3
|
||||||
|
ARG YQ_VERSION=v4.40.5
|
||||||
|
ENV RUST_LOG=info
|
||||||
|
RUN dudo -l "https://get.helm.sh/helm-{{ version }}-{{ os }}-{{ arch }}.tar.gz" -d /tmp/helm.tar.gz -p $HELM_VERSION
|
||||||
|
RUN dudo -l "https://github.com/mikefarah/yq/releases/download/{{ version }}/yq_{{ os }}_{{ arch }}.tar.gz" -d /tmp/yq.tar.gz -p $YQ_VERSION
|
||||||
|
RUN tar -xf /tmp/helm.tar.gz -C /tmp && rm -f /tmp/helm.tar.gz
|
||||||
|
RUN tar -xf /tmp/yq.tar.gz -C /tmp && rm -f /tmp/yq.tar.gz
|
||||||
|
RUN mkdir /out
|
||||||
|
RUN cp `find /tmp | grep helm` /out/
|
||||||
|
RUN mv `find /tmp | grep yq_` /out/yq
|
||||||
|
RUN chmod +x /out/helm
|
||||||
|
RUN chmod +x /out/yq
|
||||||
|
|
||||||
|
FROM alpine:3.18
|
||||||
|
RUN apk update && apk add --no-cache git
|
||||||
|
COPY --from=builder /src/target/release/helmule /bin/helmule
|
||||||
|
COPY --from=dudo /out/ /usr/bin
|
||||||
|
WORKDIR /workdir
|
||||||
|
ENTRYPOINT ["/bin/helmule"]
|
||||||
|
|
||||||
|
|
28
examples/extensions/flux2/crd-configmap.yaml
Normal file
28
examples/extensions/flux2/crd-configmap.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{{/*
|
||||||
|
We have to create individual configmaps for each CRD - they exceed the total
|
||||||
|
allowed length for a configmap if they are combined.
|
||||||
|
*/}}
|
||||||
|
{{ $currentScope := . }}
|
||||||
|
{{- if .Values.crds.install }}
|
||||||
|
{{- range $path, $_ := .Files.Glob "crd-base/**" }}
|
||||||
|
{{- with $currentScope }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}-{{ $path | base | trimSuffix ".yaml" }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-5"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
data:
|
||||||
|
content: |
|
||||||
|
{{ tpl (.Files.Get $path) . | indent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
80
examples/extensions/flux2/crd-job.yaml
Normal file
80
examples/extensions/flux2/crd-job.yaml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{{- if .Values.crds.install }}
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstallJob" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-1"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: {{ include "crdInstall" . }}
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 2000
|
||||||
|
{{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
|
||||||
|
{{- with .Values.crds.podSeccompProfile }}
|
||||||
|
seccompProfile:
|
||||||
|
{{- . | toYaml | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: kubectl
|
||||||
|
image: "{{ .Values.images.registry }}/giantswarm/docker-kubectl:1.23.6"
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -o errexit ; set -o xtrace ; set -o nounset
|
||||||
|
|
||||||
|
# piping stderr to stdout means kubectl's errors are surfaced
|
||||||
|
# in the pod's logs.
|
||||||
|
|
||||||
|
kubectl apply -f /data/ 2>&1
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
{{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
|
||||||
|
{{- with .Values.crds.seccompProfile }}
|
||||||
|
seccompProfile:
|
||||||
|
{{- . | toYaml | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
volumeMounts:
|
||||||
|
{{- range $path, $_ := .Files.Glob "crd-base/**" }}
|
||||||
|
- name: {{ $path | base | trimSuffix ".yaml" }}
|
||||||
|
mountPath: /data/{{ $path | base }}
|
||||||
|
subPath: {{ $path | base }}
|
||||||
|
{{- end }}
|
||||||
|
resources: {{- toYaml .Values.crds.resources | nindent 10 }}
|
||||||
|
volumes:
|
||||||
|
{{ $currentScope := . }}
|
||||||
|
{{- range $path, $_ := .Files.Glob "crd-base/**" }}
|
||||||
|
{{- with $currentScope }}
|
||||||
|
- name: {{ $path | base | trimSuffix ".yaml" }}
|
||||||
|
configMap:
|
||||||
|
name: {{ include "crdInstall" . }}-{{ $path | base | trimSuffix ".yaml" }}
|
||||||
|
items:
|
||||||
|
- key: content
|
||||||
|
path: {{ $path | base }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
restartPolicy: Never
|
||||||
|
backoffLimit: 4
|
||||||
|
{{- end }}
|
59
examples/extensions/flux2/crd-np.yaml
Normal file
59
examples/extensions/flux2/crd-np.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{{- if .Values.crds.install }}
|
||||||
|
{{- if .Capabilities.APIVersions.Has "cilium.io/v2/CiliumNetworkPolicy" }}
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-7"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
spec:
|
||||||
|
egress:
|
||||||
|
- toEntities:
|
||||||
|
- kube-apiserver
|
||||||
|
endpointSelector: {}
|
||||||
|
{{- else }}
|
||||||
|
kind: NetworkPolicy
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-7"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 6 }}
|
||||||
|
# allow egress traffic to the Kubernetes API
|
||||||
|
egress:
|
||||||
|
- ports:
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
# legacy port kept for compatibility
|
||||||
|
- port: 6443
|
||||||
|
protocol: TCP
|
||||||
|
to:
|
||||||
|
{{- range tuple "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" "100.64.0.0/10" }}
|
||||||
|
- ipBlock:
|
||||||
|
cidr: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
# deny ingress traffic
|
||||||
|
ingress: []
|
||||||
|
policyTypes:
|
||||||
|
- Egress
|
||||||
|
- Ingress
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
62
examples/extensions/flux2/crd-rbac.yaml
Normal file
62
examples/extensions/flux2/crd-rbac.yaml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{{- if .Values.crds.install }}
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-3"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- policy
|
||||||
|
resources:
|
||||||
|
- podsecuritypolicies
|
||||||
|
resourceNames:
|
||||||
|
- {{ include "crdInstall" . }}
|
||||||
|
verbs:
|
||||||
|
- use
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-2"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.common" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
{{- end }}
|
15
examples/extensions/flux2/crd-serviceaccount.yaml
Normal file
15
examples/extensions/flux2/crd-serviceaccount.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{{- if .Values.crds.install }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "crdInstall" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
annotations:
|
||||||
|
# create hook dependencies in the right order
|
||||||
|
"helm.sh/hook-weight": "-4"
|
||||||
|
{{- include "crdInstallAnnotations" . | nindent 4 }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: {{ include "crdInstall" . | quote }}
|
||||||
|
{{- include "labels.selector" . | nindent 4 }}
|
||||||
|
role: {{ include "crdInstallSelector" . | quote }}
|
||||||
|
{{- end }}
|
30
examples/extensions/vaultwarden/virtual-service.yaml
Normal file
30
examples/extensions/vaultwarden/virtual-service.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{{- if .Values.virtualservice.enabled -}}
|
||||||
|
{{- $fullName := include "vaultwarden.fullname" . -}}
|
||||||
|
{{- $svcPort := .Values.service.port -}}
|
||||||
|
{{- if $.Capabilities.APIVersions.Has "networking.istio.io/v1beta1" }}
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: VirtualService
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "vaultwarden.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
gateways:
|
||||||
|
- {{ .Values.virtaulservice.gatewayRef }}
|
||||||
|
hosts:
|
||||||
|
- ci.badhouseplants.ne
|
||||||
|
http:
|
||||||
|
- match:
|
||||||
|
- uri:
|
||||||
|
prefix: /
|
||||||
|
route:
|
||||||
|
- destination:
|
||||||
|
host: woodpecker-ci-server
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
51
examples/patches/flux-regexp/patch.yaml
Normal file
51
examples/patches/flux-regexp/patch.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: Remove CRDs leftovers from values
|
||||||
|
targets:
|
||||||
|
- values.yaml
|
||||||
|
before: |-
|
||||||
|
installCRDs: true
|
||||||
|
crds:
|
||||||
|
# -- Add annotations to all CRD resources, e.g. "helm.sh/resource-policy": keep
|
||||||
|
annotations: \{\}
|
||||||
|
after: |-
|
||||||
|
crds:
|
||||||
|
install: true
|
||||||
|
|
||||||
|
# Add seccomp to pod security context
|
||||||
|
podSeccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
|
||||||
|
# Add seccomp to container security context
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
|
||||||
|
---
|
||||||
|
name: Append crd install data to helpers
|
||||||
|
targets:
|
||||||
|
- templates/_helper.tpl
|
||||||
|
after: |-
|
||||||
|
{{- define "crdInstall" -}}
|
||||||
|
{{- printf "%s-%s" ( include "name" . ) "crd-install" | replace "+" "_" | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "crdInstallJob" -}}
|
||||||
|
{{- printf "%s-%s-%s" ( include "name" . ) "crd-install" .Chart.AppVersion | replace "+" "_" | replace "." "-" | trimSuffix "-" | trunc 63 -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "crdInstallAnnotations" -}}
|
||||||
|
"helm.sh/hook": "pre-install,pre-upgrade"
|
||||||
|
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded,hook-failed"
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Create a label which can be used to select any orphaned crd-install hook resources */}}
|
||||||
|
{{- define "crdInstallSelector" -}}
|
||||||
|
{{- printf "%s" "crd-install-hook" -}}
|
||||||
|
{{- end -}}
|
34
examples/patches/git/patch-2.diff
Normal file
34
examples/patches/git/patch-2.diff
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
diff --git a/values.yaml b/values.yaml
|
||||||
|
index 7ed6839..2b144ad 100644
|
||||||
|
--- a/values.yaml
|
||||||
|
+++ b/values.yaml
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
image:
|
||||||
|
repository: registry.hub.docker.com/vaultwarden/server
|
||||||
|
- pullPolicy: IfNotPresent
|
||||||
|
+ pullPolicy: Always
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: ""
|
||||||
|
imagePullSecrets: []
|
||||||
|
@@ -10,13 +10,14 @@ podAnnotations: {}
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
-securityContext: {}
|
||||||
|
-# capabilities:
|
||||||
|
-# drop:
|
||||||
|
-# - ALL
|
||||||
|
-# readOnlyRootFilesystem: true
|
||||||
|
-# runAsNonRoot: true
|
||||||
|
-# runAsUser: 1000
|
||||||
|
+securityContext:
|
||||||
|
+capabilities:
|
||||||
|
+ drop:
|
||||||
|
+ - ALL
|
||||||
|
+
|
||||||
|
+readOnlyRootFilesystem: true
|
||||||
|
+runAsNonRoot: true
|
||||||
|
+runAsUser: 1000
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
13
examples/patches/git/patch.diff
Normal file
13
examples/patches/git/patch.diff
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/Chart.yaml b/Chart.yaml
|
||||||
|
index d8995d5..0e5f5a5 100644
|
||||||
|
--- a/Chart.yaml
|
||||||
|
+++ b/Chart.yaml
|
||||||
|
@@ -8,7 +8,7 @@ keywords:
|
||||||
|
- bitwarden
|
||||||
|
- bitwarden_rs
|
||||||
|
maintainers:
|
||||||
|
-- email: allanger@badhouseplants.net
|
||||||
|
+- email: Somebody else
|
||||||
|
name: Nikolai Rodionov
|
||||||
|
url: https://badhouseplants.net
|
||||||
|
name: vaultwarden
|
7
examples/patches/regexp/patch.yaml
Normal file
7
examples/patches/regexp/patch.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
name: Add spaces before comments
|
||||||
|
targets:
|
||||||
|
- values.yaml
|
||||||
|
before: |-
|
||||||
|
^.*[\S]+.*#
|
||||||
|
after: " #"
|
1
examples/yamlfmt.yml
Normal file
1
examples/yamlfmt.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
pad_line_comments: 2
|
97
helmule.yaml
Normal file
97
helmule.yaml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# mirror charts
|
||||||
|
repositories:
|
||||||
|
- name: metrics-server
|
||||||
|
helm:
|
||||||
|
url: https://kubernetes-sigs.github.io/metrics-server/
|
||||||
|
- name: db-operator
|
||||||
|
git:
|
||||||
|
url: https://github.com/db-operator/charts.git
|
||||||
|
ref: main
|
||||||
|
path: charts
|
||||||
|
- name: badhouseplants
|
||||||
|
helm:
|
||||||
|
url: https://badhouseplants.github.io/helm-charts/
|
||||||
|
- name: flux-community
|
||||||
|
helm:
|
||||||
|
url: https://fluxcd-community.github.io/helm-charts
|
||||||
|
charts:
|
||||||
|
- name: vaultwarden
|
||||||
|
repository: badhouseplants
|
||||||
|
version: latest
|
||||||
|
extensions:
|
||||||
|
- name: Add virtual service to the chartc
|
||||||
|
target_dir: templates/extensions
|
||||||
|
source_dir: ./examples/extensions/vaultwarden
|
||||||
|
patches:
|
||||||
|
- name: Git patch 1
|
||||||
|
git:
|
||||||
|
path: ./examples/patches/git/patch.diff
|
||||||
|
- name: Git patch 2
|
||||||
|
git:
|
||||||
|
path: ./examples/patches/git/patch-2.diff
|
||||||
|
- name: yaml-fmt
|
||||||
|
custom_command:
|
||||||
|
commands:
|
||||||
|
- |-
|
||||||
|
cat <<EOT >> .yamlfmt
|
||||||
|
formatter:
|
||||||
|
pad_line_comments: 2
|
||||||
|
EOT
|
||||||
|
- yamlfmt values.yaml --conf ./yamlfmt.yaml
|
||||||
|
- rm -f yamlfmt.yaml
|
||||||
|
mirrors:
|
||||||
|
- badhouseplants-git
|
||||||
|
- custom-command
|
||||||
|
- name: flux2
|
||||||
|
repository: flux-community
|
||||||
|
extensions:
|
||||||
|
- name: Create a job that will apply crds
|
||||||
|
target_dir: templates/crd-install
|
||||||
|
source_dir: ./examples/extensions/flux2
|
||||||
|
patches:
|
||||||
|
- name: Add crds to chart files
|
||||||
|
custom_command:
|
||||||
|
commands:
|
||||||
|
- mkdir crd-base
|
||||||
|
- |-
|
||||||
|
cd crd-base && helm template flux . \
|
||||||
|
| yq '. | select(.kind == "CustomResourceDefinition")' \
|
||||||
|
| yq -s '.kind + "-" + .metadata.name'
|
||||||
|
- name: Remove CRDs from the templates
|
||||||
|
custom_command:
|
||||||
|
commands:
|
||||||
|
- find . -name "*crds*" -type f -delete
|
||||||
|
- name: Remove installCRDs value from the default values
|
||||||
|
regexp:
|
||||||
|
path: ./examples/patches/flux-regexp
|
||||||
|
- name: yaml-fmt
|
||||||
|
custom_command:
|
||||||
|
commands:
|
||||||
|
- |-
|
||||||
|
cat <<EOT >> .yamlfmt
|
||||||
|
formatter:
|
||||||
|
pad_line_comments: 2
|
||||||
|
EOT
|
||||||
|
- yamlfmt values.yaml --conf ./yamlfmt.yaml
|
||||||
|
- rm -f yamlfmt.yaml
|
||||||
|
mirrors:
|
||||||
|
- custom-command
|
||||||
|
mirrors:
|
||||||
|
- name: badhouseplants-git
|
||||||
|
git:
|
||||||
|
url: git@git.badhouseplants.net:allanger/helmuled-charts.git
|
||||||
|
branch: upgrade-{{ name }}-to-{{ version }}
|
||||||
|
path: charts/{{ name }}
|
||||||
|
commit: |-
|
||||||
|
chore: mirror {{ name }}-{{ version }}
|
||||||
|
|
||||||
|
upstream_repo: {{ repo_url }}
|
||||||
|
- name: custom-command
|
||||||
|
custom_command:
|
||||||
|
package:
|
||||||
|
- zip -r {{ name }}-{{ version }}.zip {{ name }}-{{ version }}
|
||||||
|
upload:
|
||||||
|
- rm -f /tmp/{{ name }}-{{ version }}.zip
|
||||||
|
- rm -rf /tmp/{{ name }}-{{ version }}
|
||||||
|
- cp {{ name }}-{{ version }}.zip /tmp
|
||||||
|
- unzip /tmp/{{ name }}-{{ version }}.zip -d /tmp/{{ name }}-{{ version}}
|
45
src/config/extension.rs
Normal file
45
src/config/extension.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Extension {
|
||||||
|
name: Option<String>,
|
||||||
|
target_dir: String,
|
||||||
|
source_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension {
|
||||||
|
pub(crate) fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let extension_name = match self.name.clone() {
|
||||||
|
Some(res) => res,
|
||||||
|
None => "Unnamed".to_string(),
|
||||||
|
};
|
||||||
|
info!("applying extension: '{}'", extension_name);
|
||||||
|
let target_dir = format!("{}/{}", chart_local_path, self.target_dir);
|
||||||
|
info!("trying to create a dir: {}", target_dir);
|
||||||
|
fs::create_dir(target_dir.clone())?;
|
||||||
|
info!("copying {} to {}", self.source_dir, target_dir);
|
||||||
|
copy_recursively(self.source_dir.clone(), target_dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy files from source to destination recursively.
|
||||||
|
pub fn copy_recursively(
|
||||||
|
source: impl AsRef<Path>,
|
||||||
|
destination: impl AsRef<Path>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for entry in fs::read_dir(source)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let filetype = entry.file_type()?;
|
||||||
|
if filetype.is_dir() {
|
||||||
|
copy_recursively(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||||
|
} else {
|
||||||
|
info!("trying to copy {:?}", entry.path());
|
||||||
|
fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
184
src/config/mod.rs
Normal file
184
src/config/mod.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
use log::{info, warn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
pub(crate) mod extension;
|
||||||
|
pub(crate) mod patch;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub(crate) repositories: Vec<Repository>,
|
||||||
|
pub(crate) charts: Vec<Chart>,
|
||||||
|
pub(crate) mirrors: Vec<Mirror>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(crate) fn new(config_path: String) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
info!("reading the config file");
|
||||||
|
let config_content = File::open(config_path)?;
|
||||||
|
let config: Config = serde_yaml::from_reader(config_content)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum RepositoryKind {
|
||||||
|
Helm,
|
||||||
|
Git,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Repository {
|
||||||
|
// A name of the repository to be references by charts
|
||||||
|
pub(crate) name: String,
|
||||||
|
// Helm repository data
|
||||||
|
pub(crate) helm: Option<Helm>,
|
||||||
|
pub(crate) git: Option<Git>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Mirror {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) git: Option<GitMirror>,
|
||||||
|
pub(crate) custom_command: Option<CustomCommandsMirror>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct CustomCommandsMirror {
|
||||||
|
pub(crate) package: Vec<String>,
|
||||||
|
pub(crate) upload: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct GitMirror {
|
||||||
|
pub(crate) url: String,
|
||||||
|
pub(crate) path: Option<String>,
|
||||||
|
pub(crate) branch: String,
|
||||||
|
pub(crate) commit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Helm {
|
||||||
|
// A url of the helm repository
|
||||||
|
pub(crate) url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Git {
|
||||||
|
pub(crate) url: String,
|
||||||
|
#[serde(alias = "ref")]
|
||||||
|
pub(crate) git_ref: String,
|
||||||
|
pub(crate) path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Chart {
|
||||||
|
// A name of the helm chart
|
||||||
|
pub(crate) name: String,
|
||||||
|
// A reference to repository by name
|
||||||
|
pub(crate) repository: String,
|
||||||
|
pub(crate) mirrors: Vec<String>,
|
||||||
|
// Versions to be mirrored
|
||||||
|
pub(crate) version: Option<String>,
|
||||||
|
// A repository object
|
||||||
|
pub(crate) extensions: Option<Vec<extension::Extension>>,
|
||||||
|
pub(crate) patches: Option<Vec<patch::Patch>>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub(crate) repository_obj: Option<Repository>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub(crate) mirror_objs: Option<Vec<Mirror>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chart {
|
||||||
|
pub(crate) fn populate_repository(
|
||||||
|
&mut self,
|
||||||
|
repositories: Vec<Repository>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for repository in repositories {
|
||||||
|
if repository.name == self.repository {
|
||||||
|
self.repository_obj = Some(repository);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//let err = error!("repo {} is not found in the repo list", self.repository);
|
||||||
|
let error_msg = format!("repo {} is not found in the repo list", self.repository);
|
||||||
|
return Err(Box::from(error_msg));
|
||||||
|
}
|
||||||
|
// TODO: Handle the "mirror not found" error
|
||||||
|
pub(crate) fn populate_mirrors(
|
||||||
|
&mut self,
|
||||||
|
mirrors: Vec<Mirror>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut mirror_objs: Vec<Mirror> = vec![];
|
||||||
|
|
||||||
|
for mirror_global in mirrors.clone() {
|
||||||
|
for mirror_name in self.mirrors.clone() {
|
||||||
|
if mirror_name == mirror_global.name.clone() {
|
||||||
|
mirror_objs.push(mirror_global.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mirror_objs.len() > 0 {
|
||||||
|
self.mirror_objs = Some(mirror_objs);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_helm_repository_url(&self) -> String {
|
||||||
|
match self.repository_obj.clone() {
|
||||||
|
Some(res) => res.helm.unwrap().url,
|
||||||
|
None => {
|
||||||
|
warn!("repository object is not filled for chart {}", self.name);
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_git_repository_url(&self) -> String {
|
||||||
|
match self.repository_obj.clone() {
|
||||||
|
Some(res) => res.git.unwrap().url,
|
||||||
|
None => {
|
||||||
|
warn!("repository object is not filled for chart {}", self.name);
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_git_repository_ref(&self) -> String {
|
||||||
|
match self.repository_obj.clone() {
|
||||||
|
Some(res) => res.git.unwrap().git_ref,
|
||||||
|
None => {
|
||||||
|
warn!("repository object is not filled for chart {}", self.name);
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_git_repository_path(&self) -> String {
|
||||||
|
match self.repository_obj.clone() {
|
||||||
|
Some(res) => res.git.unwrap().path,
|
||||||
|
None => {
|
||||||
|
warn!("repository object is not filled for chart {}", self.name);
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_repo_kind(&self) -> Result<RepositoryKind, Box<dyn std::error::Error>> {
|
||||||
|
match &self.repository_obj {
|
||||||
|
Some(res) => {
|
||||||
|
if res.helm.is_some() {
|
||||||
|
return Ok(RepositoryKind::Helm);
|
||||||
|
} else if res.git.is_some() {
|
||||||
|
return Ok(RepositoryKind::Git);
|
||||||
|
} else {
|
||||||
|
return Err(Box::from("unknown repository kind is found"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Box::from(
|
||||||
|
"repository object is not filled up for the chart",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
src/config/patch.rs
Normal file
200
src/config/patch.rs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
use std::{
|
||||||
|
fs::{self, read_dir, remove_dir_all, File, OpenOptions},
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{error, info};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::helpers::cli::{cli_exec, cli_exec_from_dir};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct RegexpPatch {
|
||||||
|
pub(crate) path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct GitPatch {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) enum YqOperations {
|
||||||
|
Add,
|
||||||
|
Delete,
|
||||||
|
Replace,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct YqPatch {
|
||||||
|
file: String,
|
||||||
|
op: YqOperations,
|
||||||
|
key: String,
|
||||||
|
value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct CustomCommandPatch {
|
||||||
|
commands: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Patch {
|
||||||
|
regexp: Option<RegexpPatch>,
|
||||||
|
git: Option<GitPatch>,
|
||||||
|
custom_command: Option<CustomCommandPatch>,
|
||||||
|
yq: Option<YqPatch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Patch {
|
||||||
|
pub(crate) fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let patch_action = patch_action_from_definition(self.clone())?;
|
||||||
|
patch_action.apply(chart_local_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PatchInterface {
|
||||||
|
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchInterface for YqPatch {
|
||||||
|
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let cmd = match self.op {
|
||||||
|
YqOperations::Add => format!(
|
||||||
|
"yq -i '{} += \"{}\"' {}",
|
||||||
|
self.key,
|
||||||
|
self.value.clone().unwrap(),
|
||||||
|
self.file
|
||||||
|
),
|
||||||
|
YqOperations::Delete => format!("yq -i \'del({})\' {}", self.key, self.file),
|
||||||
|
YqOperations::Replace => format!(
|
||||||
|
"yq e -i \'{} = \"{}\"\' {}",
|
||||||
|
self.key,
|
||||||
|
self.value.clone().unwrap(),
|
||||||
|
self.file
|
||||||
|
),
|
||||||
|
};
|
||||||
|
cli_exec_from_dir(cmd, chart_local_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchInterface for RegexpPatch {
|
||||||
|
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for entry in read_dir(self.path.clone())? {
|
||||||
|
let entry = entry?;
|
||||||
|
let filetype = entry.file_type()?;
|
||||||
|
if filetype.is_dir() {
|
||||||
|
error!(
|
||||||
|
"reading dirs is not supported yet, skipping {:?}",
|
||||||
|
entry.path()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!("reading a patch file: {:?}", entry.path());
|
||||||
|
let config_content = File::open(entry.path())?;
|
||||||
|
for patch_des in serde_yaml::Deserializer::from_reader(config_content) {
|
||||||
|
let patch: crate::patch::regexp::RegexpPatch =
|
||||||
|
match crate::patch::regexp::RegexpPatch::deserialize(patch_des) {
|
||||||
|
Ok(patch) => patch,
|
||||||
|
Err(err) => return Err(Box::from(err)),
|
||||||
|
};
|
||||||
|
info!("applying patch: {}", patch.name);
|
||||||
|
let after = match patch.after {
|
||||||
|
Some(after) => after,
|
||||||
|
None => "".to_string(),
|
||||||
|
};
|
||||||
|
match patch.before {
|
||||||
|
Some(before) => {
|
||||||
|
let patch_regexp = regex::Regex::new(before.as_str())?;
|
||||||
|
for target in patch.targets {
|
||||||
|
let file_path = format!("{}/{}", chart_local_path, target);
|
||||||
|
let file_content = fs::read_to_string(file_path.clone())?;
|
||||||
|
let new_content =
|
||||||
|
patch_regexp.replace_all(file_content.as_str(), after.clone());
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(file_path.clone())?;
|
||||||
|
file.write(new_content.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
for target in patch.targets {
|
||||||
|
let file_path = format!("{}/{}", chart_local_path, target);
|
||||||
|
let file_content = fs::read_to_string(file_path.clone())?;
|
||||||
|
let new_content = format!("{}\n{}", file_content, after);
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(false)
|
||||||
|
.open(file_path.clone())?;
|
||||||
|
file.write(new_content.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchInterface for GitPatch {
|
||||||
|
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if !is_git_repo(chart_local_path.clone()) {
|
||||||
|
init_git_repo(chart_local_path.clone())?;
|
||||||
|
};
|
||||||
|
let cmd = format!("git -C {} apply {}", chart_local_path, self.path);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
remove_dir_all(chart_local_path + "/.git")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatchInterface for CustomCommandPatch {
|
||||||
|
fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for cmd in self.commands.clone() {
|
||||||
|
cli_exec_from_dir(cmd, chart_local_path.clone())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn patch_action_from_definition(
|
||||||
|
patch: Patch,
|
||||||
|
) -> Result<Box<dyn PatchInterface>, Box<dyn std::error::Error>> {
|
||||||
|
if let Some(regexp) = patch.regexp {
|
||||||
|
return Ok(Box::new(RegexpPatch { path: regexp.path }));
|
||||||
|
} else if let Some(git) = patch.git {
|
||||||
|
return Ok(Box::new(GitPatch {
|
||||||
|
path: {
|
||||||
|
let path = PathBuf::from(git.path);
|
||||||
|
let can_path = fs::canonicalize(&path).ok().unwrap();
|
||||||
|
can_path.into_os_string().into_string().ok().unwrap()
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
} else if let Some(custom_command) = patch.custom_command {
|
||||||
|
return Ok(Box::new(CustomCommandPatch {
|
||||||
|
commands: custom_command.commands,
|
||||||
|
}));
|
||||||
|
} else if let Some(yq) = patch.yq {
|
||||||
|
if yq.op != YqOperations::Delete && yq.value.is_none() {
|
||||||
|
return Err(Box::from("yq patch of non kind 'delete' requires a value"));
|
||||||
|
};
|
||||||
|
return Ok(Box::from(yq));
|
||||||
|
} else {
|
||||||
|
return Err(Box::from("unknown patch type"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_git_repo(path: String) -> bool {
|
||||||
|
let dot_git_path = path + ".git";
|
||||||
|
Path::new(dot_git_path.as_str()).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_git_repo(path: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
cli_exec(format!("git -C {} init .", path))?;
|
||||||
|
cli_exec(format!("git -C {} add .", path))?;
|
||||||
|
cli_exec(format!("git -C {} commit -m 'Init commit'", path))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
66
src/helpers/cli.rs
Normal file
66
src/helpers/cli.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use std::process::{Command, ExitStatus};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
pub(crate) fn cli_exec(command: String) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
info!("executing: {}", command);
|
||||||
|
let expect = format!("command has failed: {}", command);
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(command)
|
||||||
|
.output()
|
||||||
|
.expect(&expect);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if !&output.status.success() {
|
||||||
|
return Err(Box::from(stderr));
|
||||||
|
};
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cli_exec_from_dir(
|
||||||
|
command: String,
|
||||||
|
dir: String,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
info!("executing: {}", command);
|
||||||
|
let expect = format!("command has failed: {}", command);
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.current_dir(dir)
|
||||||
|
.arg(command)
|
||||||
|
.output()
|
||||||
|
.expect(&expect);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if !&output.status.success() {
|
||||||
|
return Err(Box::from(stderr));
|
||||||
|
};
|
||||||
|
let mut stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
stdout.pop();
|
||||||
|
Ok(stdout)
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::helpers::cli::{cli_exec, cli_exec_from_dir};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stderr() {
|
||||||
|
let command = ">&2 echo \"error\" && exit 1";
|
||||||
|
let test = cli_exec(command.to_string());
|
||||||
|
assert_eq!(test.err().unwrap().to_string(), "error\n".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdout() {
|
||||||
|
let command = "echo test";
|
||||||
|
let test = cli_exec(command.to_string());
|
||||||
|
assert_eq!(test.unwrap().to_string(), "test\n".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdout_current_dir() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let command = "echo $PWD";
|
||||||
|
let dir_str = dir.into_path().into_os_string().into_string().unwrap();
|
||||||
|
let test = cli_exec_from_dir(command.to_string(), dir_str.clone());
|
||||||
|
assert!(test.unwrap().to_string().contains(dir_str.as_str()));
|
||||||
|
}
|
||||||
|
}
|
21
src/helpers/copy.rs
Normal file
21
src/helpers/copy.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
/// Copy files from source to destination recursively.
|
||||||
|
pub(crate) fn copy_recursively(
|
||||||
|
source: impl AsRef<Path>,
|
||||||
|
destination: impl AsRef<Path>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for entry in fs::read_dir(source)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let filetype = entry.file_type()?;
|
||||||
|
if filetype.is_dir() {
|
||||||
|
copy_recursively(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||||
|
} else {
|
||||||
|
info!("trying to copy {:?}", entry.path());
|
||||||
|
fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
2
src/helpers/mod.rs
Normal file
2
src/helpers/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod cli;
|
||||||
|
pub(crate) mod copy;
|
180
src/main.rs
Normal file
180
src/main.rs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
pub(crate) mod config;
|
||||||
|
pub(crate) mod helpers;
|
||||||
|
pub(crate) mod mirror;
|
||||||
|
pub(crate) mod patch;
|
||||||
|
pub(crate) mod source;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{error, info};
|
||||||
|
use std::fs;
|
||||||
|
use std::{fs::create_dir, path::PathBuf, process::exit};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
use crate::config::patch::init_git_repo;
|
||||||
|
|
||||||
|
/// Simple program to greet a person
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Name of the working dir
|
||||||
|
#[arg(short, long)]
|
||||||
|
workdir: Option<String>,
|
||||||
|
/// Path to the configuration file
|
||||||
|
#[arg(short, long)]
|
||||||
|
config: String,
|
||||||
|
/// Dry run
|
||||||
|
#[arg(short, long, default_value = "false")]
|
||||||
|
dry_run: bool,
|
||||||
|
/// Init git patch. Use it if you want to create git patch for a chart
|
||||||
|
/// It's going to pull a chart and init a git repo there, so you can
|
||||||
|
/// apply changes and create a patch file
|
||||||
|
/// It's not going to try mirroring changes, but will apply extensions
|
||||||
|
/// and patches that are already defined
|
||||||
|
#[arg(long)]
|
||||||
|
init_git_patch: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
let args = Args::parse();
|
||||||
|
// Prepare the workdir
|
||||||
|
let workdir_path = match args.workdir {
|
||||||
|
Some(res) => match create_dir(res.clone()) {
|
||||||
|
Ok(_) => {
|
||||||
|
let path = PathBuf::from(res);
|
||||||
|
let can_path = fs::canonicalize(&path).ok().unwrap();
|
||||||
|
can_path.into_os_string().into_string().ok().unwrap()
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let tmp_dir = match TempDir::new() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match tmp_dir.path().to_str() {
|
||||||
|
Some(res) => res.to_string(),
|
||||||
|
None => {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the config
|
||||||
|
let config = match config::Config::new(args.config) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for mut chart in config.clone().charts {
|
||||||
|
match chart.populate_repository(config.repositories.clone()) {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("repo is populated for chart {}", chart.name);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match chart.populate_mirrors(config.mirrors.clone()) {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("mirrors arepopulated for chart {}", chart.name)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let chart_repo = match source::repo_from_chart(chart.clone()) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match chart_repo.pull(workdir_path.clone()) {
|
||||||
|
Ok(res) => {
|
||||||
|
info!(
|
||||||
|
"succesfully pulled chart {} into {}",
|
||||||
|
chart.name.clone(),
|
||||||
|
res.path,
|
||||||
|
);
|
||||||
|
if let Some(extensions) = chart.extensions.clone() {
|
||||||
|
for extension in extensions {
|
||||||
|
if let Err(err) = extension.apply(res.clone().path) {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(patches) = chart.patches.clone() {
|
||||||
|
for patch in patches {
|
||||||
|
if let Err(err) = patch.apply(res.clone().path) {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(init_git_patch) = args.init_git_patch.clone() {
|
||||||
|
if init_git_patch.contains(&chart.name) {
|
||||||
|
info!(
|
||||||
|
"init git patch mode is enabled, go to {} to make your changes",
|
||||||
|
res.path
|
||||||
|
);
|
||||||
|
match init_git_repo(res.path) {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("not mirroring, because of the init git patch mode");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(mirrors) = chart.mirror_objs.clone() {
|
||||||
|
for mirror_obj in mirrors {
|
||||||
|
match mirror::mirror_from_mirror_obj(mirror_obj.clone()) {
|
||||||
|
Ok(mirror) => {
|
||||||
|
match mirror.push(workdir_path.clone(), res.clone(), args.dry_run) {
|
||||||
|
Ok(_) => info!(
|
||||||
|
"mirrored {} to {}",
|
||||||
|
chart.name.clone(),
|
||||||
|
mirror_obj.name
|
||||||
|
),
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate charts
|
||||||
|
// Download helm charts from config
|
||||||
|
// If workdir is not provided, create a temporary di
|
||||||
|
}
|
33
src/mirror/custom_command.rs
Normal file
33
src/mirror/custom_command.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use crate::helpers::cli::cli_exec_from_dir;
|
||||||
|
|
||||||
|
use super::Target;
|
||||||
|
|
||||||
|
pub(crate) struct CustomCommands {
|
||||||
|
pub(crate) package: Vec<String>,
|
||||||
|
pub(crate) upload: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target for CustomCommands {
|
||||||
|
fn push(
|
||||||
|
&self,
|
||||||
|
workdir_path: String,
|
||||||
|
chart_local: crate::source::ChartLocal,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
for cmd_tmpl in self.package.clone() {
|
||||||
|
let mut reg = super::register_handlebars();
|
||||||
|
reg.register_template_string("cmd", cmd_tmpl)?;
|
||||||
|
let cmd = reg.render("cmd", &chart_local)?;
|
||||||
|
cli_exec_from_dir(cmd, workdir_path.clone())?;
|
||||||
|
}
|
||||||
|
if !dry_run {
|
||||||
|
for cmd_tmpl in self.upload.clone() {
|
||||||
|
let mut reg = super::register_handlebars();
|
||||||
|
reg.register_template_string("cmd", cmd_tmpl)?;
|
||||||
|
let cmd = reg.render("cmd", &chart_local)?;
|
||||||
|
cli_exec_from_dir(cmd, workdir_path.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
60
src/mirror/git.rs
Normal file
60
src/mirror/git.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::{helpers::cli::cli_exec_from_dir, source::ChartLocal};
|
||||||
|
use dircpy::*;
|
||||||
|
|
||||||
|
use super::Target;
|
||||||
|
|
||||||
|
pub(crate) struct Git {
|
||||||
|
pub(crate) git_dir: String,
|
||||||
|
pub(crate) url: String,
|
||||||
|
pub(crate) path: String,
|
||||||
|
pub(crate) branch: String,
|
||||||
|
pub(crate) commit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target for Git {
|
||||||
|
fn push(
|
||||||
|
&self,
|
||||||
|
workdir_path: String,
|
||||||
|
chart_local: ChartLocal,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let cmd = format!("git clone {} {}", self.url, self.git_dir);
|
||||||
|
cli_exec_from_dir(cmd, workdir_path.clone())?;
|
||||||
|
let git_repo_path = format!("{}/{}", workdir_path, self.git_dir);
|
||||||
|
|
||||||
|
// Prepare branch
|
||||||
|
let mut reg = super::register_handlebars();
|
||||||
|
reg.register_template_string("branch", self.branch.clone())?;
|
||||||
|
let branch = reg.render("branch", &chart_local)?;
|
||||||
|
let cmd = format!("git checkout {}", branch);
|
||||||
|
if let Err(_) = cli_exec_from_dir(cmd, git_repo_path.clone()) {
|
||||||
|
let cmd = format!("git checkout -b {}", branch);
|
||||||
|
cli_exec_from_dir(cmd, git_repo_path.clone())?;
|
||||||
|
};
|
||||||
|
// Prepare path
|
||||||
|
reg.register_template_string("path", self.path.clone())?;
|
||||||
|
let path = reg.render("path", &chart_local)?;
|
||||||
|
let repo_local_full_path = format!("{}/{}", git_repo_path, path);
|
||||||
|
CopyBuilder::new(chart_local.path.clone(), repo_local_full_path.clone())
|
||||||
|
.overwrite_if_size_differs(true)
|
||||||
|
.run()?;
|
||||||
|
|
||||||
|
// Prepare the commit message
|
||||||
|
let commit_message = match self.commit.clone() {
|
||||||
|
Some(commit) => commit,
|
||||||
|
None => "helmuled {{ name }}-{{ version }}".to_string(),
|
||||||
|
};
|
||||||
|
reg.register_template_string("commit", commit_message.clone())?;
|
||||||
|
let commit = reg.render("commit", &chart_local)?;
|
||||||
|
let cmd = format!(
|
||||||
|
"git add . && git diff --staged --quiet || git commit -m '{}'",
|
||||||
|
commit
|
||||||
|
);
|
||||||
|
cli_exec_from_dir(cmd, repo_local_full_path.clone())?;
|
||||||
|
if !dry_run {
|
||||||
|
let cmd = format!("git push --set-upstream origin {}", branch);
|
||||||
|
cli_exec_from_dir(cmd, repo_local_full_path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
53
src/mirror/mod.rs
Normal file
53
src/mirror/mod.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use chrono::prelude::*;
|
||||||
|
use handlebars::{handlebars_helper, Handlebars};
|
||||||
|
use time::{format_description::parse, OffsetDateTime};
|
||||||
|
|
||||||
|
use crate::{config::Mirror, source::ChartLocal};
|
||||||
|
|
||||||
|
pub(crate) mod custom_command;
|
||||||
|
pub(crate) mod git;
|
||||||
|
|
||||||
|
pub(crate) trait Target {
|
||||||
|
fn push(
|
||||||
|
&self,
|
||||||
|
workdir_path: String,
|
||||||
|
chart_local: ChartLocal,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mirror_from_mirror_obj(
|
||||||
|
mirror: Mirror,
|
||||||
|
) -> Result<Box<dyn Target>, Box<dyn std::error::Error>> {
|
||||||
|
if let Some(git) = mirror.git {
|
||||||
|
return Ok(Box::from(git::Git {
|
||||||
|
git_dir: mirror.name.clone(),
|
||||||
|
url: git.url,
|
||||||
|
path: match git.path {
|
||||||
|
Some(path) => path,
|
||||||
|
None => "".to_string(),
|
||||||
|
},
|
||||||
|
branch: git.branch,
|
||||||
|
commit: git.commit,
|
||||||
|
}));
|
||||||
|
} else if let Some(command) = mirror.custom_command {
|
||||||
|
return Ok(Box::from(custom_command::CustomCommands {
|
||||||
|
package: command.package,
|
||||||
|
upload: command.upload,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Err(Box::from(format!(
|
||||||
|
"a kind is unknown for the mirror {}",
|
||||||
|
mirror.name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
handlebars_helper!(date_helper: | | Utc::now().format("%Y-%m-%d").to_string());
|
||||||
|
handlebars_helper!(time_helper: | | Utc::now().format("%H-%M-%S").to_string());
|
||||||
|
|
||||||
|
pub(crate) fn register_handlebars() -> Handlebars<'static> {
|
||||||
|
let mut handlebars = Handlebars::new();
|
||||||
|
handlebars.register_helper("date", Box::new(date_helper));
|
||||||
|
handlebars.register_helper("time", Box::new(time_helper));
|
||||||
|
handlebars
|
||||||
|
}
|
1
src/patch/mod.rs
Normal file
1
src/patch/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod regexp;
|
9
src/patch/regexp.rs
Normal file
9
src/patch/regexp.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct RegexpPatch {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) targets: Vec<String>,
|
||||||
|
pub(crate) before: Option<String>,
|
||||||
|
pub(crate) after: Option<String>,
|
||||||
|
}
|
69
src/source/git.rs
Normal file
69
src/source/git.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use std::fs::{self, rename};
|
||||||
|
|
||||||
|
use crate::helpers::cli::{cli_exec, cli_exec_from_dir};
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
|
||||||
|
use super::{ChartLocal, Repo};
|
||||||
|
|
||||||
|
pub(crate) struct Git {
|
||||||
|
git_url: String,
|
||||||
|
git_ref: String,
|
||||||
|
path: String,
|
||||||
|
pub(crate) chart: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::config::Chart> for Git {
|
||||||
|
fn from(value: crate::config::Chart) -> Self {
|
||||||
|
Git {
|
||||||
|
git_url: value.get_git_repository_url(),
|
||||||
|
git_ref: value.get_git_repository_ref(),
|
||||||
|
path: value.get_git_repository_path(),
|
||||||
|
chart: value.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repo for Git {
|
||||||
|
fn pull(&self, workdir_path: String) -> Result<ChartLocal, Box<dyn std::error::Error>> {
|
||||||
|
let repo_local_name = general_purpose::STANDARD_NO_PAD.encode(self.git_url.clone());
|
||||||
|
let cmd = format!(
|
||||||
|
"git clone {} {}/{}",
|
||||||
|
self.git_url, workdir_path, repo_local_name
|
||||||
|
);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
|
||||||
|
let cmd = format!(
|
||||||
|
"git -C {}/{} checkout {}",
|
||||||
|
workdir_path, repo_local_name, self.git_ref
|
||||||
|
);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
|
||||||
|
let old_dir_name = format!(
|
||||||
|
"{}/{}/{}/{}",
|
||||||
|
workdir_path, repo_local_name, self.path, self.chart
|
||||||
|
);
|
||||||
|
let cmd = format!("helm show chart {}", old_dir_name);
|
||||||
|
let helm_stdout = cli_exec(cmd)?;
|
||||||
|
let new_dir_name: String;
|
||||||
|
match serde_yaml::from_str::<super::Version>(&helm_stdout) {
|
||||||
|
Ok(res) => {
|
||||||
|
new_dir_name = format!("{}/{}-{}", workdir_path, self.chart, res.version);
|
||||||
|
rename(old_dir_name, new_dir_name.clone())?;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(Box::from(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cleaning up
|
||||||
|
fs::remove_dir_all(format!("{}/{}", workdir_path, repo_local_name))?;
|
||||||
|
|
||||||
|
// Get the version
|
||||||
|
let cmd = "helm show chart . | yq '.version'".to_string();
|
||||||
|
let version = cli_exec_from_dir(cmd, new_dir_name.clone())?;
|
||||||
|
Ok(ChartLocal {
|
||||||
|
name: self.chart.clone(),
|
||||||
|
version,
|
||||||
|
path: new_dir_name,
|
||||||
|
repo_url: self.git_url.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
105
src/source/helm.rs
Normal file
105
src/source/helm.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use super::{ChartLocal, Repo};
|
||||||
|
use crate::helpers::cli::{cli_exec, cli_exec_from_dir};
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use std::fs::rename;
|
||||||
|
|
||||||
|
pub(crate) enum RepoKind {
|
||||||
|
Default,
|
||||||
|
Oci,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LATEST_VERSION: &str = "latest";
|
||||||
|
|
||||||
|
pub(crate) struct Helm {
|
||||||
|
pub(crate) chart: String,
|
||||||
|
pub(crate) repository_url: String,
|
||||||
|
pub(crate) version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::config::Chart> for Helm {
|
||||||
|
fn from(value: crate::config::Chart) -> Self {
|
||||||
|
Helm {
|
||||||
|
chart: value.name.clone(),
|
||||||
|
repository_url: value.get_helm_repository_url(),
|
||||||
|
version: match value.version {
|
||||||
|
Some(res) => res,
|
||||||
|
None => LATEST_VERSION.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Helm {
|
||||||
|
fn repo_kind_from_url(&self) -> Result<RepoKind, Box<dyn std::error::Error>> {
|
||||||
|
let prefix = self
|
||||||
|
.repository_url
|
||||||
|
.chars()
|
||||||
|
.take_while(|&ch| ch != ':')
|
||||||
|
.collect::<String>();
|
||||||
|
match prefix.as_str() {
|
||||||
|
"oci" => Ok(RepoKind::Oci),
|
||||||
|
"https" | "http" => Ok(RepoKind::Default),
|
||||||
|
_ => Err(Box::from(format!(
|
||||||
|
"repo kind is not defined by the prefix: {}",
|
||||||
|
prefix
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pull_default(&self, workdir_path: String) -> Result<ChartLocal, Box<dyn std::error::Error>> {
|
||||||
|
// Add repo and update
|
||||||
|
let repo_local_name = general_purpose::STANDARD_NO_PAD.encode(self.repository_url.clone());
|
||||||
|
let cmd = format!("helm repo add {} {}", repo_local_name, self.repository_url);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
cli_exec("helm repo update".to_string())?;
|
||||||
|
|
||||||
|
let args = match self.version.as_str() {
|
||||||
|
LATEST_VERSION => "".to_string(),
|
||||||
|
_ => format!("--version {}", self.version.clone()),
|
||||||
|
};
|
||||||
|
let cmd = format!(
|
||||||
|
"helm pull {}/{} {} --destination {} --untar",
|
||||||
|
repo_local_name, &self.chart, args, workdir_path
|
||||||
|
);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
|
||||||
|
// Get the version
|
||||||
|
let cmd = format!("helm show chart {}/{}", workdir_path, &self.chart);
|
||||||
|
let helm_stdout = cli_exec(cmd)?;
|
||||||
|
let old_dir_name = format!("{}/{}", workdir_path, &self.chart);
|
||||||
|
let new_dir_name: String;
|
||||||
|
match serde_yaml::from_str::<super::Version>(&helm_stdout) {
|
||||||
|
Ok(res) => {
|
||||||
|
new_dir_name = format!("{}-{}", old_dir_name, res.version);
|
||||||
|
rename(old_dir_name, new_dir_name.clone())?;
|
||||||
|
}
|
||||||
|
Err(err) => return Err(Box::from(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
//cleaning up
|
||||||
|
let cmd = format!("helm repo remove {}", repo_local_name);
|
||||||
|
cli_exec(cmd)?;
|
||||||
|
|
||||||
|
let cmd = "helm show chart . | yq '.version'".to_string();
|
||||||
|
let version = cli_exec_from_dir(cmd, new_dir_name.clone())?;
|
||||||
|
Ok(ChartLocal {
|
||||||
|
name: self.chart.clone(),
|
||||||
|
version,
|
||||||
|
path: new_dir_name,
|
||||||
|
repo_url: self.repository_url.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repo for Helm {
|
||||||
|
fn pull(&self, workdir_path: String) -> Result<ChartLocal, Box<dyn std::error::Error>> {
|
||||||
|
let repository_kind = self.repo_kind_from_url()?;
|
||||||
|
let path = match repository_kind {
|
||||||
|
RepoKind::Default => self.pull_default(workdir_path)?,
|
||||||
|
RepoKind::Oci => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
}
|
46
src/source/mod.rs
Normal file
46
src/source/mod.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::config::Chart;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub(crate) mod git;
|
||||||
|
pub(crate) mod helm;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub(crate) struct ChartLocal {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) version: String,
|
||||||
|
pub(crate) path: String,
|
||||||
|
pub(crate) repo_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChartLocal {
|
||||||
|
pub(crate) fn test(&self) -> String {
|
||||||
|
"test-me-if-you-can".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Repo {
|
||||||
|
fn pull(&self, workdir_path: String) -> Result<ChartLocal, Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub(crate) struct Version {
|
||||||
|
pub(crate) version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn repo_from_chart(chart: Chart) -> Result<Box<dyn Repo>, Box<dyn std::error::Error>> {
|
||||||
|
match chart.get_repo_kind() {
|
||||||
|
Ok(res) => {
|
||||||
|
return match res {
|
||||||
|
crate::config::RepositoryKind::Helm => {
|
||||||
|
let helm: helm::Helm = chart.into();
|
||||||
|
Ok(Box::new(helm))
|
||||||
|
}
|
||||||
|
crate::config::RepositoryKind::Git => {
|
||||||
|
let git: git::Git = chart.into();
|
||||||
|
Ok(Box::new(git))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => return Err(Box::from(err)),
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user