Helmule MVP
Basic functionality is there, helmule can mirror helm chart with small modifications
This commit is contained in:
		
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -4,13 +4,8 @@
 | 
			
		||||
debug/
 | 
			
		||||
target/
 | 
			
		||||
 | 
			
		||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
 | 
			
		||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
 | 
			
		||||
Cargo.lock
 | 
			
		||||
 | 
			
		||||
# These are backup files generated by rustfmt
 | 
			
		||||
**/*.rs.bk
 | 
			
		||||
 | 
			
		||||
# MSVC Windows builds of rustc generate these, which store debugging information
 | 
			
		||||
*.pdb
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										15
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
[workspace]
 | 
			
		||||
resolver = "2"
 | 
			
		||||
members = [
 | 
			
		||||
    "helmule",
 | 
			
		||||
    "helmudi",
 | 
			
		||||
    "lib",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[workspace.dependencies]
 | 
			
		||||
serde = { version = "1.0.193", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.110"
 | 
			
		||||
serde_yaml = "0.9.29"
 | 
			
		||||
clap = { version = "4.4.18", features = ["derive"] }
 | 
			
		||||
tempfile = "3.9.0"
 | 
			
		||||
base64 = "0.21.7"
 | 
			
		||||
							
								
								
									
										10
									
								
								helmudi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								helmudi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "helmudi"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
clap = { version = "4.4.18", features = ["derive"] }
 | 
			
		||||
helmzoo_lib = { path = "../lib" }
 | 
			
		||||
							
								
								
									
										46
									
								
								helmudi/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								helmudi/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
use std::{error::Error, process::exit};
 | 
			
		||||
 | 
			
		||||
use helmzoo_lib::{
 | 
			
		||||
    self,
 | 
			
		||||
    output::{message_empty, message_error},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
/// 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,
 | 
			
		||||
    #[arg(long, default_value = "false")]
 | 
			
		||||
    skip_prerequisites_check: 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 exec(args: Args) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    let workdir_path = helmzoo_lib::workdir::setup_workdir(args.workdir)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    match exec(Args::parse()) {
 | 
			
		||||
        Ok(()) => message_empty("Thanks for using helmule"),
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            message_error(err);
 | 
			
		||||
            exit(1)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								helmule/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								helmule/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
[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]
 | 
			
		||||
clap = { workspace = true }
 | 
			
		||||
dircpy = "0.3.15"
 | 
			
		||||
helmzoo_lib = { path = "../lib" }
 | 
			
		||||
regex = "1.10.3"
 | 
			
		||||
serde = { workspace = true }
 | 
			
		||||
serde_json ={ workspace = true }
 | 
			
		||||
serde_yaml = { workspace = true }
 | 
			
		||||
tempfile = { workspace = true }
 | 
			
		||||
base64 = { workspace = true }
 | 
			
		||||
							
								
								
									
										28
									
								
								helmule/examples/extensions/flux2/crd-configmap.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								helmule/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
									
								
								helmule/examples/extensions/flux2/crd-job.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								helmule/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
									
								
								helmule/examples/extensions/flux2/crd-np.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								helmule/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
									
								
								helmule/examples/extensions/flux2/crd-rbac.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								helmule/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
									
								
								helmule/examples/extensions/flux2/crd-serviceaccount.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								helmule/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
									
								
								helmule/examples/extensions/vaultwarden/virtual-service.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								helmule/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 }}
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
name: external-secrets
 | 
			
		||||
repository: external-secrets
 | 
			
		||||
variables:
 | 
			
		||||
  target_repo: app-external-secrets-operator
 | 
			
		||||
version: 0.8.3
 | 
			
		||||
mirrors:
 | 
			
		||||
  - apps-git
 | 
			
		||||
							
								
								
									
										61
									
								
								helmule/examples/giantswarm/charts/gitops-server.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								helmule/examples/giantswarm/charts/gitops-server.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# -- GitOps Server Application
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
- name: weave-gitops
 | 
			
		||||
  repository: weave
 | 
			
		||||
  version: 4.0.15
 | 
			
		||||
  variables:
 | 
			
		||||
    target_repo: app-gitops-server
 | 
			
		||||
  mirrors:
 | 
			
		||||
    - apps-git
 | 
			
		||||
  extensions:
 | 
			
		||||
    - name: Add VPA
 | 
			
		||||
      source_dir: ../extensions/vpa-gitops-server
 | 
			
		||||
      target_dir: templates/gs-vpa
 | 
			
		||||
  patches:
 | 
			
		||||
    - name: Git patch
 | 
			
		||||
      git:
 | 
			
		||||
        path: ../patches/git/gitops-server.patch
 | 
			
		||||
    - name: Generate values.schema
 | 
			
		||||
      custom_command:
 | 
			
		||||
        commands:
 | 
			
		||||
          - helm schema-gen values.yaml > values.schema.json
 | 
			
		||||
    - name: Git patch for values schema
 | 
			
		||||
      git:
 | 
			
		||||
        path: ../patches/git/gitops-server-values-schema.patch
 | 
			
		||||
    - name: Git patch for test-job security
 | 
			
		||||
      git:
 | 
			
		||||
        path: ../patches/git/gitops-server-test-job.patch
 | 
			
		||||
    # -- Update Chart.ymal
 | 
			
		||||
    #- name: Change the chart name
 | 
			
		||||
    #  yq:
 | 
			
		||||
    #    op: Replace
 | 
			
		||||
    #    file: Chart.yaml
 | 
			
		||||
    #    key: .name
 | 
			
		||||
    #    value: gitops-server
 | 
			
		||||
    - name: Set the home URL
 | 
			
		||||
      yq:
 | 
			
		||||
        op: Add
 | 
			
		||||
        file: Chart.yaml
 | 
			
		||||
        key: .home
 | 
			
		||||
        value: https://github.com/giantswarm/gitops-server-app
 | 
			
		||||
    - name: set the icon url
 | 
			
		||||
      yq:
 | 
			
		||||
        op: Add
 | 
			
		||||
        file: Chart.yaml
 | 
			
		||||
        key: .icon
 | 
			
		||||
        value: https://s.giantswarm.io/app-icons/weaveworks/1/icon_light.svg
 | 
			
		||||
    - name: Add keywords
 | 
			
		||||
      yq:
 | 
			
		||||
        op: Add
 | 
			
		||||
        file: Chart.yaml
 | 
			
		||||
        key: .keywords
 | 
			
		||||
        value: '["gitops", "flux"]'
 | 
			
		||||
    - name: team annotation
 | 
			
		||||
    - name: gs version
 | 
			
		||||
      yq:
 | 
			
		||||
        op: Add
 | 
			
		||||
        key: .annotations."config.giantswarm.io/version"
 | 
			
		||||
        value: 1.x.x
 | 
			
		||||
        file: Chart.yaml
 | 
			
		||||
    - name: yamlfmt
 | 
			
		||||
							
								
								
									
										24
									
								
								helmule/examples/giantswarm/charts/zot.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								helmule/examples/giantswarm/charts/zot.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# -- Zot Application
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
name: zot
 | 
			
		||||
repository: zot-git
 | 
			
		||||
extensions:
 | 
			
		||||
  - name: Add VPA
 | 
			
		||||
    source_dir: ../extensions/vpa
 | 
			
		||||
    target_dir: templates/gs-vpa
 | 
			
		||||
  - name: Add values for CI
 | 
			
		||||
    source_dir: ../extensions/ci-values
 | 
			
		||||
    target_dir: ci
 | 
			
		||||
variables:
 | 
			
		||||
  target_repo: zot-app
 | 
			
		||||
patches:
 | 
			
		||||
  - name: team annotation
 | 
			
		||||
  - name: set home
 | 
			
		||||
  - name: set engine
 | 
			
		||||
  - name: yamlfmt
 | 
			
		||||
  - name: Git patch
 | 
			
		||||
    git:
 | 
			
		||||
      path: ../patches/git/zot.patch
 | 
			
		||||
mirrors:
 | 
			
		||||
  - apps-git
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
resources:
 | 
			
		||||
  requests:
 | 
			
		||||
    memory: 100Mi
 | 
			
		||||
    cpu: 70m
 | 
			
		||||
  limits:
 | 
			
		||||
    memory: 700Mi
 | 
			
		||||
    cpu: 400m
 | 
			
		||||
vpa:
 | 
			
		||||
  enabled: true
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
{{ if eq (include "resource.vpa.enabled" .) "true" }}
 | 
			
		||||
apiVersion: autoscaling.k8s.io/v1
 | 
			
		||||
kind: VerticalPodAutoscaler
 | 
			
		||||
metadata:
 | 
			
		||||
  name: gitops-server
 | 
			
		||||
  namespace: {{ .Release.Namespace }}
 | 
			
		||||
  labels:
 | 
			
		||||
    {{- include "chart.labels" . | nindent 4 }}
 | 
			
		||||
spec:
 | 
			
		||||
  resourcePolicy:
 | 
			
		||||
    containerPolicies:
 | 
			
		||||
    - containerName: {{ .Chart.Name }}
 | 
			
		||||
      controlledValues: RequestsAndLimits
 | 
			
		||||
      minAllowed:
 | 
			
		||||
        cpu: {{ .Values.giantswarm.resources.server.requests.cpu }}
 | 
			
		||||
        memory: {{ .Values.giantswarm.resources.server.requests.memory }}
 | 
			
		||||
      maxAllowed:
 | 
			
		||||
        cpu: 1000m
 | 
			
		||||
        memory: 1000Mi
 | 
			
		||||
      mode: Auto
 | 
			
		||||
  targetRef:
 | 
			
		||||
    apiVersion: apps/v1
 | 
			
		||||
    kind: Deployment
 | 
			
		||||
    name: {{ include "chart.fullname" . }}
 | 
			
		||||
  updatePolicy:
 | 
			
		||||
    updateMode: Auto
 | 
			
		||||
{{ end }}
 | 
			
		||||
							
								
								
									
										51
									
								
								helmule/examples/giantswarm/extensions/vpa/_helpers.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								helmule/examples/giantswarm/extensions/vpa/_helpers.tpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
{{/*
 | 
			
		||||
Expand the name of the chart.
 | 
			
		||||
*/}}
 | 
			
		||||
{{- define "chart.name" -}}
 | 
			
		||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{/*
 | 
			
		||||
Create a default fully qualified app name.
 | 
			
		||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
 | 
			
		||||
If release name contains chart name it will be used as a full name.
 | 
			
		||||
*/}}
 | 
			
		||||
{{- define "chart.fullname" -}}
 | 
			
		||||
{{- if .Values.fullnameOverride }}
 | 
			
		||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
 | 
			
		||||
{{- else }}
 | 
			
		||||
{{- $name := default .Chart.Name .Values.nameOverride }}
 | 
			
		||||
{{- if contains $name .Release.Name }}
 | 
			
		||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
 | 
			
		||||
{{- else }}
 | 
			
		||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{/*
 | 
			
		||||
Create chart name and version as used by the chart label.
 | 
			
		||||
*/}}
 | 
			
		||||
{{- define "chart.chart" -}}
 | 
			
		||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{/*
 | 
			
		||||
Common labels
 | 
			
		||||
*/}}
 | 
			
		||||
{{- define "chart.labels" -}}
 | 
			
		||||
helm.sh/chart: {{ include "chart.chart" . }}
 | 
			
		||||
{{ include "chart.selectorLabels" . }}
 | 
			
		||||
{{- if .Chart.AppVersion }}
 | 
			
		||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{/*
 | 
			
		||||
Selector labels
 | 
			
		||||
*/}}
 | 
			
		||||
{{- define "chart.selectorLabels" -}}
 | 
			
		||||
app.kubernetes.io/name: {{ include "chart.name" . }}
 | 
			
		||||
app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										29
									
								
								helmule/examples/giantswarm/extensions/vpa/vpa.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								helmule/examples/giantswarm/extensions/vpa/vpa.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
{{ if (.Values.vpa).enabled }}
 | 
			
		||||
{{ if .Values.resources }}
 | 
			
		||||
apiVersion: autoscaling.k8s.io/v1
 | 
			
		||||
kind: VerticalPodAutoscaler
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "chart.fullname" . }}
 | 
			
		||||
  namespace: {{ .Release.Namespace }}
 | 
			
		||||
  labels:
 | 
			
		||||
    {{- include "chart.labels" . | nindent 4 }}
 | 
			
		||||
