Init commit
This commit is contained in:
		
							
								
								
									
										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)),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user