Helmule MVP
Basic functionality is there, helmule can mirror helm chart with small modifications
This commit is contained in:
parent
2f8170cf95
commit
aabcb21f3b
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,13 +4,8 @@
|
|||||||
debug/
|
debug/
|
||||||
target/
|
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
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.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(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user