spec:
 | 
			
		||||
  resourcePolicy:
 | 
			
		||||
    containerPolicies:
 | 
			
		||||
    - containerName: manager
 | 
			
		||||
      controlledValues: RequestsAndLimits
 | 
			
		||||
      minAllowed:
 | 
			
		||||
        cpu: {{ .Values.resources.requests.cpu }}
 | 
			
		||||
        memory: {{ .Values.resources.requests.memory }}
 | 
			
		||||
      maxAllowed:
 | 
			
		||||
        cpu: {{ .Values.resources.limits.cpu }}
 | 
			
		||||
        memory: {{ .Values.resources.limits.memory }}
 | 
			
		||||
      mode: Auto
 | 
			
		||||
  targetRef:
 | 
			
		||||
    apiVersion: apps/v1
 | 
			
		||||
    kind: Deployment
 | 
			
		||||
    name: {{ include "chart.fullname" . }}
 | 
			
		||||
  updatePolicy:
 | 
			
		||||
    updateMode: Auto
 | 
			
		||||
{{- end }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
							
								
								
									
										58
									
								
								helmule/examples/giantswarm/helmule.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								helmule/examples/giantswarm/helmule.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
variables:
 | 
			
		||||
  global: example
 | 
			
		||||
 | 
			
		||||
include:
 | 
			
		||||
  - kind: Charts
 | 
			
		||||
    path: ./charts/zot.yaml
 | 
			
		||||
  - kind: Charts
 | 
			
		||||
    path: ./charts/gitops-server.yaml
 | 
			
		||||
  - kind: Charts
 | 
			
		||||
    path: ./charts/external-secrets-operator.yaml
 | 
			
		||||
patches:
 | 
			
		||||
  - name: yamlfmt
 | 
			
		||||
    custom_command:
 | 
			
		||||
      commands:
 | 
			
		||||
        - "cat <<EOT >> .yamlfmt\n  formatter:\n    pad_line_comments: 2\nEOT"
 | 
			
		||||
        - yamlfmt values.yaml --conf ./yamlfmt.yaml
 | 
			
		||||
        - rm -f yamlfmt.yaml
 | 
			
		||||
  - name: team annotation
 | 
			
		||||
    yq:
 | 
			
		||||
      op: Add
 | 
			
		||||
      key: .annotations."application.giantswarm.io/team"
 | 
			
		||||
      value: team-honeybadger
 | 
			
		||||
      file: Chart.yaml
 | 
			
		||||
  - name: set home
 | 
			
		||||
    yq:
 | 
			
		||||
      op: Add
 | 
			
		||||
      key: .home
 | 
			
		||||
      value: https://github.com/giantswarm/flux-app
 | 
			
		||||
      file: Chart.yaml
 | 
			
		||||
  - name: set engine
 | 
			
		||||
    yq:
 | 
			
		||||
      op: Add
 | 
			
		||||
      key: .engine
 | 
			
		||||
      value: gtpl
 | 
			
		||||
      file: Chart.yaml
 | 
			
		||||
repositories:
 | 
			
		||||
  # -- Because their helm repo seems not to be accessible
 | 
			
		||||
  - name: zot-git
 | 
			
		||||
    git:
 | 
			
		||||
      url: https://github.com/project-zot/helm-charts.git
 | 
			
		||||
      git_ref: zot-0.1.42
 | 
			
		||||
      path: charts
 | 
			
		||||
  - name: weave
 | 
			
		||||
    helm:
 | 
			
		||||
      url: https://helm.gitops.weave.works
 | 
			
		||||
  - name: external-secrets
 | 
			
		||||
    helm:
 | 
			
		||||
      url: https://charts.external-secrets.io
 | 
			
		||||
mirrors:
 | 
			
		||||
  - name: apps-git
 | 
			
		||||
    git:
 | 
			
		||||
      url: git@git.badhouseplants.net:allanger/{{ variables.target_repo }}.git
 | 
			
		||||
      git_dir: app-{{ name }}-git
 | 
			
		||||
      branch: upgrade-{{ name }}-to-{{ version }}
 | 
			
		||||
      path: helm/{{ name }}
 | 
			
		||||
      commit: |-
 | 
			
		||||
        chore: mirror {{ name }}-{{ version }}
 | 
			
		||||
        upstream_repo: {{ repo_url }}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
diff --git a/templates/tests/test-connection.yaml b/templates/tests/test-connection.yaml
 | 
			
		||||
index 8dfed87..b4b98bc 100644
 | 
			
		||||
--- a/templates/tests/test-connection.yaml
 | 
			
		||||
+++ b/templates/tests/test-connection.yaml
 | 
			
		||||
@@ -9,7 +9,13 @@ metadata:
 | 
			
		||||
 spec:
 | 
			
		||||
   containers:
 | 
			
		||||
     - name: wget
 | 
			
		||||
-      image: busybox
 | 
			
		||||
+      image: "{{ .Values.image.registry }}/{{ .Values.giantswarm.images.test.image }}:{{ .Values.giantswarm.images.test.tag }}"
 | 
			
		||||
+      imagePullPolicy: {{ .Values.giantswarm.images.test.pullPolicy }}
 | 
			
		||||
       command: ['wget']
 | 
			
		||||
       args: ['{{ include "chart.fullname" . }}:{{ .Values.service.port }}']
 | 
			
		||||
+      securityContext:
 | 
			
		||||
+        readOnlyRootFilesystem: true
 | 
			
		||||
+        runAsUser: 1000
 | 
			
		||||
+      resources:
 | 
			
		||||
+        {{- toYaml .Values.giantswarm.resources.test | nindent 8 }}
 | 
			
		||||
   restartPolicy: Never
 | 
			
		||||
@@ -0,0 +1,210 @@
 | 
			
		||||
diff --git a/values.schema.json b/values.schema.json
 | 
			
		||||
index f759f82..c0762fa 100644
 | 
			
		||||
--- a/values.schema.json
 | 
			
		||||
+++ b/values.schema.json
 | 
			
		||||
@@ -43,10 +43,51 @@
 | 
			
		||||
             }
 | 
			
		||||
         },
 | 
			
		||||
         "extraVolumeMounts": {
 | 
			
		||||
-            "type": "array"
 | 
			
		||||
+            "type": "array",
 | 
			
		||||
+            "items": {
 | 
			
		||||
+                "type": "object",
 | 
			
		||||
+                "properties": {
 | 
			
		||||
+                    "mountPath": {
 | 
			
		||||
+                        "type": "string"
 | 
			
		||||
+                    },
 | 
			
		||||
+                    "name": {
 | 
			
		||||
+                        "type": "string"
 | 
			
		||||
+                    },
 | 
			
		||||
+                    "readOnly": {
 | 
			
		||||
+                        "type": "boolean"
 | 
			
		||||
+                    }
 | 
			
		||||
+                }
 | 
			
		||||
+            }
 | 
			
		||||
         },
 | 
			
		||||
         "extraVolumes": {
 | 
			
		||||
-            "type": "array"
 | 
			
		||||
+            "type": "array",
 | 
			
		||||
+            "items": {
 | 
			
		||||
+                "type": "object",
 | 
			
		||||
+                "properties": {
 | 
			
		||||
+                    "csi": {
 | 
			
		||||
+                        "type": "object",
 | 
			
		||||
+                        "properties": {
 | 
			
		||||
+                            "driver": {
 | 
			
		||||
+                                "type": "string"
 | 
			
		||||
+                            },
 | 
			
		||||
+                            "readOnly": {
 | 
			
		||||
+                                "type": "boolean"
 | 
			
		||||
+                            },
 | 
			
		||||
+                            "volumeAttributes": {
 | 
			
		||||
+                                "type": "object",
 | 
			
		||||
+                                "properties": {
 | 
			
		||||
+                                    "secretProviderClass": {
 | 
			
		||||
+                                        "type": "string"
 | 
			
		||||
+                                    }
 | 
			
		||||
+                                }
 | 
			
		||||
+                            }
 | 
			
		||||
+                        }
 | 
			
		||||
+                    },
 | 
			
		||||
+                    "name": {
 | 
			
		||||
+                        "type": "string"
 | 
			
		||||
+                    }
 | 
			
		||||
+                }
 | 
			
		||||
+            }
 | 
			
		||||
         },
 | 
			
		||||
         "fullnameOverride": {
 | 
			
		||||
             "type": "string"
 | 
			
		||||
@@ -91,7 +132,30 @@
 | 
			
		||||
                     "type": "object",
 | 
			
		||||
                     "properties": {
 | 
			
		||||
                         "additionalRules": {
 | 
			
		||||
-                            "type": "array"
 | 
			
		||||
+                            "type": "array",
 | 
			
		||||
+                            "items": {
 | 
			
		||||
+                                "type": "object",
 | 
			
		||||
+                                "properties": {
 | 
			
		||||
+                                    "apiGroups": {
 | 
			
		||||
+                                        "type": "array",
 | 
			
		||||
+                                        "items": {
 | 
			
		||||
+                                            "type": "string"
 | 
			
		||||
+                                        }
 | 
			
		||||
+                                    },
 | 
			
		||||
+                                    "resources": {
 | 
			
		||||
+                                        "type": "array",
 | 
			
		||||
+                                        "items": {
 | 
			
		||||
+                                            "type": "string"
 | 
			
		||||
+                                        }
 | 
			
		||||
+                                    },
 | 
			
		||||
+                                    "verbs": {
 | 
			
		||||
+                                        "type": "array",
 | 
			
		||||
+                                        "items": {
 | 
			
		||||
+                                            "type": "string"
 | 
			
		||||
+                                        }
 | 
			
		||||
+                                    }
 | 
			
		||||
+                                }
 | 
			
		||||
+                            }
 | 
			
		||||
                         },
 | 
			
		||||
                         "create": {
 | 
			
		||||
                             "type": "boolean"
 | 
			
		||||
@@ -106,7 +170,10 @@
 | 
			
		||||
                                             "type": "boolean"
 | 
			
		||||
                                         },
 | 
			
		||||
                                         "resourceNames": {
 | 
			
		||||
-                                            "type": "array"
 | 
			
		||||
+                                            "type": "array",
 | 
			
		||||
+                                            "items": {
 | 
			
		||||
+                                                "type": "string"
 | 
			
		||||
+                                            }
 | 
			
		||||
                                         }
 | 
			
		||||
                                     }
 | 
			
		||||
                                 },
 | 
			
		||||
@@ -117,7 +184,10 @@
 | 
			
		||||
                                             "type": "boolean"
 | 
			
		||||
                                         },
 | 
			
		||||
                                         "resourceNames": {
 | 
			
		||||
-                                            "type": "array"
 | 
			
		||||
+                                            "type": "array",
 | 
			
		||||
+                                            "items": {
 | 
			
		||||
+                                                "type": "string"
 | 
			
		||||
+                                            }
 | 
			
		||||
                                         }
 | 
			
		||||
                                     }
 | 
			
		||||
                                 }
 | 
			
		||||
@@ -134,6 +204,14 @@
 | 
			
		||||
                 "resources": {
 | 
			
		||||
                     "type": "object",
 | 
			
		||||
                     "properties": {
 | 
			
		||||
+                        "vpa":{
 | 
			
		||||
+                          "type": "object",
 | 
			
		||||
+                          "properties": {
 | 
			
		||||
+                            "enabled": {
 | 
			
		||||
+                              "type": "boolean"
 | 
			
		||||
+                            }
 | 
			
		||||
+                          }
 | 
			
		||||
+                        },
 | 
			
		||||
                         "server": {
 | 
			
		||||
                             "type": "object",
 | 
			
		||||
                             "properties": {
 | 
			
		||||
@@ -187,14 +265,6 @@
 | 
			
		||||
                                     }
 | 
			
		||||
                                 }
 | 
			
		||||
                             }
 | 
			
		||||
-                        },
 | 
			
		||||
-                        "vpa": {
 | 
			
		||||
-                            "type": "object",
 | 
			
		||||
-                            "properties": {
 | 
			
		||||
-                                "enabled": {
 | 
			
		||||
-                                    "type": "boolean"
 | 
			
		||||
-                                }
 | 
			
		||||
-                            }
 | 
			
		||||
                         }
 | 
			
		||||
                     }
 | 
			
		||||
                 }
 | 
			
		||||
@@ -209,7 +279,15 @@
 | 
			
		||||
             }
 | 
			
		||||
         },
 | 
			
		||||
         "imagePullSecrets": {
 | 
			
		||||
-            "type": "array"
 | 
			
		||||
+            "type": "array",
 | 
			
		||||
+            "items": {
 | 
			
		||||
+                "type": "object",
 | 
			
		||||
+                "properties": {
 | 
			
		||||
+                    "name": {
 | 
			
		||||
+                        "type": "string"
 | 
			
		||||
+                    }
 | 
			
		||||
+                }
 | 
			
		||||
+            }
 | 
			
		||||
         },
 | 
			
		||||
         "ingress": {
 | 
			
		||||
             "type": "object",
 | 
			
		||||
@@ -224,10 +302,46 @@
 | 
			
		||||
                     "type": "boolean"
 | 
			
		||||
                 },
 | 
			
		||||
                 "hosts": {
 | 
			
		||||
-                    "type": "array"
 | 
			
		||||
+                    "type": "array",
 | 
			
		||||
+                    "items": {
 | 
			
		||||
+                        "type": "object",
 | 
			
		||||
+                        "properties": {
 | 
			
		||||
+                            "host": {
 | 
			
		||||
+                                "type": "string"
 | 
			
		||||
+                            },
 | 
			
		||||
+                            "paths": {
 | 
			
		||||
+                                "type": "array",
 | 
			
		||||
+                                "items": {
 | 
			
		||||
+                                    "type": "object",
 | 
			
		||||
+                                    "properties": {
 | 
			
		||||
+                                        "path": {
 | 
			
		||||
+                                            "type": "string"
 | 
			
		||||
+                                        },
 | 
			
		||||
+                                        "pathType": {
 | 
			
		||||
+                                            "type": "string"
 | 
			
		||||
+                                        }
 | 
			
		||||
+                                    }
 | 
			
		||||
+                                }
 | 
			
		||||
+                            }
 | 
			
		||||
+                        }
 | 
			
		||||
+                    }
 | 
			
		||||
                 },
 | 
			
		||||
                 "tls": {
 | 
			
		||||
-                    "type": "array"
 | 
			
		||||
+                    "type": "array",
 | 
			
		||||
+                    "items": {
 | 
			
		||||
+                        "type": "object",
 | 
			
		||||
+                        "properties": {
 | 
			
		||||
+                            "hosts": {
 | 
			
		||||
+                                "type": "array",
 | 
			
		||||
+                                "items": {
 | 
			
		||||
+                                    "type": "string"
 | 
			
		||||
+                                }
 | 
			
		||||
+                            },
 | 
			
		||||
+                            "secretName": {
 | 
			
		||||
+                                "type": "string"
 | 
			
		||||
+                            }
 | 
			
		||||
+                        }
 | 
			
		||||
+                    }
 | 
			
		||||
                 }
 | 
			
		||||
             }
 | 
			
		||||
         },
 | 
			
		||||
							
								
								
									
										307
									
								
								helmule/examples/giantswarm/patches/git/gitops-server.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								helmule/examples/giantswarm/patches/git/gitops-server.patch
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,307 @@
 | 
			
		||||
diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl
 | 
			
		||||
index af32c5b..1fdf723 100644
 | 
			
		||||
--- a/templates/_helpers.tpl
 | 
			
		||||
+++ b/templates/_helpers.tpl
 | 
			
		||||
@@ -39,6 +39,7 @@ helm.sh/chart: {{ include "chart.chart" . }}
 | 
			
		||||
 {{- if .Chart.AppVersion }}
 | 
			
		||||
 app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
 | 
			
		||||
 {{- end }}
 | 
			
		||||
+application.giantswarm.io/team: {{ index .Chart.Annotations "application.giantswarm.io/team" | quote }}
 | 
			
		||||
 app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
 {{- end }}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,3 +76,16 @@ Return the target Kubernetes version
 | 
			
		||||
 {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}}
 | 
			
		||||
 {{- end -}}
 | 
			
		||||
 {{- end -}}
 | 
			
		||||
+
 | 
			
		||||
+{{- define "resource.vpa.enabled" -}}
 | 
			
		||||
+{{- if and (.Capabilities.APIVersions.Has "autoscaling.k8s.io/v1") (.Values.giantswarm.resources.vpa.enabled) }}true{{ else }}false{{ end }}
 | 
			
		||||
+{{- end -}}
 | 
			
		||||
+
 | 
			
		||||
+{{- define "deployment.resources" -}}
 | 
			
		||||
+requests:
 | 
			
		||||
+{{ toYaml .Values.giantswarm.resources.server.requests | indent 2 -}}
 | 
			
		||||
+{{ if eq (include "resource.vpa.enabled" .) "false" }}
 | 
			
		||||
+limits:
 | 
			
		||||
+{{ toYaml .Values.giantswarm.resources.server.limits | indent 2 -}}
 | 
			
		||||
+{{- end -}}
 | 
			
		||||
+{{- end -}}
 | 
			
		||||
diff --git a/templates/admin-user-roles.yaml b/templates/admin-user-roles.yaml
 | 
			
		||||
index 74a1844..c0fa72c 100644
 | 
			
		||||
--- a/templates/admin-user-roles.yaml
 | 
			
		||||
+++ b/templates/admin-user-roles.yaml
 | 
			
		||||
@@ -30,8 +30,8 @@ rules:
 | 
			
		||||
     resources: ["terraforms"]
 | 
			
		||||
     verbs: [ "get", "list", "watch", "patch" ]
 | 
			
		||||
 
 | 
			
		||||
-{{- if gt (len $.Values.rbac.additionalRules) 0 -}}
 | 
			
		||||
-{{- toYaml $.Values.rbac.additionalRules | nindent 2 -}}
 | 
			
		||||
+{{- if gt (len $.Values.giantswarm.rbac.additionalRules) 0 -}}
 | 
			
		||||
+{{- toYaml $.Values.giantswarm.rbac.additionalRules | nindent 2 -}}
 | 
			
		||||
 {{- end }}
 | 
			
		||||
 {{- if .Values.adminUser.createClusterRole }}
 | 
			
		||||
 ---
 | 
			
		||||
@@ -72,8 +72,8 @@ rules:
 | 
			
		||||
     resources: [ "providers", "alerts" ]
 | 
			
		||||
     verbs: [ "get", "list", "watch", "patch" ]
 | 
			
		||||
 
 | 
			
		||||
-{{- if gt (len $.Values.rbac.additionalRules) 0 -}}
 | 
			
		||||
-{{- toYaml $.Values.rbac.additionalRules | nindent 2 -}}
 | 
			
		||||
+{{- if gt (len $.Values.giantswarm.rbac.additionalRules) 0 -}}
 | 
			
		||||
+{{- toYaml $.Values.giantswarm.rbac.additionalRules | nindent 2 -}}
 | 
			
		||||
 {{- end -}}
 | 
			
		||||
 {{- end }}
 | 
			
		||||
 {{- end }}
 | 
			
		||||
diff --git a/templates/deployment.yaml b/templates/deployment.yaml
 | 
			
		||||
index a54c37c..a498259 100644
 | 
			
		||||
--- a/templates/deployment.yaml
 | 
			
		||||
+++ b/templates/deployment.yaml
 | 
			
		||||
@@ -36,8 +36,8 @@ spec:
 | 
			
		||||
         - name: {{ .Chart.Name }}
 | 
			
		||||
           securityContext:
 | 
			
		||||
             {{- toYaml .Values.securityContext | nindent 12 }}
 | 
			
		||||
-          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
 | 
			
		||||
-          imagePullPolicy: {{ .Values.image.pullPolicy }}
 | 
			
		||||
+          image: "{{ .Values.image.registry }}/{{ .Values.giantswarm.images.server.image }}:{{ .Values.giantswarm.images.server.tag  | default .Chart.AppVersion }}"
 | 
			
		||||
+          imagePullPolicy: {{ .Values.giantswarm.images.server.pullPolicy }}
 | 
			
		||||
           args:
 | 
			
		||||
             - "--log-level"
 | 
			
		||||
             - "{{ .Values.logLevel }}"
 | 
			
		||||
@@ -88,7 +88,7 @@ spec:
 | 
			
		||||
           {{- end }}
 | 
			
		||||
           {{- end }}
 | 
			
		||||
           resources:
 | 
			
		||||
-            {{- toYaml .Values.resources | nindent 12 }}
 | 
			
		||||
+            {{- include "deployment.resources" . | nindent 12 }}
 | 
			
		||||
           {{- if or .Values.serverTLS.enable .Values.extraVolumeMounts }}
 | 
			
		||||
           volumeMounts:
 | 
			
		||||
           {{- end }}
 | 
			
		||||
diff --git a/templates/role.yaml b/templates/role.yaml
 | 
			
		||||
index b292176..5a55339 100644
 | 
			
		||||
--- a/templates/role.yaml
 | 
			
		||||
+++ b/templates/role.yaml
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
-{{- if .Values.rbac.create -}}
 | 
			
		||||
+{{- if .Values.giantswarm.rbac.create -}}
 | 
			
		||||
 {{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}}
 | 
			
		||||
 apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
			
		||||
 {{- else }}
 | 
			
		||||
@@ -6,32 +6,39 @@ apiVersion: rbac.authorization.k8s.io/v1
 | 
			
		||||
 {{- end }}
 | 
			
		||||
 kind: ClusterRole
 | 
			
		||||
 metadata:
 | 
			
		||||
-  name:  {{ include "chart.fullname" . }}
 | 
			
		||||
+  name: {{ include "chart.fullname" . }}
 | 
			
		||||
 rules:
 | 
			
		||||
   # impersonation rules for ui calls
 | 
			
		||||
+  {{- if .Values.giantswarm.rbac.impersonation.users.enabled }}
 | 
			
		||||
   - apiGroups: [""]
 | 
			
		||||
-    resources: {{ .Values.rbac.impersonationResources | toJson }}
 | 
			
		||||
+    resources: ["users"]
 | 
			
		||||
     verbs: [ "impersonate" ]
 | 
			
		||||
-    {{- with .Values.rbac.impersonationResourceNames }}
 | 
			
		||||
+    {{- with .Values.giantswarm.rbac.impersonation.users.resourceNames }}
 | 
			
		||||
     resourceNames: {{ . | toJson }}
 | 
			
		||||
     {{- end }}
 | 
			
		||||
+  {{- end }}
 | 
			
		||||
+  {{- if .Values.giantswarm.rbac.impersonation.groups.enabled }}
 | 
			
		||||
+  {{- if and .Values.giantswarm.rbac.impersonation.groups.enabled (not .Values.giantswarm.rbac.impersonation.users.enabled) }}
 | 
			
		||||
+  {{- fail "Enabling impersonation for groups requires users impersonation permissions, see https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation" }}
 | 
			
		||||
+  {{- end }}
 | 
			
		||||
+  - apiGroups: [""]
 | 
			
		||||
+    resources: ["groups"]
 | 
			
		||||
+    verbs: [ "impersonate" ]
 | 
			
		||||
+    {{- with .Values.giantswarm.rbac.impersonation.groups.resourceNames }}
 | 
			
		||||
+    resourceNames: {{ . | toJson }}
 | 
			
		||||
+    {{- end }}
 | 
			
		||||
+  {{- end }}
 | 
			
		||||
   # Access to enterprise entitlement
 | 
			
		||||
   - apiGroups: [""]
 | 
			
		||||
     resources: [ "secrets" ]
 | 
			
		||||
     verbs: [ "get", "list" ]
 | 
			
		||||
-    {{- if and .Values.rbac.viewSecrets .Values.rbac.viewSecretsResourceNames }}
 | 
			
		||||
-    {{- fail "You've supplied both rbac.viewSecrets and rbac.viewSecretsResourceNames. Please only use rbac.viewSecretsResourceNames" }}
 | 
			
		||||
-    {{- end }}
 | 
			
		||||
-    # or should return the first non-falsy result
 | 
			
		||||
-    {{- with (or .Values.rbac.viewSecretsResourceNames .Values.rbac.viewSecrets) }}
 | 
			
		||||
+    {{- with .Values.giantswarm.rbac.viewSecretsResourceNames }}
 | 
			
		||||
     resourceNames: {{ . | toJson }}
 | 
			
		||||
     {{- end }}
 | 
			
		||||
-
 | 
			
		||||
   # The service account needs to read namespaces to know where it can query
 | 
			
		||||
   - apiGroups: [ "" ]
 | 
			
		||||
     resources: [ "namespaces" ]
 | 
			
		||||
     verbs: [ "get", "list", "watch" ]
 | 
			
		||||
-
 | 
			
		||||
   # The service account needs to list custom resources to query if given feature
 | 
			
		||||
   # is available or not.
 | 
			
		||||
   - apiGroups: [ "apiextensions.k8s.io" ]
 | 
			
		||||
diff --git a/templates/rolebinding.yaml b/templates/rolebinding.yaml
 | 
			
		||||
index b8756fe..df718ff 100644
 | 
			
		||||
--- a/templates/rolebinding.yaml
 | 
			
		||||
+++ b/templates/rolebinding.yaml
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
-{{- if .Values.rbac.create -}}
 | 
			
		||||
+{{- if .Values.giantswarm.rbac.create -}}
 | 
			
		||||
 {{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}}
 | 
			
		||||
 apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
			
		||||
 {{- else }}
 | 
			
		||||
@@ -9,7 +9,7 @@ metadata:
 | 
			
		||||
   name:  {{ include "chart.fullname" . }}
 | 
			
		||||
   labels:
 | 
			
		||||
     {{- include "chart.labels" . | nindent 4 }}
 | 
			
		||||
-  {{- with .Values.rbac.annotations }}
 | 
			
		||||
+  {{- with .Values.giantswarm.rbac.annotations }}
 | 
			
		||||
   annotations:
 | 
			
		||||
     {{- toYaml . | nindent 4 }}
 | 
			
		||||
   {{- end }}
 | 
			
		||||
diff --git a/values.yaml b/values.yaml
 | 
			
		||||
index 374ad32..7b3b35f 100644
 | 
			
		||||
--- a/values.yaml
 | 
			
		||||
+++ b/values.yaml
 | 
			
		||||
@@ -1,16 +1,57 @@
 | 
			
		||||
-# Default values for chart.
 | 
			
		||||
-# This is a YAML-formatted file.
 | 
			
		||||
-# Declare variables to be passed into your templates.
 | 
			
		||||
+giantswarm:
 | 
			
		||||
+  images:
 | 
			
		||||
+    server:
 | 
			
		||||
+      image: giantswarm/weaveworks-wego-app
 | 
			
		||||
+      pullPolicy: IfNotPresent
 | 
			
		||||
+      tag: v0.18.0
 | 
			
		||||
+    test:
 | 
			
		||||
+      image: giantswarm/busybox
 | 
			
		||||
+      pullPolicy: IfNotPresent
 | 
			
		||||
+      tag: 1.36.0
 | 
			
		||||
+  resources:
 | 
			
		||||
+    vpa:
 | 
			
		||||
+      enabled: true
 | 
			
		||||
+    server:
 | 
			
		||||
+      limits:
 | 
			
		||||
+        cpu: 200m
 | 
			
		||||
+        memory: 256Mi
 | 
			
		||||
+      requests:
 | 
			
		||||
+        cpu: 100m
 | 
			
		||||
+        memory: 128Mi
 | 
			
		||||
+    test:
 | 
			
		||||
+      requests:
 | 
			
		||||
+        cpu: 10m
 | 
			
		||||
+        memory: 2Mi
 | 
			
		||||
+      limits:
 | 
			
		||||
+        cpu: 10m
 | 
			
		||||
+        memory: 4Mi
 | 
			
		||||
+  rbac:
 | 
			
		||||
+    create: true
 | 
			
		||||
+    impersonation:
 | 
			
		||||
+      users:
 | 
			
		||||
+        enabled: true
 | 
			
		||||
+        # -- If non-empty, this limits the users names that the service account
 | 
			
		||||
+        # can impersonate, e.g. `['user1@corporation.com', 'user2@corporation.com']`
 | 
			
		||||
+        resourceNames: []
 | 
			
		||||
+      groups:
 | 
			
		||||
+        enabled: true
 | 
			
		||||
+        # -- If non-empty, this limits the groups names that the service account
 | 
			
		||||
+        # can impersonate, e.g. `['admins', 'operations', 'devops']`
 | 
			
		||||
+        resourceNames: []
 | 
			
		||||
+    # -- If non-empty, this limits the secrets that can be accessed by
 | 
			
		||||
+    # the service account to the specified ones, e.g. `['weave-gitops-enterprise-credentials']`
 | 
			
		||||
+    viewSecretsResourceNames: ["cluster-user-auth", "oidc-auth"]
 | 
			
		||||
+    # -- If non-empty, these additional rules will be appended to the RBAC role and the cluster role.
 | 
			
		||||
+    # for example,
 | 
			
		||||
+    # additionalRules:
 | 
			
		||||
+    # - apiGroups: ["infra.contrib.fluxcd.io"]
 | 
			
		||||
+    #   resources: ["terraforms"]
 | 
			
		||||
+    #   verbs: [ "get", "list", "patch" ]
 | 
			
		||||
+    additionalRules: []
 | 
			
		||||
 
 | 
			
		||||
-# Note: paragraphs starting with `# --` will end up in our manual -
 | 
			
		||||
-# see https://github.com/norwoodj/helm-docs
 | 
			
		||||
 replicaCount: 1
 | 
			
		||||
 image:
 | 
			
		||||
-  # FIXME check the app name
 | 
			
		||||
-  repository: ghcr.io/weaveworks/wego-app
 | 
			
		||||
-  pullPolicy: IfNotPresent
 | 
			
		||||
-  # Overrides the image tag whose default is the chart appVersion.
 | 
			
		||||
-  tag: "v0.18.0"
 | 
			
		||||
+  registry: gsoci.azurecr.io
 | 
			
		||||
 imagePullSecrets: []
 | 
			
		||||
 nameOverride: ""
 | 
			
		||||
 fullnameOverride: ""
 | 
			
		||||
@@ -43,28 +84,9 @@ serviceAccount:
 | 
			
		||||
   # -- The name of the service account to use.
 | 
			
		||||
   # If not set and create is true, a name is generated using the fullname template
 | 
			
		||||
   name: ""
 | 
			
		||||
-rbac:
 | 
			
		||||
-  # -- Specifies whether the clusterRole & binding to the service account should be created
 | 
			
		||||
-  create: true
 | 
			
		||||
-  # -- If non-empty, this limits the resources that the service
 | 
			
		||||
-  # account can impersonate. This applies to both users and groups, e.g.
 | 
			
		||||
-  # `['user1@corporation.com', 'user2@corporation.com', 'operations']`
 | 
			
		||||
-  impersonationResourceNames: []
 | 
			
		||||
-  # -- Limit the type of principal that can be impersonated
 | 
			
		||||
-  impersonationResources: ["users", "groups"]
 | 
			
		||||
-  # -- If non-empty, this limits the secrets that can be accessed by
 | 
			
		||||
-  # the service account to the specified ones, e.g. `['weave-gitops-enterprise-credentials']`
 | 
			
		||||
-  viewSecretsResourceNames: ["cluster-user-auth", "oidc-auth"]
 | 
			
		||||
-  # -- If non-empty, these additional rules will be appended to the RBAC role and the cluster role.
 | 
			
		||||
-  # for example,
 | 
			
		||||
-  # additionalRules:
 | 
			
		||||
-  # - apiGroups: ["infra.contrib.fluxcd.io"]
 | 
			
		||||
-  #   resources: ["terraforms"]
 | 
			
		||||
-  #   verbs: [ "get", "list", "patch" ]
 | 
			
		||||
-  additionalRules: []
 | 
			
		||||
 adminUser:
 | 
			
		||||
   # -- Whether the local admin user should be created.
 | 
			
		||||
-  # If you use this make sure you add it to `rbac.impersonationResourceNames`.
 | 
			
		||||
+  # If you use this make sure you add it to `giantswarm.rbac.impersonation.users.resourceNames`.
 | 
			
		||||
   create: false
 | 
			
		||||
   # -- Specifies whether the clusterRole & binding to the admin user should be created.
 | 
			
		||||
   # Will be created only if `adminUser.create` is enabled. Without this,
 | 
			
		||||
@@ -82,7 +104,7 @@ adminUser:
 | 
			
		||||
   # -- (string) Set the password for local admin user. Requires `adminUser.create` and `adminUser.createSecret`
 | 
			
		||||
   # This needs to have been hashed using bcrypt.
 | 
			
		||||
   # You can do this via our CLI with `gitops get bcrypt-hash`.
 | 
			
		||||
-  passwordHash:
 | 
			
		||||
+  passwordHash: ""
 | 
			
		||||
 podAnnotations: {}
 | 
			
		||||
 podLabels: {}
 | 
			
		||||
 # aadpodidbinding: identity
 | 
			
		||||
@@ -111,7 +133,7 @@ ingress:
 | 
			
		||||
   annotations: {}
 | 
			
		||||
   # kubernetes.io/ingress.class: nginx
 | 
			
		||||
   # kubernetes.io/tls-acme: "true"
 | 
			
		||||
-  hosts:
 | 
			
		||||
+  hosts: []
 | 
			
		||||
   # - host: chart-example.local
 | 
			
		||||
   #   paths:
 | 
			
		||||
   #     - path: /
 | 
			
		||||
@@ -123,8 +145,8 @@ ingress:
 | 
			
		||||
   #      - chart-example.local
 | 
			
		||||
 extraVolumes: []
 | 
			
		||||
 extraVolumeMounts: []
 | 
			
		||||
-# Example using extraVolumes and extraVolumeMounts to load 'oidc-auth' secret 
 | 
			
		||||
-# with a secrets store CSI driver. Specify the secretName 'oidc-auth' in the 
 | 
			
		||||
+# Example using extraVolumes and extraVolumeMounts to load 'oidc-auth' secret
 | 
			
		||||
+# with a secrets store CSI driver. Specify the secretName 'oidc-auth' in the
 | 
			
		||||
 # secretProviderClass so this will be created by the secrets store CSI driver.
 | 
			
		||||
 # See https://secrets-store-csi-driver.sigs.k8s.io/topics/sync-as-kubernetes-secret.html
 | 
			
		||||
 # extraVolumeMounts:
 | 
			
		||||
@@ -138,17 +160,6 @@ extraVolumeMounts: []
 | 
			
		||||
 #       readOnly: true
 | 
			
		||||
 #       volumeAttributes:
 | 
			
		||||
 #         secretProviderClass: ww-gitops-oauth-provider
 | 
			
		||||
-resources: {}
 | 
			
		||||
-# We usually recommend not to specify default resources and to leave this as a conscious
 | 
			
		||||
-# choice for the user. This also increases chances charts run on environments with little
 | 
			
		||||
-# resources, such as Minikube. If you do want to specify resources, uncomment the following
 | 
			
		||||
-# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
 | 
			
		||||
-# limits:
 | 
			
		||||
-#   cpu: 100m
 | 
			
		||||
-#   memory: 128Mi
 | 
			
		||||
-# requests:
 | 
			
		||||
-#   cpu: 100m
 | 
			
		||||
-#   memory: 128Mi
 | 
			
		||||
 
 | 
			
		||||
 networkPolicy:
 | 
			
		||||
   # -- Specifies whether default network policies should be created.
 | 
			
		||||
							
								
								
									
										121
									
								
								helmule/examples/giantswarm/patches/git/zot.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								helmule/examples/giantswarm/patches/git/zot.patch
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
diff --git a/templates/deployment.yaml b/templates/deployment.yaml
 | 
			
		||||
index c48dda1..b6de3af 100644
 | 
			
		||||
--- a/templates/deployment.yaml
 | 
			
		||||
+++ b/templates/deployment.yaml
 | 
			
		||||
@@ -24,12 +24,28 @@ spec:
 | 
			
		||||
       {{- end }}
 | 
			
		||||
       serviceAccountName: {{ include "zot.serviceAccountName" . }}
 | 
			
		||||
       securityContext:
 | 
			
		||||
-        {{- toYaml .Values.podSecurityContext | nindent 8 }}
 | 
			
		||||
+        fsGroup: 1337
 | 
			
		||||
+        {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+        {{- with .Values.podSeccompProfile }}
 | 
			
		||||
+        seccompProfile:
 | 
			
		||||
+          {{- . | toYaml | nindent 10 }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
       containers:
 | 
			
		||||
         - name: {{ .Chart.Name }}
 | 
			
		||||
           securityContext:
 | 
			
		||||
-            {{- toYaml .Values.securityContext | nindent 12 }}
 | 
			
		||||
-          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
 | 
			
		||||
+            {{- with .Values.containerSecurityContext }}
 | 
			
		||||
+            {{- . | toYaml | nindent 12 }}
 | 
			
		||||
+            {{- end }}
 | 
			
		||||
+            readOnlyRootFilesystem: true
 | 
			
		||||
+            runAsUser: 100
 | 
			
		||||
+            {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+            {{- with .Values.seccompProfile }}
 | 
			
		||||
+            seccompProfile:
 | 
			
		||||
+              {{- . | toYaml | nindent 14 }}
 | 
			
		||||
+            {{- end }}
 | 
			
		||||
+            {{- end }}
 | 
			
		||||
+          image: "{{ .Values.image.registry }}/{{ .Values.image.image }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
 | 
			
		||||
           imagePullPolicy: {{ .Values.image.pullPolicy }}
 | 
			
		||||
           env:
 | 
			
		||||
             {{- toYaml .Values.env | nindent 12 }}
 | 
			
		||||
diff --git a/templates/tests/test-connection-fails.yaml b/templates/tests/test-connection-fails.yaml
 | 
			
		||||
index 0e7a059..6ec4916 100644
 | 
			
		||||
--- a/templates/tests/test-connection-fails.yaml
 | 
			
		||||
+++ b/templates/tests/test-connection-fails.yaml
 | 
			
		||||
@@ -8,8 +8,28 @@ metadata:
 | 
			
		||||
     "helm.sh/hook": test
 | 
			
		||||
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
 | 
			
		||||
 spec:
 | 
			
		||||
+  securityContext:
 | 
			
		||||
+    fsGroup: 1337
 | 
			
		||||
+    {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+    {{- with .Values.podSeccompProfile }}
 | 
			
		||||
+    seccompProfile:
 | 
			
		||||
+      {{- . | toYaml | nindent 10 }}
 | 
			
		||||
+    {{- end }}
 | 
			
		||||
+    {{- end }}
 | 
			
		||||
   containers:
 | 
			
		||||
     - name: wget
 | 
			
		||||
+      securityContext:
 | 
			
		||||
+        {{- with .Values.containerSecurityContext }}
 | 
			
		||||
+        {{- . | toYaml | nindent 12 }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
+        readOnlyRootFilesystem: true
 | 
			
		||||
+        runAsUser: 100
 | 
			
		||||
+        {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+        {{- with .Values.seccompProfile }}
 | 
			
		||||
+        seccompProfile:
 | 
			
		||||
+          {{- . | toYaml | nindent 14 }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
       image: alpine:3.18
 | 
			
		||||
       command:
 | 
			
		||||
       - sh
 | 
			
		||||
diff --git a/templates/tests/test-connection.yaml b/templates/tests/test-connection.yaml
 | 
			
		||||
index 59c64b4..2ded317 100644
 | 
			
		||||
--- a/templates/tests/test-connection.yaml
 | 
			
		||||
+++ b/templates/tests/test-connection.yaml
 | 
			
		||||
@@ -8,8 +8,28 @@ metadata:
 | 
			
		||||
     "helm.sh/hook": test
 | 
			
		||||
     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
 | 
			
		||||
 spec:
 | 
			
		||||
+  securityContext:
 | 
			
		||||
+    fsGroup: 1337
 | 
			
		||||
+    {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+    {{- with .Values.podSeccompProfile }}
 | 
			
		||||
+    seccompProfile:
 | 
			
		||||
+      {{- . | toYaml | nindent 10 }}
 | 
			
		||||
+    {{- end }}
 | 
			
		||||
+    {{- end }}
 | 
			
		||||
   containers:
 | 
			
		||||
     - name: wget
 | 
			
		||||
+      securityContext:
 | 
			
		||||
+        {{- with .Values.containerSecurityContext }}
 | 
			
		||||
+        {{- . | toYaml | nindent 12 }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
+        readOnlyRootFilesystem: true
 | 
			
		||||
+        runAsUser: 100
 | 
			
		||||
+        {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
+        {{- with .Values.seccompProfile }}
 | 
			
		||||
+        seccompProfile:
 | 
			
		||||
+          {{- . | toYaml | nindent 14 }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
+        {{- end }}
 | 
			
		||||
       image: alpine:3.18
 | 
			
		||||
       command:
 | 
			
		||||
       - sh
 | 
			
		||||
diff --git a/values.yaml b/values.yaml
 | 
			
		||||
index ac7f0f0..9730e9c 100644
 | 
			
		||||
--- a/values.yaml
 | 
			
		||||
+++ b/values.yaml
 | 
			
		||||
@@ -3,10 +3,10 @@
 | 
			
		||||
 # Declare variables to be passed into your templates.
 | 
			
		||||
 replicaCount: 1
 | 
			
		||||
 image:
 | 
			
		||||
-  repository: ghcr.io/project-zot/zot-linux-amd64
 | 
			
		||||
-  pullPolicy: IfNotPresent
 | 
			
		||||
-  # Overrides the image tag whose default is the chart appVersion.
 | 
			
		||||
-  tag: "v2.0.0"
 | 
			
		||||
+  registry: gsoci.azurecr.io
 | 
			
		||||
+  image: dummy/zot-linux
 | 
			
		||||
+  pullPolicy: Always
 | 
			
		||||
+  tag: ""
 | 
			
		||||
 serviceAccount:
 | 
			
		||||
   # Specifies whether a service account should be created
 | 
			
		||||
   create: true
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
---
 | 
			
		||||
name: Replace image repository in values
 | 
			
		||||
targets:
 | 
			
		||||
  - values.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
  image:
 | 
			
		||||
    repository: ghcr.io/project-zot/zot-linux-amd64
 | 
			
		||||
    pullPolicy: IfNotPresent
 | 
			
		||||
    # Overrides the image tag whose default is the chart appVersion.
 | 
			
		||||
    tag: "v2.0.0"
 | 
			
		||||
after: |-
 | 
			
		||||
  image:
 | 
			
		||||
    repository: gsoci/dummy/zot-linux
 | 
			
		||||
    pullPolicy: Always
 | 
			
		||||
    tag: ""
 | 
			
		||||
---
 | 
			
		||||
name: Fix security policies in the deployment
 | 
			
		||||
targets:
 | 
			
		||||
  - templates/deployment.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
          \{\{- toYaml .Values.podSecurityContext .*
 | 
			
		||||
after: |-
 | 
			
		||||
            fsGroup: 1337
 | 
			
		||||
                    {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
                    {{- with .Values.podSeccompProfile }}
 | 
			
		||||
                    seccompProfile:
 | 
			
		||||
                      {{- . | toYaml | nindent 10 }}
 | 
			
		||||
                    {{- end }}
 | 
			
		||||
                    {{- end }}
 | 
			
		||||
---
 | 
			
		||||
name: Fix security policies in container
 | 
			
		||||
targets:
 | 
			
		||||
  - templates/deployment.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
  \{\{- toYaml .Values.securityContext .*
 | 
			
		||||
after: |-
 | 
			
		||||
            {{- with .Values.containerSecurityContext }}
 | 
			
		||||
                        {{- . | toYaml | nindent 12 }}
 | 
			
		||||
                        {{- end }}
 | 
			
		||||
                        readOnlyRootFilesystem: true
 | 
			
		||||
                        runAsUser: 100
 | 
			
		||||
                        {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
                        {{- with .Values.seccompProfile }}
 | 
			
		||||
                        seccompProfile:
 | 
			
		||||
                          {{- . | toYaml | nindent 14 }}
 | 
			
		||||
                        {{- end }}
 | 
			
		||||
                        {{- end }}
 | 
			
		||||
---
 | 
			
		||||
name: Fix security policies in test jobs
 | 
			
		||||
targets:
 | 
			
		||||
  - templates/tests/test-connection-fails.yaml
 | 
			
		||||
  - templates/tests/test-connection.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
  spec:
 | 
			
		||||
    containers:
 | 
			
		||||
after: |-
 | 
			
		||||
  spec:
 | 
			
		||||
    securityContext:
 | 
			
		||||
      fsGroup: 1337
 | 
			
		||||
      {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
      {{- with .Values.podSeccompProfile }}
 | 
			
		||||
      seccompProfile:
 | 
			
		||||
        {{- . | toYaml | nindent 10 }}
 | 
			
		||||
      {{- end }}
 | 
			
		||||
      {{- end }}
 | 
			
		||||
    containers:
 | 
			
		||||
---
 | 
			
		||||
name: Fix security policies in test jobs containers
 | 
			
		||||
targets:
 | 
			
		||||
  - templates/tests/test-connection-fails.yaml
 | 
			
		||||
  - templates/tests/test-connection.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
    containers:
 | 
			
		||||
        - name: wget
 | 
			
		||||
after: |-
 | 
			
		||||
    containers:
 | 
			
		||||
        - name: wget
 | 
			
		||||
          securityContext:
 | 
			
		||||
            {{- with .Values.containerSecurityContext }}
 | 
			
		||||
            {{- . | toYaml | nindent 12 }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
            readOnlyRootFilesystem: true
 | 
			
		||||
            runAsUser: 100
 | 
			
		||||
            {{- if ge (int .Capabilities.KubeVersion.Minor) 19 }}
 | 
			
		||||
            {{- with .Values.seccompProfile }}
 | 
			
		||||
            seccompProfile:
 | 
			
		||||
              {{- . | toYaml | nindent 14 }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
							
								
								
									
										51
									
								
								helmule/examples/patches/flux-regexp/patch.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								helmule/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
									
								
								helmule/examples/patches/git/patch-2.diff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								helmule/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
									
								
								helmule/examples/patches/git/patch.diff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								helmule/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
									
								
								helmule/examples/patches/regexp/patch.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								helmule/examples/patches/regexp/patch.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
---
 | 
			
		||||
name: Add spaces before comments
 | 
			
		||||
targets:
 | 
			
		||||
  - values.yaml
 | 
			
		||||
before: |-
 | 
			
		||||
  ^.*[\S]+.*#
 | 
			
		||||
after: " #"
 | 
			
		||||
							
								
								
									
										27
									
								
								helmule/examples/use/charts/vaultwarden.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								helmule/examples/use/charts/vaultwarden.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
name: vaultwarden
 | 
			
		||||
repository: badhouseplants
 | 
			
		||||
version: latest
 | 
			
		||||
extensions:
 | 
			
		||||
  - name: Add virtual service to the chartc
 | 
			
		||||
    target_dir: templates/extensions
 | 
			
		||||
    source_dir: ../../extensions/vaultwarden
 | 
			
		||||
patches:
 | 
			
		||||
  - name: Git patch 1
 | 
			
		||||
    git:
 | 
			
		||||
      path: ../../patches/git/patch.diff
 | 
			
		||||
  - name: Git patch 2
 | 
			
		||||
    git:
 | 
			
		||||
      path: ../../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
 | 
			
		||||
							
								
								
									
										54
									
								
								helmule/examples/use/charts/vaultwardens.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								helmule/examples/use/charts/vaultwardens.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
- 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: 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
 | 
			
		||||
							
								
								
									
										1
									
								
								helmule/examples/yamlfmt.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								helmule/examples/yamlfmt.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pad_line_comments: 2
 | 
			
		||||
							
								
								
									
										308
									
								
								helmule/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								helmule/src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,308 @@
 | 
			
		||||
use std::{collections::HashMap, error::Error};
 | 
			
		||||
 | 
			
		||||
use helmzoo_lib::{
 | 
			
		||||
    cli::is_path_relative,
 | 
			
		||||
    config::ConfigImpl,
 | 
			
		||||
    helm::{chart::Chart, repository::Repository},
 | 
			
		||||
    include::Include,
 | 
			
		||||
    output::message_info,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{extensions::Extension, mirror::Mirror, patches::Patch};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, PartialOrd, Ord, Eq)]
 | 
			
		||||
pub(crate) enum SupportedIncludes {
 | 
			
		||||
    Repositories,
 | 
			
		||||
    Mirrors,
 | 
			
		||||
    Charts,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
 | 
			
		||||
pub(crate) struct ChartExtended {
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    pub(crate) chart: Chart,
 | 
			
		||||
    pub(crate) extensions: Option<Vec<Extension>>,
 | 
			
		||||
    pub(crate) patches: Option<Vec<Patch>>,
 | 
			
		||||
    pub variables: Option<HashMap<String, String>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ChartExtended {
 | 
			
		||||
    pub(crate) fn populate_variables(&mut self, global_variables: Option<HashMap<String, String>>) {
 | 
			
		||||
        if let Some(global_vars) = global_variables {
 | 
			
		||||
            self.variables = match self.variables.clone() {
 | 
			
		||||
                Some(mut vars) => {
 | 
			
		||||
                    vars.extend(global_vars);
 | 
			
		||||
                    Some(vars)
 | 
			
		||||
                }
 | 
			
		||||
                None => Some(global_vars),
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
 | 
			
		||||
pub(crate) struct Config {
 | 
			
		||||
    pub(crate) variables: Option<HashMap<String, String>>,
 | 
			
		||||
    #[serde(default = "empty_vec")]
 | 
			
		||||
    pub(crate) repositories: Vec<Repository>,
 | 
			
		||||
    pub(crate) include: Option<Vec<Include<SupportedIncludes>>>,
 | 
			
		||||
    #[serde(default = "empty_vec")]
 | 
			
		||||
    pub(crate) charts: Vec<ChartExtended>,
 | 
			
		||||
    pub(crate) patches: Option<Vec<Patch>>,
 | 
			
		||||
    #[serde(default = "empty_vec")]
 | 
			
		||||
    pub(crate) mirrors: Vec<Mirror>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn empty_vec<T>() -> Vec<T> {
 | 
			
		||||
    vec![]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ConfigImpl for Config {
 | 
			
		||||
    fn apply_includes(&mut self, config_path: String) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        if let Some(mut include) = self.include.clone() {
 | 
			
		||||
            include.sort_by_key(|f| f.kind.clone());
 | 
			
		||||
            include
 | 
			
		||||
                .iter()
 | 
			
		||||
                .try_for_each(|i| -> Result<(), Box<dyn Error>> {
 | 
			
		||||
                    let include_path = match is_path_relative(i.path.clone()) {
 | 
			
		||||
                        true => format!("{}/{}", config_path, i.path),
 | 
			
		||||
                        false => i.path.clone(),
 | 
			
		||||
                    };
 | 
			
		||||
                    match i.kind {
 | 
			
		||||
                        SupportedIncludes::Charts => {
 | 
			
		||||
                            Ok(self.append_to_charts(include_charts(include_path)?))
 | 
			
		||||
                        }
 | 
			
		||||
                        SupportedIncludes::Repositories => {
 | 
			
		||||
                            Ok(self.append_to_repositories(include_repositories(include_path)?))
 | 
			
		||||
                        }
 | 
			
		||||
                        SupportedIncludes::Mirrors => todo!(),
 | 
			
		||||
                    }
 | 
			
		||||
                })?
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Config {
 | 
			
		||||
    // TODO: Maybe it can be a generic function
 | 
			
		||||
    fn append_to_repositories(&mut self, mut repositories: Vec<Repository>) {
 | 
			
		||||
        self.repositories.append(&mut repositories);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn append_to_charts(&mut self, mut charts: Vec<ChartExtended>) {
 | 
			
		||||
        self.charts.append(&mut charts);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn include_repositories(path: String) -> Result<Vec<Repository>, Box<dyn Error>> {
 | 
			
		||||
    message_info(&format!(
 | 
			
		||||
        "trying to include repositories from {}",
 | 
			
		||||
        path.clone()
 | 
			
		||||
    ));
 | 
			
		||||
    let file = std::fs::File::open(path.clone())?;
 | 
			
		||||
    let repositories: Vec<Repository> = match serde_yaml::from_reader(file) {
 | 
			
		||||
        Ok(res) => res,
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            let file = std::fs::File::open(path.clone())?;
 | 
			
		||||
            let repo: Repository = serde_yaml::from_reader(file)?;
 | 
			
		||||
            vec![repo]
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    Ok(repositories)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn include_charts(path: String) -> Result<Vec<ChartExtended>, Box<dyn Error>> {
 | 
			
		||||
    message_info(&format!("trying to include chart from {}", path.clone()));
 | 
			
		||||
    let file = std::fs::File::open(path.clone())?;
 | 
			
		||||
 | 
			
		||||
    let chart_dir = match std::path::Path::new(&path).parent() {
 | 
			
		||||
        Some(dir) => match dir.to_str() {
 | 
			
		||||
            Some(dir) => dir.to_string(),
 | 
			
		||||
            None => {
 | 
			
		||||
                return Err(Box::from(format!(
 | 
			
		||||
                    "chart parrent dir not found for {}",
 | 
			
		||||
                    path
 | 
			
		||||
                )));
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        None => {
 | 
			
		||||
            return Err(Box::from(format!(
 | 
			
		||||
                "chart parrent dir not found for {}",
 | 
			
		||||
                path
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let mut charts: Vec<ChartExtended> = match serde_yaml::from_reader(file) {
 | 
			
		||||
        Ok(res) => res,
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            let file = std::fs::File::open(path.clone())?;
 | 
			
		||||
            let chart: ChartExtended = serde_yaml::from_reader(file)?;
 | 
			
		||||
            vec![chart]
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    charts.iter_mut().for_each(|chart| {
 | 
			
		||||
        match chart.extensions {
 | 
			
		||||
            Some(ref mut extensions) => extensions.iter_mut().for_each(|extension| {
 | 
			
		||||
                if is_path_relative(extension.source_dir.clone()) {
 | 
			
		||||
                    let clean_path = match extension.source_dir.clone().starts_with("./") {
 | 
			
		||||
                        true => extension.source_dir.clone().replacen("./", "", 1),
 | 
			
		||||
                        false => extension.source_dir.clone(),
 | 
			
		||||
                    };
 | 
			
		||||
                    if is_path_relative(clean_path.clone()) {
 | 
			
		||||
                        let new_path = format!("{}/{}", chart_dir, clean_path);
 | 
			
		||||
                        extension.source_dir = new_path;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            None => message_info(&format!("no extensions set, nothing to update")),
 | 
			
		||||
        };
 | 
			
		||||
        match chart.patches {
 | 
			
		||||
            Some(ref mut patches) => patches.iter_mut().for_each(|patch| {
 | 
			
		||||
                if is_path_relative(patch.get_path().clone()) {
 | 
			
		||||
                    let clean_path = match patch.get_path().clone().starts_with("./") {
 | 
			
		||||
                        true => patch.get_path().clone().replacen("./", "", 1),
 | 
			
		||||
                        false => patch.get_path().clone(),
 | 
			
		||||
                    };
 | 
			
		||||
                    if is_path_relative(clean_path.clone()) {
 | 
			
		||||
                        let new_path = format!("{}/{}", chart_dir, clean_path);
 | 
			
		||||
                        patch.set_path(new_path);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            None => message_info(&format!("no patch set, nothing to update")),
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    Ok(charts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::{error::Error, fs::File, io::Write};
 | 
			
		||||
 | 
			
		||||
    use helmzoo_lib::{
 | 
			
		||||
        config::ConfigImpl,
 | 
			
		||||
        helm::{helm_repository::HelmRepo, repository::Repository},
 | 
			
		||||
        include::Include,
 | 
			
		||||
    };
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    use crate::config::SupportedIncludes;
 | 
			
		||||
 | 
			
		||||
    use super::{include_repositories, Config};
 | 
			
		||||
 | 
			
		||||
    fn prepare_test_file(name: &str, data: &str) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let dir = tempdir()?;
 | 
			
		||||
        let file_path = dir.into_path().join(&name);
 | 
			
		||||
        let mut file = File::create(file_path.clone())?;
 | 
			
		||||
        file.write_all(data.as_bytes())?;
 | 
			
		||||
        let path = file_path.into_os_string().to_str().unwrap().to_string();
 | 
			
		||||
        Ok(path)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_cfg_apply_includes_repo() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let data = "---
 | 
			
		||||
- name: test
 | 
			
		||||
  helm:
 | 
			
		||||
    url: test.rocks
 | 
			
		||||
            ";
 | 
			
		||||
        let path = prepare_test_file("repositories.yaml", data)?;
 | 
			
		||||
        let repo_1: Repository = Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            helm: None,
 | 
			
		||||
            git: None,
 | 
			
		||||
        };
 | 
			
		||||
        let repos = vec![repo_1.clone()];
 | 
			
		||||
        let includes: Vec<Include<SupportedIncludes>> = vec![Include {
 | 
			
		||||
            path: path.clone(),
 | 
			
		||||
            kind: SupportedIncludes::Repositories,
 | 
			
		||||
        }];
 | 
			
		||||
        let mut config = Config {
 | 
			
		||||
            repositories: repos.clone(),
 | 
			
		||||
            include: Some(includes),
 | 
			
		||||
            charts: vec![],
 | 
			
		||||
            variables: None,
 | 
			
		||||
            patches: None,
 | 
			
		||||
            mirrors: vec![],
 | 
			
		||||
        };
 | 
			
		||||
        let repo_2 = Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            helm: Some(HelmRepo {
 | 
			
		||||
                url: "test.rocks".to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
            git: None,
 | 
			
		||||
        };
 | 
			
		||||
        config.apply_includes(path)?;
 | 
			
		||||
        assert_eq!(config.repositories, vec!(repo_1, repo_2));
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_cfg_append_to_repos() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let repo_1: Repository = Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            helm: None,
 | 
			
		||||
            git: None,
 | 
			
		||||
        };
 | 
			
		||||
        let repos = vec![repo_1.clone()];
 | 
			
		||||
        let mut config = Config {
 | 
			
		||||
            variables: None,
 | 
			
		||||
            repositories: repos.clone(),
 | 
			
		||||
            include: None,
 | 
			
		||||
            mirrors: vec![],
 | 
			
		||||
            charts: vec![],
 | 
			
		||||
            patches: None,
 | 
			
		||||
        };
 | 
			
		||||
        let repo_2 = Repository {
 | 
			
		||||
            name: "test_2".to_string(),
 | 
			
		||||
            helm: None,
 | 
			
		||||
            git: None,
 | 
			
		||||
        };
 | 
			
		||||
        let repo_2_vec = vec![repo_2.clone()];
 | 
			
		||||
        config.append_to_repositories(repo_2_vec);
 | 
			
		||||
        assert_eq!(config.repositories, vec!(repo_1, repo_2));
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_include_repositories() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let data = "---
 | 
			
		||||
- name: test
 | 
			
		||||
  helm:
 | 
			
		||||
    url: test.rocks
 | 
			
		||||
            ";
 | 
			
		||||
        let path = prepare_test_file("repositories.yaml", data)?;
 | 
			
		||||
        let include = include_repositories(path)?;
 | 
			
		||||
        let expected: Vec<Repository> = vec![Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            git: None,
 | 
			
		||||
            helm: Some(HelmRepo {
 | 
			
		||||
                url: "test.rocks".to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
        }];
 | 
			
		||||
        assert_eq!(expected, include);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_include_repository() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let data = "---
 | 
			
		||||
name: test
 | 
			
		||||
helm:
 | 
			
		||||
  url: test.rocks
 | 
			
		||||
            ";
 | 
			
		||||
        let path = prepare_test_file("repositories.yaml", data)?;
 | 
			
		||||
        let include = include_repositories(path)?;
 | 
			
		||||
        let expected: Vec<Repository> = vec![Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            git: None,
 | 
			
		||||
            helm: Some(HelmRepo {
 | 
			
		||||
                url: "test.rocks".to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
        }];
 | 
			
		||||
        assert_eq!(expected, include);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								helmule/src/extensions.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								helmule/src/extensions.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
use std::fs::create_dir;
 | 
			
		||||
 | 
			
		||||
use helmzoo_lib::{
 | 
			
		||||
    cli::{copy_recursively, is_path_relative},
 | 
			
		||||
    output::message_info,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) struct Extension {
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    target_dir: String,
 | 
			
		||||
    pub(crate) source_dir: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Extension {
 | 
			
		||||
    pub(crate) fn apply(
 | 
			
		||||
        &self,
 | 
			
		||||
        chart_path: String,
 | 
			
		||||
        config_path: String,
 | 
			
		||||
    ) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        let extension_path = match is_path_relative(self.source_dir.clone()) {
 | 
			
		||||
            true => format!("{}/{}", config_path, self.source_dir),
 | 
			
		||||
            false => self.source_dir.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let extension_name = match self.name.clone() {
 | 
			
		||||
            Some(res) => res,
 | 
			
		||||
            None => "Unnamed".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        message_info(&format!("applying extension: '{}'", extension_name));
 | 
			
		||||
        let target_dir = format!("{}/{}", chart_path, self.target_dir);
 | 
			
		||||
        message_info(&format!("trying to create a dir: {}", target_dir));
 | 
			
		||||
        create_dir(target_dir.clone())?;
 | 
			
		||||
        message_info(&format!("copying {} to {}", extension_path, target_dir));
 | 
			
		||||
        copy_recursively(extension_path, target_dir)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								helmule/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								helmule/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use config::Config;
 | 
			
		||||
use helmzoo_lib::{
 | 
			
		||||
    self,
 | 
			
		||||
    cli::{check_prerequisites, get_full_path_dir},
 | 
			
		||||
    config::{read_config, ConfigImpl},
 | 
			
		||||
    helm::repository::RepositoryImpl,
 | 
			
		||||
    output::{message_empty, message_error},
 | 
			
		||||
};
 | 
			
		||||
use std::{error::Error, process::exit};
 | 
			
		||||
 | 
			
		||||
use crate::mirror::mirror_from_mirror_obj;
 | 
			
		||||
 | 
			
		||||
mod config;
 | 
			
		||||
mod extensions;
 | 
			
		||||
mod mirror;
 | 
			
		||||
mod patches;
 | 
			
		||||
 | 
			
		||||
/// 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,
 | 
			
		||||
    #[arg(long, default_value = "false")]
 | 
			
		||||
    skip_prerequisites_check: 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>>,
 | 
			
		||||
    #[arg(long, default_value = "helm")]
 | 
			
		||||
    helm_bin: String,
 | 
			
		||||
    #[arg(long, default_value = "git")]
 | 
			
		||||
    git_bin: String,
 | 
			
		||||
    #[arg(long, default_value = "yq")]
 | 
			
		||||
    yq_bin: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn exec(args: Args) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    let prerequisites = vec![args.helm_bin, args.git_bin, args.yq_bin];
 | 
			
		||||
    check_prerequisites(prerequisites)?;
 | 
			
		||||
    let workdir_path = helmzoo_lib::workdir::setup_workdir(args.workdir)?;
 | 
			
		||||
    let mut config: Config = read_config(args.config.clone())?;
 | 
			
		||||
    let config_full_path = get_full_path_dir(args.config.clone())?;
 | 
			
		||||
    config.apply_includes(config_full_path.clone())?;
 | 
			
		||||
    config
 | 
			
		||||
        .charts
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .try_for_each(|mut chart| -> Result<(), Box<dyn Error>> {
 | 
			
		||||
            chart.populate_variables(config.variables.clone());
 | 
			
		||||
            // First step is to pull the chart to the working dir
 | 
			
		||||
            let current_repo = chart.chart.find_repo(config.repositories.clone())?;
 | 
			
		||||
            let chart_path = current_repo.pull_chart(chart.chart.clone(), workdir_path.clone())?;
 | 
			
		||||
            if let Some(extensions) = chart.extensions.clone() {
 | 
			
		||||
                extensions
 | 
			
		||||
                    .into_iter()
 | 
			
		||||
                    .try_for_each(|extension| -> Result<(), Box<dyn Error>> {
 | 
			
		||||
                        extension.apply(chart_path.clone(), config_full_path.clone())
 | 
			
		||||
                    })?
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(patches) = chart.patches.clone() {
 | 
			
		||||
                patches
 | 
			
		||||
                    .into_iter()
 | 
			
		||||
                    .try_for_each(|patch| -> Result<(), Box<dyn Error>> {
 | 
			
		||||
                        patch.apply(chart_path.clone(), config.patches.clone())
 | 
			
		||||
                    })?
 | 
			
		||||
            }
 | 
			
		||||
            config
 | 
			
		||||
                .mirrors
 | 
			
		||||
                .iter()
 | 
			
		||||
                .try_for_each(|mirror| -> Result<(), Box<dyn Error>> {
 | 
			
		||||
                    mirror_from_mirror_obj(mirror.clone())?.push(
 | 
			
		||||
                        workdir_path.clone(),
 | 
			
		||||
                        chart_path.clone(),
 | 
			
		||||
                        chart.clone(),
 | 
			
		||||
                        args.dry_run,
 | 
			
		||||
                    )
 | 
			
		||||
                })?;
 | 
			
		||||
            message_empty(&format!("{}", chart.chart.name));
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })?;
 | 
			
		||||
    todo!()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    match exec(Args::parse()) {
 | 
			
		||||
        Ok(()) => message_empty("Thanks for using helmule"),
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            message_error(err);
 | 
			
		||||
            exit(1)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								helmule/src/mirror/custom_command.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								helmule/src/mirror/custom_command.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
use helmzoo_lib::{cli::cli_exec_from_dir, template};
 | 
			
		||||
 | 
			
		||||
use crate::config::ChartExtended;
 | 
			
		||||
 | 
			
		||||
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_path: String,
 | 
			
		||||
        chart_local: ChartExtended,
 | 
			
		||||
        dry_run: bool,
 | 
			
		||||
    ) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        for cmd_tmpl in self.package.clone() {
 | 
			
		||||
            let mut reg = helmzoo_lib::template::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 = template::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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::CustomCommands;
 | 
			
		||||
    use crate::{config::ChartExtended, mirror::Target};
 | 
			
		||||
    use std::{collections::HashMap, fs::create_dir_all, path::Path};
 | 
			
		||||
    use tempfile::TempDir;
 | 
			
		||||
 | 
			
		||||
    fn get_chart_local() -> ChartExtended {
 | 
			
		||||
        let mut vars: HashMap<String, String> = HashMap::new();
 | 
			
		||||
        vars.insert("key".to_string(), "value".to_string());
 | 
			
		||||
        ChartExtended {
 | 
			
		||||
            name: "chart".to_string(),
 | 
			
		||||
            version: "1.0.0".to_string(),
 | 
			
		||||
            path: "chart-1.0.0".to_string(),
 | 
			
		||||
            repo_url: "https:://helm.repo".to_string(),
 | 
			
		||||
            vars,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn prepare_test_workdir(chart_path: String) -> String {
 | 
			
		||||
        let workdir = TempDir::new().unwrap().path().to_str().unwrap().to_string();
 | 
			
		||||
        println!("test workdir is {}", workdir.clone());
 | 
			
		||||
        create_dir_all(format!("{}/{}", workdir, chart_path)).unwrap();
 | 
			
		||||
        workdir
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_package_basic() {
 | 
			
		||||
        let chart_local = get_chart_local();
 | 
			
		||||
        let workdir = prepare_test_workdir(chart_local.path.clone());
 | 
			
		||||
 | 
			
		||||
        let custom_commands = CustomCommands {
 | 
			
		||||
            package: vec!["touch package".to_string()],
 | 
			
		||||
            upload: vec!["touch upload".to_string()],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let cc_target: Box<dyn Target> = Box::from(custom_commands);
 | 
			
		||||
        cc_target.push(workdir.clone(), chart_local, true).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert!(Path::new(&format!("{}/package", workdir)).exists());
 | 
			
		||||
        assert!(!Path::new(&format!("{}/upload", workdir)).exists());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_upload_basic() {
 | 
			
		||||
        let chart_local = get_chart_local();
 | 
			
		||||
        let workdir = prepare_test_workdir(chart_local.path.clone());
 | 
			
		||||
 | 
			
		||||
        let custom_commands = CustomCommands {
 | 
			
		||||
            package: vec!["touch package".to_string()],
 | 
			
		||||
            upload: vec!["touch upload".to_string()],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let cc_target: Box<dyn Target> = Box::from(custom_commands);
 | 
			
		||||
        cc_target.push(workdir.clone(), chart_local, false).unwrap();
 | 
			
		||||
 | 
			
		||||
        assert!(Path::new(&format!("{}/package", workdir)).exists());
 | 
			
		||||
        assert!(Path::new(&format!("{}/upload", workdir)).exists());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_templates() {
 | 
			
		||||
        let chart_local = get_chart_local();
 | 
			
		||||
        let workdir = prepare_test_workdir(chart_local.path.clone());
 | 
			
		||||
 | 
			
		||||
        let custom_commands = CustomCommands {
 | 
			
		||||
            package: vec!["touch {{ name }}-{{ version }}".to_string()],
 | 
			
		||||
            upload: vec!["touch {{ repo_url }}-{{ vars.key }}".to_string()],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let cc_target: Box<dyn Target> = Box::from(custom_commands);
 | 
			
		||||
        cc_target
 | 
			
		||||
            .push(workdir.clone(), chart_local.clone(), true)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        assert!(Path::new(&format!(
 | 
			
		||||
            "{}/{}-{}",
 | 
			
		||||
            workdir, chart_local.name, chart_local.version
 | 
			
		||||
        ))
 | 
			
		||||
        .exists());
 | 
			
		||||
        assert!(!Path::new(&format!(
 | 
			
		||||
            "{}/{}-{}",
 | 
			
		||||
            workdir,
 | 
			
		||||
            chart_local.repo_url,
 | 
			
		||||
            chart_local.vars.get("key").unwrap()
 | 
			
		||||
        ))
 | 
			
		||||
        .exists());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								helmule/src/mirror/git.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								helmule/src/mirror/git.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
use base64::{engine::general_purpose, Engine};
 | 
			
		||||
use dircpy::*;
 | 
			
		||||
use helmzoo_lib::git::{CheckoutOptions, CommitOptions, Git, GitOptions, PushOptions};
 | 
			
		||||
use helmzoo_lib::template;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::config::ChartExtended;
 | 
			
		||||
 | 
			
		||||
use super::Target;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) struct GitMirror {
 | 
			
		||||
    pub(crate) git_dir: Option<String>,
 | 
			
		||||
    pub(crate) url: String,
 | 
			
		||||
    pub(crate) path: String,
 | 
			
		||||
    #[serde(default = "default_branch")]
 | 
			
		||||
    pub(crate) branch: String,
 | 
			
		||||
    #[serde(default = "default_commit")]
 | 
			
		||||
    pub(crate) commit: String,
 | 
			
		||||
    pub(crate) rebase_to: Option<String>,
 | 
			
		||||
    #[serde(default = "default_git_bin")]
 | 
			
		||||
    pub(crate) git_bin: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_commit() -> String {
 | 
			
		||||
    "helmuled {{ name }}-{{ version }}".to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_branch() -> String {
 | 
			
		||||
    "main".to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_git_bin() -> String {
 | 
			
		||||
    "git".to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Target for GitMirror {
 | 
			
		||||
    fn push(
 | 
			
		||||
        &self,
 | 
			
		||||
        workdir_path: String,
 | 
			
		||||
        chart_path: String,
 | 
			
		||||
        chart_local: ChartExtended,
 | 
			
		||||
        dry_run: bool,
 | 
			
		||||
    ) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        let repo_path = match self.git_dir.clone() {
 | 
			
		||||
            Some(dir) => template::render(dir.clone(), &chart_local)?,
 | 
			
		||||
            None => general_purpose::STANDARD_NO_PAD.encode(self.url.clone()),
 | 
			
		||||
        };
 | 
			
		||||
        let git_instance = Git {
 | 
			
		||||
            url: template::render(self.url.clone(), &chart_local)?,
 | 
			
		||||
            repo_path: repo_path.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let git_opts = GitOptions::new(self.git_bin.clone(), Some(workdir_path.clone()));
 | 
			
		||||
        git_instance.clone(git_opts.clone())?;
 | 
			
		||||
 | 
			
		||||
        // Prepare branch
 | 
			
		||||
        let checkout_opts = CheckoutOptions {
 | 
			
		||||
            create: true,
 | 
			
		||||
            git_ref: template::render(self.branch.clone(), &chart_local)?,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        git_instance.checkout(git_opts.clone(), checkout_opts)?;
 | 
			
		||||
 | 
			
		||||
        // Prepare path
 | 
			
		||||
        let path = template::render(self.path.clone(), &chart_local)?;
 | 
			
		||||
        let repo_local_full_path = format!("{}/{}/{}", workdir_path.clone(), repo_path, path);
 | 
			
		||||
        CopyBuilder::new(chart_path.clone(), repo_local_full_path.clone())
 | 
			
		||||
            .overwrite_if_size_differs(true)
 | 
			
		||||
            .run()?;
 | 
			
		||||
 | 
			
		||||
        // Prepare the commit message
 | 
			
		||||
        let commit_opts = CommitOptions {
 | 
			
		||||
            message: template::render(self.commit.clone(), &chart_local)?,
 | 
			
		||||
            add: true,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        git_instance.commit(git_opts.clone(), commit_opts)?;
 | 
			
		||||
 | 
			
		||||
        if !dry_run {
 | 
			
		||||
            let force_push = match self.rebase_to {
 | 
			
		||||
                Some(_) => true,
 | 
			
		||||
                None => false,
 | 
			
		||||
            };
 | 
			
		||||
            let push_opts = PushOptions {
 | 
			
		||||
                rebase_to: self.rebase_to.clone(),
 | 
			
		||||
                force: force_push,
 | 
			
		||||
                brahcn: self.branch.clone(),
 | 
			
		||||
            };
 | 
			
		||||
            git_instance.push(git_opts.clone(), push_opts)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								helmule/src/mirror/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								helmule/src/mirror/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::config::ChartExtended;
 | 
			
		||||
 | 
			
		||||
pub(crate) mod custom_command;
 | 
			
		||||
pub(crate) mod git;
 | 
			
		||||
 | 
			
		||||
pub(crate) trait Target {
 | 
			
		||||
    fn push(
 | 
			
		||||
        &self,
 | 
			
		||||
        workdir_path: String,
 | 
			
		||||
        chart_path: String,
 | 
			
		||||
        chart_local: ChartExtended,
 | 
			
		||||
        dry_run: bool,
 | 
			
		||||
    ) -> Result<(), Box<dyn std::error::Error>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) struct Mirror {
 | 
			
		||||
    pub(crate) name: String,
 | 
			
		||||
    pub(crate) git: Option<git::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>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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));
 | 
			
		||||
    } 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
 | 
			
		||||
    )))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										313
									
								
								helmule/src/patches.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								helmule/src/patches.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,313 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    fs::{self, read_dir, remove_dir_all, File, OpenOptions},
 | 
			
		||||
    io::Write,
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use helmzoo_lib::{
 | 
			
		||||
    cli::{cli_exec, cli_exec_from_dir},
 | 
			
		||||
    output::message_info,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) struct RegexpPatchObj {
 | 
			
		||||
    pub(crate) name: String,
 | 
			
		||||
    pub(crate) targets: Vec<String>,
 | 
			
		||||
    pub(crate) before: Option<String>,
 | 
			
		||||
    pub(crate) after: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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 {
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    regexp: Option<RegexpPatch>,
 | 
			
		||||
    git: Option<GitPatch>,
 | 
			
		||||
    custom_command: Option<CustomCommandPatch>,
 | 
			
		||||
    yq: Option<YqPatch>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Patch {
 | 
			
		||||
    pub(crate) fn apply(
 | 
			
		||||
        &self,
 | 
			
		||||
        chart_local_path: String,
 | 
			
		||||
        global_patches: Option<Vec<Patch>>,
 | 
			
		||||
    ) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        let patch_action: Box<dyn PatchInterface>;
 | 
			
		||||
        if self.is_ref() {
 | 
			
		||||
            let patch_ref = self.get_ref(global_patches)?;
 | 
			
		||||
            patch_action = Box::from(patch_action_from_definition(patch_ref)?);
 | 
			
		||||
        } else {
 | 
			
		||||
            patch_action = Box::from(patch_action_from_definition(self.clone())?);
 | 
			
		||||
        }
 | 
			
		||||
        patch_action.apply(chart_local_path)
 | 
			
		||||
    }
 | 
			
		||||
    pub(crate) fn get_path(&self) -> String {
 | 
			
		||||
        match patch_action_from_definition(self.clone()) {
 | 
			
		||||
            Ok(patch) => patch.get_path(),
 | 
			
		||||
            Err(_) => "".to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub(crate) fn set_path(&mut self, path: String) {
 | 
			
		||||
        if let Some(ref mut regexp) = self.regexp {
 | 
			
		||||
            regexp.path = path;
 | 
			
		||||
        } else if let Some(ref mut git) = self.git {
 | 
			
		||||
            git.path = path;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_ref(&self) -> bool {
 | 
			
		||||
        self.regexp.is_none()
 | 
			
		||||
            && self.git.is_none()
 | 
			
		||||
            && self.custom_command.is_none()
 | 
			
		||||
            && self.yq.is_none()
 | 
			
		||||
            && self.name.is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn get_ref(
 | 
			
		||||
        &self,
 | 
			
		||||
        global_patches: Option<Vec<Patch>>,
 | 
			
		||||
    ) -> Result<Patch, Box<dyn std::error::Error>> {
 | 
			
		||||
        match global_patches {
 | 
			
		||||
            Some(patches) => {
 | 
			
		||||
                let patch = patches
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .find(|&patch| patch.clone().name.unwrap() == self.clone().name.unwrap());
 | 
			
		||||
                match patch {
 | 
			
		||||
                    Some(patch) => {
 | 
			
		||||
                        return Ok(patch.clone());
 | 
			
		||||
                    }
 | 
			
		||||
                    None => {
 | 
			
		||||
                        return Err(Box::from(format!(
 | 
			
		||||
                            "global patch is not found: {}",
 | 
			
		||||
                            self.clone().name.unwrap()
 | 
			
		||||
                        )))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => {
 | 
			
		||||
                return Err(Box::from(format!(
 | 
			
		||||
                    "patch {} is recognized as a reference, but global patches are not defined",
 | 
			
		||||
                    self.clone().name.unwrap()
 | 
			
		||||
                )))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
trait PatchInterface {
 | 
			
		||||
    fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>>;
 | 
			
		||||
    fn get_path(&self) -> String;
 | 
			
		||||
    fn set_path(&mut self, new_path: String);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PatchInterface for YqPatch {
 | 
			
		||||
    fn apply(&self, chart_local_path: String) -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        let cmd = match self.op {
 | 
			
		||||
            YqOperations::Add => {
 | 
			
		||||
                let value = match self
 | 
			
		||||
                    .value
 | 
			
		||||
                    .clone()
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .starts_with(['{', '[', '\"', '\''])
 | 
			
		||||
                {
 | 
			
		||||
                    true => self.value.clone().unwrap(),
 | 
			
		||||
                    false => format!("\"{}\"", self.value.clone().unwrap()),
 | 
			
		||||
                };
 | 
			
		||||
                format!("yq -i '{} += {}' {}", self.key, value, self.file)
 | 
			
		||||
            }
 | 
			
		||||
            YqOperations::Delete => format!("yq -i \'del({})\' {}", self.key, self.file),
 | 
			
		||||
            YqOperations::Replace => {
 | 
			
		||||
                let value = match self.value.clone().unwrap().starts_with(['{', '[']) {
 | 
			
		||||
                    true => self.value.clone().unwrap(),
 | 
			
		||||
                    false => format!("\"{}\"", self.value.clone().unwrap()),
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                format!("yq e -i '{} = {}' {}", self.key, value, self.file)
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        cli_exec_from_dir(cmd, chart_local_path)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_path(&self) -> String {
 | 
			
		||||
        "".to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_path(&mut self, _new_path: String) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
                message_info(&format!(
 | 
			
		||||
                    "reading dirs is not supported yet, skipping {:?}",
 | 
			
		||||
                    entry.path()
 | 
			
		||||
                ));
 | 
			
		||||
            } else {
 | 
			
		||||
                message_info(&format!("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: RegexpPatchObj = match RegexpPatchObj::deserialize(patch_des) {
 | 
			
		||||
                        Ok(patch) => patch,
 | 
			
		||||
                        Err(err) => return Err(Box::from(err)),
 | 
			
		||||
                    };
 | 
			
		||||
                    message_info(&format!("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(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_path(&self) -> String {
 | 
			
		||||
        self.path.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_path(&mut self, new_path: String) {
 | 
			
		||||
        self.path = new_path
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_path(&self) -> String {
 | 
			
		||||
        self.path.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_path(&mut self, new_path: String) {
 | 
			
		||||
        self.path = new_path
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 get_path(&self) -> String {
 | 
			
		||||
        // Empty stings, cause cc patch doesn't have a path
 | 
			
		||||
        "".to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_path(&mut self, _new_path: String) {
 | 
			
		||||
        ()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn patch_action_from_definition(
 | 
			
		||||
    patch: Patch,
 | 
			
		||||
) -> Result<Box<dyn PatchInterface>, Box<dyn std::error::Error>> {
 | 
			
		||||
    if let Some(regexp) = patch.regexp {
 | 
			
		||||
        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.clone());
 | 
			
		||||
                match fs::canonicalize(path).ok() {
 | 
			
		||||
                    Some(can_path) => can_path.into_os_string().into_string().ok().unwrap(),
 | 
			
		||||
                    None => git.path.clone(),
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }));
 | 
			
		||||
    } 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(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								lib/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "helmzoo_lib"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
serde = { workspace = true }
 | 
			
		||||
serde_json ={ workspace = true }
 | 
			
		||||
serde_yaml = { workspace = true }
 | 
			
		||||
tempfile = { workspace = true }
 | 
			
		||||
base64 = { workspace = true }
 | 
			
		||||
console = "0.15.8"
 | 
			
		||||
dialoguer = "0.11.0"
 | 
			
		||||
env_logger = "0.10.1"
 | 
			
		||||
indicatif = "0.17.7"
 | 
			
		||||
log = "0.4.20"
 | 
			
		||||
which = "6.0.0"
 | 
			
		||||
handlebars = "5.0.0"
 | 
			
		||||
chrono = "0.4.31"
 | 
			
		||||
							
								
								
									
										144
									
								
								lib/src/cli.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/src/cli.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    error::Error,
 | 
			
		||||
    fs::{self, read_dir},
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
    process::Command,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use which::which;
 | 
			
		||||
 | 
			
		||||
use crate::output::message_info;
 | 
			
		||||
 | 
			
		||||
pub fn cli_exec(command: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    message_info(&format!("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 fn cli_exec_from_dir(command: String, dir: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    message_info(&format!("executing: {} from {}", command, dir));
 | 
			
		||||
    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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A helper that checks wheter all the required binaries are installed
 | 
			
		||||
pub fn check_prerequisites(bins: Vec<String>) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    message_info(&"checking prerequisites".to_string());
 | 
			
		||||
    for bin in bins {
 | 
			
		||||
        message_info(&format!("checking {}", bin));
 | 
			
		||||
        which(bin)?;
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_full_path(rel_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    match PathBuf::from(&rel_path)
 | 
			
		||||
        .canonicalize()?
 | 
			
		||||
        .into_os_string()
 | 
			
		||||
        .into_string()
 | 
			
		||||
    {
 | 
			
		||||
        Ok(path) => Ok(path),
 | 
			
		||||
        Err(_) => Err(Box::from(format!(
 | 
			
		||||
            "{} can't be converted into a full path",
 | 
			
		||||
            rel_path
 | 
			
		||||
        ))),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_full_path_dir(rel_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    let res = match PathBuf::from(&rel_path).parent() {
 | 
			
		||||
        Some(res) => res.canonicalize()?.into_os_string().into_string(),
 | 
			
		||||
        None => PathBuf::from(&rel_path)
 | 
			
		||||
            .canonicalize()?
 | 
			
		||||
            .into_os_string()
 | 
			
		||||
            .into_string(),
 | 
			
		||||
    };
 | 
			
		||||
    match res {
 | 
			
		||||
        Ok(path) => Ok(path),
 | 
			
		||||
        Err(_) => Err(Box::from(format!(
 | 
			
		||||
            "{} can't be converted into a full path",
 | 
			
		||||
            rel_path
 | 
			
		||||
        ))),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Copy files from source to destination recursively.
 | 
			
		||||
pub fn copy_recursively(
 | 
			
		||||
    source: impl AsRef<Path>,
 | 
			
		||||
    destination: impl AsRef<Path>,
 | 
			
		||||
) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    for entry in 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 {
 | 
			
		||||
            message_info(&format!("trying to copy {:?}", entry.path()));
 | 
			
		||||
            fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_path_relative(path: String) -> bool {
 | 
			
		||||
    !path.starts_with('/')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::{cli_exec, cli_exec_from_dir};
 | 
			
		||||
    use tempfile::TempDir;
 | 
			
		||||
 | 
			
		||||
    #[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 = TempDir::new().unwrap();
 | 
			
		||||
        let dir_str = dir.path().to_str().unwrap().to_string();
 | 
			
		||||
        let command = "echo $PWD";
 | 
			
		||||
        let test = cli_exec_from_dir(command.to_string(), dir_str.clone());
 | 
			
		||||
        assert!(test.unwrap().to_string().contains(dir_str.as_str()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_stderr_current_dir() {
 | 
			
		||||
        let dir = TempDir::new().unwrap();
 | 
			
		||||
        let dir_str = dir.path().to_str().unwrap().to_string();
 | 
			
		||||
        let command = ">&2 echo \"error\" && exit 1";
 | 
			
		||||
        let test = cli_exec_from_dir(command.to_string(), dir_str.clone());
 | 
			
		||||
        assert_eq!(test.err().unwrap().to_string(), "error\n".to_string());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								lib/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
use std::{error::Error, ffi::OsStr, fs::File, path::Path};
 | 
			
		||||
 | 
			
		||||
use serde::de::DeserializeOwned;
 | 
			
		||||
 | 
			
		||||
pub trait ConfigImpl {
 | 
			
		||||
    fn apply_includes(&mut self, config_path: String) -> Result<(), Box<dyn Error>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_config<T: DeserializeOwned>(path: String) -> Result<T, Box<dyn Error>> {
 | 
			
		||||
    let config_content = File::open(path.clone())?;
 | 
			
		||||
    let config = match get_extension_from_filename(&path) {
 | 
			
		||||
        Some(ext) => match ext {
 | 
			
		||||
            "yaml" | "yml" => serde_yaml::from_reader(config_content)?,
 | 
			
		||||
            _ => return Err(Box::from(format!("{} files are not supported", ext))),
 | 
			
		||||
        },
 | 
			
		||||
        None => return Err(Box::from("can't read file without extension")),
 | 
			
		||||
    };
 | 
			
		||||
    Ok(config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_extension_from_filename(filename: &str) -> Option<&str> {
 | 
			
		||||
    Path::new(filename).extension().and_then(OsStr::to_str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::{get_extension_from_filename, read_config};
 | 
			
		||||
    use serde::{Deserialize, Serialize};
 | 
			
		||||
    use std::{error::Error, fs::File, io::Write};
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_extension_getter() {
 | 
			
		||||
        let filepath = "/tmp/config.yaml";
 | 
			
		||||
        let extension = get_extension_from_filename(filepath);
 | 
			
		||||
        assert_eq!(extension, Some("yaml"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_extension_getter_empty() {
 | 
			
		||||
        let filepath = "/tmp/config";
 | 
			
		||||
        let extension = get_extension_from_filename(filepath);
 | 
			
		||||
        assert_eq!(extension, None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
    struct DummyConfig {
 | 
			
		||||
        string: String,
 | 
			
		||||
        amounts: Vec<DummyProperty>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
    struct DummyProperty {
 | 
			
		||||
        amount: i32,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn prepare_test_file(name: &str, data: &str) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let dir = tempdir()?;
 | 
			
		||||
        let file_path = dir.into_path().join(&name);
 | 
			
		||||
        let mut file = File::create(file_path.clone())?;
 | 
			
		||||
        file.write_all(data.as_bytes())?;
 | 
			
		||||
        let path = file_path.into_os_string().to_str().unwrap().to_string();
 | 
			
		||||
        Ok(path)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_config_reader() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let content = "---
 | 
			
		||||
            string: test
 | 
			
		||||
            amounts: 
 | 
			
		||||
              - amount: 4
 | 
			
		||||
              - amount: 5 
 | 
			
		||||
            ";
 | 
			
		||||
        let file_path = prepare_test_file("config.yaml", content)?;
 | 
			
		||||
        let config_data: DummyConfig;
 | 
			
		||||
        config_data = read_config(file_path)?;
 | 
			
		||||
 | 
			
		||||
        let expected = DummyConfig {
 | 
			
		||||
            string: "test".to_string(),
 | 
			
		||||
            amounts: vec![DummyProperty { amount: 4 }, DummyProperty { amount: 5 }],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        assert_eq!(expected, config_data);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										327
									
								
								lib/src/git.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								lib/src/git.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,327 @@
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::cli::{cli_exec, cli_exec_from_dir};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct GitOptions {
 | 
			
		||||
    bin: String,
 | 
			
		||||
    workdir: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct CheckoutOptions {
 | 
			
		||||
    // Checkout with -b if branch doesn't exist
 | 
			
		||||
    pub create: bool,
 | 
			
		||||
    #[serde(alias = "ref")]
 | 
			
		||||
    pub git_ref: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct PushOptions {
 | 
			
		||||
    pub rebase_to: Option<String>,
 | 
			
		||||
    // If rebase, should be always set to true
 | 
			
		||||
    pub force: bool,
 | 
			
		||||
    pub brahcn: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct CommitOptions {
 | 
			
		||||
    pub message: String,
 | 
			
		||||
    pub add: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GitOptions {
 | 
			
		||||
    pub fn new(bin: String, workdir: Option<String>) -> Self {
 | 
			
		||||
        Self { bin, workdir }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct Git {
 | 
			
		||||
    pub url: String,
 | 
			
		||||
    pub repo_path: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Git {
 | 
			
		||||
    pub fn new(url: String, repo_path: String) -> Self {
 | 
			
		||||
        Self { url, repo_path }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clone(&self, git_opts: GitOptions) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let cmd = format!("{} clone {} {}", git_opts.bin, self.url, self.repo_path);
 | 
			
		||||
        match self.exec(cmd, git_opts.workdir.clone()) {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => Err(err),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn checkout(
 | 
			
		||||
        &self,
 | 
			
		||||
        git_opts: GitOptions,
 | 
			
		||||
        opts: CheckoutOptions,
 | 
			
		||||
    ) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "{} -C {} checkout {}",
 | 
			
		||||
            git_opts.bin, self.repo_path, opts.git_ref
 | 
			
		||||
        );
 | 
			
		||||
        match self.exec(cmd, git_opts.workdir.clone()) {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => match opts.create {
 | 
			
		||||
                true => {
 | 
			
		||||
                    let cmd = format!(
 | 
			
		||||
                        "{} -C {} checkout -b {}",
 | 
			
		||||
                        git_opts.bin, self.repo_path, opts.git_ref
 | 
			
		||||
                    );
 | 
			
		||||
                    match self.exec(cmd, git_opts.workdir.clone()) {
 | 
			
		||||
                        Ok(_) => Ok(()),
 | 
			
		||||
                        Err(err) => Err(err),
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                false => Err(err),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn commit(&self, git_opts: GitOptions, opts: CommitOptions) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        if opts.add {
 | 
			
		||||
            let cmd = format!("{} -C {} add .", git_opts.bin, self.repo_path);
 | 
			
		||||
            let _ = self.exec(cmd, git_opts.workdir.clone())?;
 | 
			
		||||
        }
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "{} diff --staged --quiet || {} -C {} commit -m \"{}\"",
 | 
			
		||||
            git_opts.bin, git_opts.bin, self.repo_path, opts.message
 | 
			
		||||
        );
 | 
			
		||||
        match self.exec(cmd, git_opts.workdir.clone()) {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => Err(err),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: Add tests for rebase and force
 | 
			
		||||
    pub fn push(&self, git_opts: GitOptions, opts: PushOptions) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        if let Some(branch) = opts.rebase_to {
 | 
			
		||||
            let cmd = format!("{} -C {} rebase {}", git_opts.bin, self.repo_path, branch);
 | 
			
		||||
            let _ = self.exec(cmd, git_opts.workdir.clone())?;
 | 
			
		||||
        }
 | 
			
		||||
        let mut args = String::new();
 | 
			
		||||
        if opts.force {
 | 
			
		||||
            args = format!("{} --force", args);
 | 
			
		||||
        }
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "{} -C {} push --set-upstream origin {} {}",
 | 
			
		||||
            git_opts.bin, self.repo_path, opts.brahcn, args
 | 
			
		||||
        );
 | 
			
		||||
        let _ = self.exec(cmd, git_opts.workdir.clone());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: Add tests
 | 
			
		||||
    pub fn init(&self, git_opts: GitOptions) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let cmd = format!("{} -C {} init", git_opts.bin, self.repo_path);
 | 
			
		||||
        let _ = self.exec(cmd, git_opts.workdir.clone())?;
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "{} -C {} remote add origin {}",
 | 
			
		||||
            git_opts.bin, self.repo_path, self.url
 | 
			
		||||
        );
 | 
			
		||||
        let _ = self.exec(cmd, git_opts.workdir.clone())?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn exec(&self, cmd: String, workdir: Option<String>) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        match workdir {
 | 
			
		||||
            Some(workdir) => cli_exec_from_dir(cmd, workdir),
 | 
			
		||||
            None => cli_exec(cmd),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::cli::cli_exec_from_dir;
 | 
			
		||||
    use crate::git::{CheckoutOptions, CommitOptions, Git, PushOptions};
 | 
			
		||||
    use std::error::Error;
 | 
			
		||||
    use std::path::Path;
 | 
			
		||||
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    use super::GitOptions;
 | 
			
		||||
 | 
			
		||||
    fn prepare_a_repo() -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = tempdir()?
 | 
			
		||||
            .into_path()
 | 
			
		||||
            .into_os_string()
 | 
			
		||||
            .into_string()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        cli_exec_from_dir("git init".to_string(), tmp_dir.clone())?;
 | 
			
		||||
        Ok(tmp_dir)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn prepare_a_workdir() -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = tempdir()?
 | 
			
		||||
            .into_path()
 | 
			
		||||
            .into_os_string()
 | 
			
		||||
            .into_string()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        Ok(tmp_dir)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_pull_no_wd() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = format!("{}/test", tmp_dir);
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        git.clone(git_opts)?;
 | 
			
		||||
        let result = Path::new(&git_dir).exists();
 | 
			
		||||
        assert!(result);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_pull_wd() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        println!("{}", tmp_dir.clone());
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        git.clone(git_opts)?;
 | 
			
		||||
        let result = Path::new(&format!("{}/{}", tmp_dir, git_dir)).exists();
 | 
			
		||||
        assert!(result);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_checkout_no_create() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        cli_exec_from_dir("git checkout -b test".to_string(), git.url.clone())?;
 | 
			
		||||
        cli_exec_from_dir(
 | 
			
		||||
            "touch test.txt && git add . && git commit -m test".to_string(),
 | 
			
		||||
            git.url.clone(),
 | 
			
		||||
        )?;
 | 
			
		||||
        cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
 | 
			
		||||
        let checkout_options = CheckoutOptions {
 | 
			
		||||
            create: false,
 | 
			
		||||
            git_ref: "test".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        git.checkout(git_opts, checkout_options)?;
 | 
			
		||||
        let result = cli_exec_from_dir(
 | 
			
		||||
            "git rev-parse --abbrev-ref HEAD".to_string(),
 | 
			
		||||
            format!("{}/{}", tmp_dir.clone(), git_dir.clone()),
 | 
			
		||||
        )?;
 | 
			
		||||
        assert_eq!(result, "test");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_checkout_no_create_err() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
 | 
			
		||||
        cli_exec_from_dir(
 | 
			
		||||
            "touch test.txt && git add . && git commit -m test".to_string(),
 | 
			
		||||
            git.url.clone(),
 | 
			
		||||
        )?;
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        let checkout_options = CheckoutOptions {
 | 
			
		||||
            create: false,
 | 
			
		||||
            git_ref: "test".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        let res = git.checkout(git_opts, checkout_options);
 | 
			
		||||
        assert!(res.is_err());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_checkout_create() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
 | 
			
		||||
        cli_exec_from_dir(
 | 
			
		||||
            "touch test.txt && git add . && git commit -m test".to_string(),
 | 
			
		||||
            git.url.clone(),
 | 
			
		||||
        )?;
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        let checkout_options = CheckoutOptions {
 | 
			
		||||
            create: true,
 | 
			
		||||
            git_ref: "test".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        git.checkout(git_opts, checkout_options)?;
 | 
			
		||||
        let result = cli_exec_from_dir(
 | 
			
		||||
            "git rev-parse --abbrev-ref HEAD".to_string(),
 | 
			
		||||
            format!("{}/{}", tmp_dir.clone(), git_dir.clone()),
 | 
			
		||||
        )?;
 | 
			
		||||
        assert_eq!(result, "test");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_commit() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        let commit_options = CommitOptions {
 | 
			
		||||
            message: "test commit".to_string(),
 | 
			
		||||
            add: false,
 | 
			
		||||
        };
 | 
			
		||||
        cli_exec_from_dir("touch test.txt && git add .".to_string(), full_path.clone())?;
 | 
			
		||||
        git.commit(git_opts, commit_options)?;
 | 
			
		||||
        let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), full_path)?;
 | 
			
		||||
        assert_eq!(result, "test commit\n");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_commit_add() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        let commit_options = CommitOptions {
 | 
			
		||||
            message: "test commit".to_string(),
 | 
			
		||||
            add: true,
 | 
			
		||||
        };
 | 
			
		||||
        cli_exec_from_dir("touch test.txt".to_string(), full_path.clone())?;
 | 
			
		||||
        git.commit(git_opts, commit_options)?;
 | 
			
		||||
        let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), full_path)?;
 | 
			
		||||
        assert_eq!(result, "test commit\n");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_push_no_rebase() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let tmp_dir = prepare_a_workdir()?;
 | 
			
		||||
        let git_dir = "test".to_string();
 | 
			
		||||
        let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
 | 
			
		||||
        let git = Git::new(prepare_a_repo()?, git_dir.clone());
 | 
			
		||||
        let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
 | 
			
		||||
        git.clone(git_opts.clone())?;
 | 
			
		||||
        let push_options = PushOptions {
 | 
			
		||||
            rebase_to: None,
 | 
			
		||||
            force: false,
 | 
			
		||||
            brahcn: "main".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        cli_exec_from_dir("git checkout -b main".to_string(), full_path.clone())?;
 | 
			
		||||
        cli_exec_from_dir(
 | 
			
		||||
            "touch test.txt && git add . && git commit -m 'test commit'".to_string(),
 | 
			
		||||
            full_path.clone(),
 | 
			
		||||
        )?;
 | 
			
		||||
        git.push(git_opts, push_options)?;
 | 
			
		||||
        cli_exec_from_dir("git checkout main".to_string(), git.url.clone())?;
 | 
			
		||||
        let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), git.url)?;
 | 
			
		||||
        assert_eq!(result, "test commit\n");
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								lib/src/helm/chart.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								lib/src/helm/chart.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use super::repository::{Repository, RepositoryImpl};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
 | 
			
		||||
pub struct Chart {
 | 
			
		||||
    // A name of the helm chart
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    // A reference to repository by name
 | 
			
		||||
    pub repository: String,
 | 
			
		||||
    pub mirrors: Vec<String>,
 | 
			
		||||
    // Versions to be mirrored
 | 
			
		||||
    #[serde(default = "latest")]
 | 
			
		||||
    pub version: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) fn latest() -> String {
 | 
			
		||||
    "latest".to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Chart {
 | 
			
		||||
    pub fn find_repo(
 | 
			
		||||
        &self,
 | 
			
		||||
        repositories: Vec<Repository>,
 | 
			
		||||
    ) -> Result<Box<dyn RepositoryImpl>, Box<dyn Error>> {
 | 
			
		||||
        for repository in repositories {
 | 
			
		||||
            if repository.name == self.repository {
 | 
			
		||||
                if let Some(helm) = repository.helm {
 | 
			
		||||
                    return Ok(Box::from(helm));
 | 
			
		||||
                } else if let Some(git) = repository.git {
 | 
			
		||||
                    return Ok(Box::from(git));
 | 
			
		||||
                } else {
 | 
			
		||||
                    return Err(Box::from("unsupported kind of repository"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //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);
 | 
			
		||||
        Err(Box::from(error_msg))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::error::Error;
 | 
			
		||||
 | 
			
		||||
    use crate::helm::{
 | 
			
		||||
        chart::latest,
 | 
			
		||||
        helm_repository::HelmRepo,
 | 
			
		||||
        repository::{Repository, RepositoryImpl},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    use super::Chart;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_find_repo() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let chart = Chart {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            repository: "test".to_string(),
 | 
			
		||||
            mirrors: vec!["test".to_string()],
 | 
			
		||||
            version: latest(),
 | 
			
		||||
        };
 | 
			
		||||
        let repo = Repository {
 | 
			
		||||
            name: "test".to_string(),
 | 
			
		||||
            helm: Some(HelmRepo {
 | 
			
		||||
                url: "test.rocks".to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
        };
 | 
			
		||||
        let res = chart.find_repo(vec![repo])?;
 | 
			
		||||
        assert_eq!(res.get_url(), "test.rocks".to_string());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								lib/src/helm/git_repository.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/src/helm/git_repository.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
use crate::cli::cli_exec_from_dir;
 | 
			
		||||
use crate::git::CheckoutOptions;
 | 
			
		||||
use crate::git::GitOptions;
 | 
			
		||||
use crate::{cli::cli_exec, helm::repository::Version};
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::fs::{self, rename};
 | 
			
		||||
 | 
			
		||||
use base64::{engine::general_purpose, Engine};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
use crate::git::Git;
 | 
			
		||||
 | 
			
		||||
use super::{chart::Chart, repository::RepositoryImpl};
 | 
			
		||||
// A struct that represents a git repo with a chart
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct GitRepo {
 | 
			
		||||
    pub url: String,
 | 
			
		||||
    #[serde(alias = "ref")]
 | 
			
		||||
    pub git_ref: String,
 | 
			
		||||
    pub path: String,
 | 
			
		||||
    #[serde(default = "default_git_bin")]
 | 
			
		||||
    pub(crate) git_bin: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn default_git_bin() -> String {
 | 
			
		||||
    "git".to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RepositoryImpl for GitRepo {
 | 
			
		||||
    fn pull_chart(&self, chart: Chart, workdir_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let repo_name = general_purpose::STANDARD_NO_PAD.encode(self.get_url().clone());
 | 
			
		||||
        let git_instance = Git::new(self.get_url(), repo_name.clone());
 | 
			
		||||
 | 
			
		||||
        let git_opts = GitOptions::new(self.git_bin.clone(), Some(workdir_path.clone()));
 | 
			
		||||
        git_instance.clone(git_opts.clone())?;
 | 
			
		||||
 | 
			
		||||
        let checkout_opts = CheckoutOptions {
 | 
			
		||||
            create: true,
 | 
			
		||||
            git_ref: self.git_ref.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        git_instance.checkout(git_opts, checkout_opts)?;
 | 
			
		||||
 | 
			
		||||
        let old_dir_name = format!(
 | 
			
		||||
            "{}/{}/{}/{}",
 | 
			
		||||
            workdir_path,
 | 
			
		||||
            repo_name,
 | 
			
		||||
            self.path,
 | 
			
		||||
            chart.name.clone()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        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::<Version>(&helm_stdout) {
 | 
			
		||||
            Ok(res) => {
 | 
			
		||||
                new_dir_name = format!("{}/{}-{}", workdir_path, chart.name.clone(), 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_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(new_dir_name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_url(&self) -> String {
 | 
			
		||||
        self.url.clone()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								lib/src/helm/helm_repository.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lib/src/helm/helm_repository.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
use std::{error::Error, fs::rename};
 | 
			
		||||
 | 
			
		||||
use base64::{engine::general_purpose, Engine};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::cli::{cli_exec, cli_exec_from_dir};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    chart::Chart,
 | 
			
		||||
    repository::{RepositoryImpl, Version},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A struct that represents a regular helm repo
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct HelmRepo {
 | 
			
		||||
    // A URL of the helm repository
 | 
			
		||||
    pub url: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LATEST_VERSION: &str = "latest";
 | 
			
		||||
 | 
			
		||||
impl RepositoryImpl for HelmRepo {
 | 
			
		||||
    fn pull_chart(&self, chart: Chart, workdir_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        match self.repo_kind_from_url(self.clone())? {
 | 
			
		||||
            RepoKind::Default => self.pull_default(chart, workdir_path),
 | 
			
		||||
            RepoKind::Oci => self.pull_oci(chart, workdir_path),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_url(&self) -> String {
 | 
			
		||||
        self.url.clone()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) enum RepoKind {
 | 
			
		||||
    Default,
 | 
			
		||||
    Oci,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HelmRepo {
 | 
			
		||||
    fn repo_kind_from_url(&self, repository: HelmRepo) -> Result<RepoKind, Box<dyn Error>> {
 | 
			
		||||
        let prefix = 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_oci(&self, chart: Chart, workdir_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        let args = match chart.version.as_str() {
 | 
			
		||||
            LATEST_VERSION => "".to_string(),
 | 
			
		||||
            _ => format!("--version {}", chart.version.clone()),
 | 
			
		||||
        };
 | 
			
		||||
        let repo = match self.get_url().ends_with('/') {
 | 
			
		||||
            true => {
 | 
			
		||||
                let mut repo = self.get_url().clone();
 | 
			
		||||
                repo.pop();
 | 
			
		||||
                repo
 | 
			
		||||
            }
 | 
			
		||||
            false => self.get_url().clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "helm pull {}/{} {} --destination {} --untar",
 | 
			
		||||
            repo, chart.name, args, workdir_path
 | 
			
		||||
        );
 | 
			
		||||
        cli_exec(cmd)?;
 | 
			
		||||
        // Get the version
 | 
			
		||||
        let cmd = format!("helm show chart {}/{}", workdir_path, chart.name);
 | 
			
		||||
        let helm_stdout = cli_exec(cmd)?;
 | 
			
		||||
        let old_dir_name = format!("{}/{}", workdir_path, chart.name);
 | 
			
		||||
        let new_dir_name: String;
 | 
			
		||||
        match serde_yaml::from_str::<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)),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // TODO: Do we really need it?
 | 
			
		||||
        let cmd = "helm show chart . | yq '.version'".to_string();
 | 
			
		||||
        let _version = cli_exec_from_dir(cmd, new_dir_name.clone())?;
 | 
			
		||||
        Ok(new_dir_name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn pull_default(&self, chart: Chart, workdir_path: String) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
        // Add repo and update
 | 
			
		||||
        let repo_local_name = general_purpose::STANDARD_NO_PAD.encode(self.get_url());
 | 
			
		||||
        let cmd = format!("helm repo add {} {}", repo_local_name, self.get_url());
 | 
			
		||||
        cli_exec(cmd)?;
 | 
			
		||||
        cli_exec("helm repo update".to_string())?;
 | 
			
		||||
 | 
			
		||||
        let args = match chart.version.as_str() {
 | 
			
		||||
            LATEST_VERSION => "".to_string(),
 | 
			
		||||
            _ => format!("--version {}", chart.version.clone()),
 | 
			
		||||
        };
 | 
			
		||||
        let cmd = format!(
 | 
			
		||||
            "helm pull {}/{} {} --destination {} --untar",
 | 
			
		||||
            repo_local_name, chart.name, args, workdir_path
 | 
			
		||||
        );
 | 
			
		||||
        cli_exec(cmd)?;
 | 
			
		||||
 | 
			
		||||
        // Get the version
 | 
			
		||||
        let cmd = format!("helm show chart {}/{}", workdir_path, chart.name);
 | 
			
		||||
        let helm_stdout = cli_exec(cmd)?;
 | 
			
		||||
        let old_dir_name = format!("{}/{}", workdir_path, chart.name);
 | 
			
		||||
        let new_dir_name: String;
 | 
			
		||||
        match serde_yaml::from_str::<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)?;
 | 
			
		||||
 | 
			
		||||
        // TODO: Do we really need it?
 | 
			
		||||
        let cmd = "helm show chart . | yq '.version'".to_string();
 | 
			
		||||
        let _version = cli_exec_from_dir(cmd, new_dir_name.clone())?;
 | 
			
		||||
        Ok(new_dir_name)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								lib/src/helm/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								lib/src/helm/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
pub mod chart;
 | 
			
		||||
pub mod git_repository;
 | 
			
		||||
pub mod helm_repository;
 | 
			
		||||
pub mod repository;
 | 
			
		||||
							
								
								
									
										36
									
								
								lib/src/helm/repository.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								lib/src/helm/repository.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use super::{chart::Chart, git_repository::GitRepo, helm_repository::HelmRepo};
 | 
			
		||||
 | 
			
		||||
// A struct that represents a helm repository
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct Repository {
 | 
			
		||||
    // A name of the repo. It's going to be used by tools
 | 
			
		||||
    // to get a URL, so it can be any string
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub helm: Option<HelmRepo>,
 | 
			
		||||
    pub git: Option<GitRepo>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Supported kinds of helm repos
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) enum RepositoryKind {
 | 
			
		||||
    // Regular helm repos and OCI
 | 
			
		||||
    Helm,
 | 
			
		||||
    // Git, it's not supposed to use helm-git plugin
 | 
			
		||||
    // but instead it's just using git to get a repo
 | 
			
		||||
    // and then look for charts in the repo
 | 
			
		||||
    Git,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait RepositoryImpl {
 | 
			
		||||
    fn pull_chart(&self, chart: Chart, workdir_path: String) -> Result<String, Box<dyn Error>>;
 | 
			
		||||
    fn get_url(&self) -> String;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub(crate) struct Version {
 | 
			
		||||
    pub(crate) version: String,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								lib/src/include.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/src/include.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
 | 
			
		||||
pub struct Include<T> {
 | 
			
		||||
    pub path: String,
 | 
			
		||||
    pub kind: T,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								lib/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
pub mod cli;
 | 
			
		||||
pub mod config;
 | 
			
		||||
pub mod git;
 | 
			
		||||
pub mod helm;
 | 
			
		||||
pub mod include;
 | 
			
		||||
pub mod output;
 | 
			
		||||
pub mod template;
 | 
			
		||||
pub mod workdir;
 | 
			
		||||
 | 
			
		||||
pub fn add(left: usize, right: usize) -> usize {
 | 
			
		||||
    left + right
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn it_works() {
 | 
			
		||||
        let result = add(2, 2);
 | 
			
		||||
        assert_eq!(result, 4);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								lib/src/output.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								lib/src/output.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
use console::style;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
pub fn message_empty(msg: &str) {
 | 
			
		||||
    println!(" {}", style(msg).blue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn message_info(msg: &str) {
 | 
			
		||||
    let prefix = format!("{}", style("-->"));
 | 
			
		||||
    let msg = format!("{} {}", prefix, msg,);
 | 
			
		||||
    println!(" {}", style(msg).blue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn message_error(err: Box<dyn Error>) {
 | 
			
		||||
    let prefix = format!("{}", style("!->").red());
 | 
			
		||||
    let msg = format!("{} {}", prefix, err);
 | 
			
		||||
    println!(" {}", style(msg).red());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								lib/src/template.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								lib/src/template.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
use chrono::prelude::*;
 | 
			
		||||
use handlebars::{handlebars_helper, Handlebars};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
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 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn render<T>(string: String, data: &T) -> Result<String, Box<dyn std::error::Error>>
 | 
			
		||||
where
 | 
			
		||||
    T: Serialize,
 | 
			
		||||
{
 | 
			
		||||
    let mut reg = register_handlebars();
 | 
			
		||||
    let tmpl_name = "template";
 | 
			
		||||
    reg.register_template_string(tmpl_name, string)?;
 | 
			
		||||
    let result = reg.render(tmpl_name, data)?;
 | 
			
		||||
    Ok(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
    use chrono::Utc;
 | 
			
		||||
 | 
			
		||||
    use crate::template::register_handlebars;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_date_helper() {
 | 
			
		||||
        let mut reg = register_handlebars();
 | 
			
		||||
        reg.register_template_string("test", "{{ date }}").unwrap();
 | 
			
		||||
        let result = reg
 | 
			
		||||
            .render("test", &HashMap::<String, String>::new())
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let expected = Utc::now().format("%Y-%m-%d").to_string();
 | 
			
		||||
        assert_eq!(result, expected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_time_helper() {
 | 
			
		||||
        let mut reg = register_handlebars();
 | 
			
		||||
        reg.register_template_string("test", "{{ time }}").unwrap();
 | 
			
		||||
        let result = reg
 | 
			
		||||
            .render("test", &HashMap::<String, String>::new())
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let expected = Utc::now().format("%H-%M-%S").to_string();
 | 
			
		||||
        assert_eq!(result, expected);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								lib/src/workdir.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								lib/src/workdir.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
use crate::output::message_info;
 | 
			
		||||
use std::{error::Error, fs::create_dir, path::PathBuf};
 | 
			
		||||
 | 
			
		||||
pub fn setup_workdir(path: Option<String>) -> Result<String, Box<dyn Error>> {
 | 
			
		||||
    let path = match path {
 | 
			
		||||
        Some(res) => {
 | 
			
		||||
            message_info(&format!("trying to create a dir: {}", res));
 | 
			
		||||
            match create_dir(res.clone()) {
 | 
			
		||||
                Ok(_) => PathBuf::from(res),
 | 
			
		||||
                Err(err) => {
 | 
			
		||||
                    let _msg = format!("couldn't create dir {}", res);
 | 
			
		||||
                    return Err(Box::from(err));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None => {
 | 
			
		||||
            message_info("trying to create a temporary dir");
 | 
			
		||||
            // I'm using into_path to prevent the dir from being removed
 | 
			
		||||
            tempdir()?.into_path()
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    Ok(path.into_os_string().into_string().unwrap())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use std::{error::Error, fs::remove_dir_all, path::Path};
 | 
			
		||||
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    use crate::workdir::setup_workdir;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_temporary_dir() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let wd = setup_workdir(None)?;
 | 
			
		||||
        let result = Path::new(&wd).exists();
 | 
			
		||||
        assert!(result);
 | 
			
		||||
        remove_dir_all(wd)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_specified_dir() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        let dir = tempdir()?;
 | 
			
		||||
        let path = dir.path().join("test").to_str().unwrap().to_string();
 | 
			
		||||
        let wd = setup_workdir(Some(path.clone()))?;
 | 
			
		||||
        let result = Path::new(&wd).exists();
 | 
			
		||||
        assert!(result);
 | 
			
		||||
        assert!(setup_workdir(Some(path)).is_err());
 | 
			
		||||
        remove_dir_all(dir)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user